初学者在 iPhone 上常犯的多线程错误
我刚刚在我的应用程序中引入了多线程,只是为了让一个愚蠢的 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;
}
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这个问题有一些关于可可多线程的好资源:“在哪里可以找到有关 iPhone/Objective c 多线程的优秀教程?”
我还强烈建议您阅读新的 并发编程指南(
但是,请忽略块和调度队列,因为 Grand Central Dispatch 在 iPhone OS 上尚不可用iOS 4.0 刚刚添加了块和 GCD),因为它为使用 NSOperation 和 NSOperationQueue 等结构作为手动创建线程的替代方案提供了强有力的理由。有关手动创建线程的信息,请参阅 线程编程指南。正如 RC 提到的,多线程 Cocoa 应用程序崩溃的最大根源是同时访问共享资源。
@synchronized
指令不是最快的,如 Colin Wheeler 指出,因此您可能需要使用NSLock
来保护对共享资源的访问。然而,任何类型的锁定都可能代价高昂,这就是为什么我一直将我的应用程序迁移到使用单宽 NSOperationQueues 来访问这些资源。性能改进非常显着。Cocoa 和多线程的另一个问题来自用户界面更新。 Cocoa 中的所有 UI 更新都必须在主线程上完成,否则可能会导致不稳定。如果您有后台线程执行计算,请确保将更新 UI 的任何方法包装在
-performSelectorOnMainThread:withObject:waitUntilDone:
方法调用中。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 OSiOS 4.0 just added blocks and GCD), because it makes a strong case for using structures likeNSOperation
andNSOperationQueue
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 useNSLock
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-wideNSOperationQueues
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.初学者(在任何语言中)使用线程时最常见的错误可能是允许在没有防护/互斥锁的情况下访问可变共享资源。您可以保护以下资源:
您需要限制线程之间共享的数据量,如果必须共享数据,则更喜欢不可变对象,以减少同步引起的争用。
管理线程是另一个可能出现问题的地方。这是专门针对 iPhone 中线程的使用的文档参考。
http://developer.apple.com/iphone/library/documentation/cocoa/概念/多线程/CreatingThreads/CreatingThreads.html。
如果不提供代码,任何人都可以猜测您的应用程序出了什么问题,但我首先要确保您正确管理线程的创建和终止,并特别注意线程尝试访问的任何共享资源。
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:
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.