views:

3857

answers:

2

I just introduced multithreading into my app JUST to get a silly UIActivityIndicatorView to work. Well, the activity indicator works, alright -- but now my app crashes sometimes, and sometimes it doesn't -- under otherwise controlled conditions... I need to figure this out but don't know where to start looking...

So, what are some common mistakes beginners often make with multithreading on the iPhone? Please be specific in your answers. Thanks for your time.

UPDATE: I added my problematic source for reference.

//--------------------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;


}
+3  A: 

Probably the most common mistake beginners make (in any language) when working with threads is allowing access to mutable shared resources without guards/mutexes. You guard resources like:

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

You'll want to limit the amount of data shared between threads and if it must be shared, prefer immutable objects in order to reduce contention caused by synchronization.

Managing threads is another place where trouble can arise. Here is a document reference specific to the use of threads in the iPhone.

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

Without providing code, it's anyone's guess as to what is wrong with your app, but I would start with making sure you are correctly managing the creation and termination of the thread as well as putting particular attention to any shared resources that thread attempts to access.

RC
Cool. RC mentions another good thing: Distinction between Creation AND Termination. I use methods such as -performSelectorInBackground:withObject to Create a thread, but I don't know what I am doing to terminate it as I had assumed that at the end of the work the thread would just end itself. I'll have to read the documentation more. Thanks RC.
RexOnRoids
The thread should terminate when the method reaches its end. You don't need to manually destroy it.
Brad Larson
+8  A: 

This question has some good resources on Cocoa multithreading: "Where can I find a good tutorial on iPhone/Objective c multithreading?"

I also highly recommend reading the new Concurrency Programming Guide (however, ignore the blocks and dispatch queues, as Grand Central Dispatch is not yet available on iPhone OS iOS 4.0 just added blocks and GCD), because it makes a strong case for using structures like NSOperation and NSOperationQueue as an alternative to manually created threads. For information on manually created threads, refer to the Threading Programming Guide.

As RC mentions, the single biggest source of crashes with multithreaded Cocoa applications is simultaneous access to a shared resource. The @synchronized directive is not the fastest, as pointed out by Colin Wheeler, so you might want to use NSLock to protect access to your shared resources. However, locking of any sort can be expensive, which is why I've been migrating my applications over to using single-wide NSOperationQueues for access to these resources. The performance improvements have been significant.

Another problem area with Cocoa and multithreading comes from user interface updates. All UI updates in Cocoa must be done on the main thread, or instability can result. If you have a background thread performing a calculation, make sure to wrap whatever method updates the UI in a -performSelectorOnMainThread:withObject:waitUntilDone: method call.

Brad Larson
Very good...I'm especially interested in the part where you mention that UI updates can cause problems with multithreading. Because in my app I send a significant chunk of work (including some relating to UI) to a background thread so that I can show a UIActivityIndicator view. Sometimes it crashes, sometimes it doesn't -- under CONSTANT conditions, mind you. This leads one to wonder if the instability lies in the way the app relates to components of the iPhone OS itself at the time of the crash due to the impact of the extra thread. I'll have to look into this more. Thanks.
RexOnRoids
Threading issues often cause nondeterministic crashes. That's what makes them so fun. I did notice in your above code that you do some rendering of a layer into a context within the -buildPhoto which is running into the background. I'm not sure that's a threadsafe operation.
Brad Larson
Thanks! I'll look into that.
RexOnRoids