Распространенные ошибки многопоточности на iPhone делают новички - PullRequest
8 голосов
/ 31 августа 2009

Я только что ввел многопоточность в свое приложение просто, чтобы заставить работать глупый UIActivityIndicatorView. Хорошо, индикатор активности работает, хорошо - но теперь мое приложение иногда падает, а иногда нет - при других контролируемых условиях ... Мне нужно это выяснить, но я не знаю, с чего начать искать ...

Итак, какие же ошибки часто допускают новички в многопоточности на iPhone? Пожалуйста, будьте конкретны в своих ответах. Спасибо за ваше время.

ОБНОВЛЕНИЕ : я добавил проблемный источник для справки.

//--------------------Where the multithreading starts------------------------


-(IBAction)processEdits:(id)sender
{
        //Try to disable the UI to prevent user from launching duplicate threads
    [self.view setUserInteractionEnabled:NO];

        //Initialize indicator (delcared in .h)
    myIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(155, 230, 20, 20)];
    myIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
    [self.view addSubview:myIndicator];
    [self.view bringSubviewToFront:myIndicator];
    [myIndicator startAnimating];


    //Prepare and set properties of the NEXT modal view controller to switch to
    controller = [[EndViewController alloc] initWithNibName:@"EndViewController" bundle:nil];

    controller.delegate = self;

    [self performSelectorInBackground:@selector(threadWork:) withObject:nil];


}



//-----------------------------THE THREAD WORK--------------------------------


-(IBAction)threadWork:(id)sender{

    NSAutoreleasePool * pool;
    NSString *          status;

    pool = [[NSAutoreleasePool alloc] init];
    assert(pool != nil);


        //The image processing work that takes time
    controller.photoImage = [self buildPhoto];

    //Stop the UIActivityIndicatorView and launch next modal view
    [self performSelectorOnMainThread:@selector(stopSpinner:)withObject:nil waitUntilDone:NO];

    [pool drain];


}




//-------------------Most of the WORKLOAD called in above thread ------------------------



-(UIImage*)buildPhoto
{
    /* 
       This is the work performed in the background thread. Process photos that the user has edited and arrange them into a UIView to be finally flattened out into a new UIImage. Problem: UI usually changes for some reason during this work.
       */

    UIView* photoContainerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,975,1300)];
    photoContainerView.backgroundColor = [UIColor whiteColor];
    UIImage* purikuraFlattened;
    int spacerX = 10;
    int spacerY = 10;

    switch (myPattern) {

        case 0:

            photoContainerView.frame = CGRectMake(0, 0, 320, 427);
            layoutSingle = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x,photoContainerView.frame.origin.y,320,427)];
            [photoContainerView addSubview:layoutSingle];
            layoutSingle.image = editPhotoData1;

            break;


        case 1:

            layoutAimg1 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY, 427, 320)];
            layoutAimg2 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY, 427, 320)];
            layoutAimg3 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+320, 427, 320)];
            layoutAimg4 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+320, 427, 320)];
            layoutAimg5 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+(320*2), 427, 320)];
            layoutAimg6 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+(320*2), 427, 320)];
            layoutAimg7 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+(320*3), 427, 320)];
            layoutAimg8 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+(320*3), 427, 320)];

            [photoContainerView addSubview:layoutAimg1];
            [photoContainerView addSubview:layoutAimg2];
            [photoContainerView addSubview:layoutAimg3];
            [photoContainerView addSubview:layoutAimg4];
            [photoContainerView addSubview:layoutAimg5];
            [photoContainerView addSubview:layoutAimg6];
            [photoContainerView addSubview:layoutAimg7];
            [photoContainerView addSubview:layoutAimg8];


            if(myShots == 1){

            rotPhoto1 = [self rotateImage:editPhotoData1.size:editPhotoData1];

                layoutAimg1.image = rotPhoto1;
                layoutAimg2.image = rotPhoto1;
                layoutAimg3.image = rotPhoto1;
                layoutAimg4.image = rotPhoto1;
                layoutAimg5.image = rotPhoto1;
                layoutAimg6.image = rotPhoto1;
                layoutAimg7.image = rotPhoto1;
                layoutAimg8.image = rotPhoto1;



            }else if(myShots == 2){


            rotPhoto1 = [self rotateImage:editPhotoData1.size: editPhotoData1];
            rotPhoto2 = [self rotateImage:editPhotoData2.size: editPhotoData2];

                layoutAimg1.image = rotPhoto1;
                layoutAimg2.image = rotPhoto2;
                layoutAimg3.image = rotPhoto2;
                layoutAimg4.image = rotPhoto1;
                layoutAimg5.image = rotPhoto1;
                layoutAimg6.image = rotPhoto2;
                layoutAimg7.image = rotPhoto2;
                layoutAimg8.image = rotPhoto1;


            }else if(myShots == 4){

                rotPhoto1 = [self rotateImage:editPhotoData1.size: editPhotoData1];
                rotPhoto2 = [self rotateImage:editPhotoData2.size: editPhotoData2];
                rotPhoto3 = [self rotateImage:editPhotoData3.size: editPhotoData3];
                rotPhoto4 = [self rotateImage:editPhotoData4.size: editPhotoData4];

                layoutAimg1.image = rotPhoto1;
                layoutAimg2.image = rotPhoto2;
                layoutAimg3.image = rotPhoto3;
                layoutAimg4.image = rotPhoto4;
                layoutAimg5.image = rotPhoto1;
                layoutAimg6.image = rotPhoto2;
                layoutAimg7.image = rotPhoto3;
                layoutAimg8.image = rotPhoto4;


            }
            break;

      }


    UIGraphicsBeginImageContext(photoContainerView.bounds.size);
    [purikuraContainerView.layer renderInContext:UIGraphicsGetCurrentContext()];
    photoFlattened = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext(); 


    NSEnumerator *enumerator = [[photoContainerView subviews] objectEnumerator];
    id object;

    while ((object = [enumerator nextObject])) {

        [object removeFromSuperview];

    }


    [photoContainerView release];

    photoContainerView = nil;

    if(rotPhoto1 != nil){
    [rotPhoto1 release];
        rotPhoto1 = nil;
    }
    if(rotPhoto2 != nil){
    [rotPhoto2 release];
    rotPhoto2 = nil;
    }
    if(rotPhoto3 != nil){
    [rotPhoto3 release];
    rotPhoto3 = nil;
    }
    if(rotPhoto4 != nil){
    [rotPhoto4 release];
    rotPhoto4 = nil;
    }

    if(rotPhotoSm1 != nil){
    [rotPhotoSm1 release];
    rotPhotoSm1 = nil;
    }
    if(rotPhotoSm2 != nil){
    [rotPhotoSm2 release];
    rotPhotoSm2 = nil;
    }
    if(rotPhotoSm3 != nil){
    [rotPhotoSm3 release];
    rotPhotoSm3 = nil;
    }
    if(rotPhotoSm4 != nil){
    [rotPhotoSm4 release];
    rotPhotoSm4 = nil;
    }

    return photoFlattened;

}



//-----------------------------STOP THE UIACTIVITYINDICATORVIEW---------------------



-(IBAction)stopSpinner:(id)sender
{

    [self.view setUserInteractionEnabled:YES];
    [myIndicator stopAnimating];
    [myIndicator release];
    myIndicator = nil;

    if(myPattern == 0){
        NSLog(@"SINGLE-SHOT MODE");
        controller.isSingleShot = TRUE;

    }else{

        NSLog(@"MULTI-SHOT MODE");
        controller.isSingleShot = FALSE;

    }

    controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentModalViewController:controller animated:YES];

    [controller release];

    [allStamps removeAllObjects];
    [imageFrames removeAllObjects];


    switch (myShots) {
        case 1:
            [editPhotoData1 release];
            break;

        case 2:
            [editPhotoData1 release];
            [editPhotoData2 release];
            break;

        case 4:
            [editPhotoData1 release];
            [editPhotoData2 release];
            [editPhotoData3 release];
            [editPhotoData4 release];
            break;

    }

        /* This is the edited photo that has been onscreen. Processing is now done so it is okay to release it. The UI should be updated and now have a blank, black background instead of the image.
*/
        editedPhoto.image = nil;
    [editedPhoto release];
    editedPhoto = nil;


}

Ответы [ 2 ]

15 голосов
/ 31 августа 2009

У этого вопроса есть несколько полезных ресурсов по многопоточности Cocoa: "Где найти хороший учебник по многопоточности iPhone / Objective c?"

Я также настоятельно рекомендую прочитать новое Руководство по программированию параллелизма (, однако игнорируйте блоки и очереди отправки, так как Grand Central Dispatch еще не доступен на iPhone OS iOS 4.0 только что добавлена блоков и GCD), потому что это дает веские основания для использования таких структур, как NSOperation и NSOperationQueue, в качестве альтернативы созданным вручную потокам. Для получения информации о созданных вручную потоках см. Руководство по программированию потоков .

Как упоминает RC, самым большим источником сбоев в многопоточных приложениях Cocoa является одновременный доступ к общему ресурсу. Директива @synchronized не самая быстрая, как отмечает Колин Уилер , поэтому вы можете использовать NSLock для защиты доступа к вашим общим ресурсам. Однако блокировка любого рода может быть дорогой, поэтому я перенес свои приложения на использование единого NSOperationQueues для доступа к этим ресурсам. Улучшения производительности были значительными.

Еще одна проблемная область с какао и многопоточностью связана с обновлениями пользовательского интерфейса. Все обновления пользовательского интерфейса в Какао должны выполняться в главном потоке, иначе это может привести к нестабильности. Если у вас есть фоновый поток, выполняющий вычисления, убедитесь, что любой метод обновляет пользовательский интерфейс в вызове метода -performSelectorOnMainThread:withObject:waitUntilDone:.

5 голосов
/ 31 августа 2009

Вероятно, самая распространенная ошибка, которую допускают новички (на любом языке) при работе с потоками, - это предоставление доступа к изменяемым общим ресурсам без охраны / мьютексов. Вы охраняете ресурсы, такие как:

@synchronized(sharedData)
{
   // modify sharedData safely
}

Вы захотите ограничить объем данных, разделяемых между потоками, и, если они должны быть общими, предпочитать неизменяемые объекты, чтобы уменьшить конкуренцию, вызванную синхронизацией.

Управление потоками - это еще одно место, где могут возникнуть проблемы. Вот ссылка на документ, относящийся к использованию потоков в iPhone.

http://developer.apple.com/iphone/library/documentation/cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html.

Без предоставления кода можно догадаться, что не так с вашим приложением, но я бы начал с того, чтобы убедиться, что вы правильно управляете созданием и завершением потока, а также уделять особое внимание любым общим ресурсам, которые пытается выполнить поток для доступа.

...