Summary of classic crashes of multithreading in IOS

Time:2020-11-21

preface

IOS crash is a headache for IOS developers. If an app crashes, it indicates that there is something wrong with the code. How to quickly locate the crash site is very important. It’s easier to find the problem in the debugging phase, but it’s more troublesome to analyze the crash report of the app that has been launched.

This article will give you a summary of some classic crashes of multithreading in IOS. Let’s not say much about it. Let’s take a look at the detailed introduction.

Crash of 0x0 block callback

In the MRC environment, use block to set the downloaded image successfully. When self is released, weakself becomes a wild pointer, and then tragedy happens


 __block ViewController *weakSelf = self;
 [self.imageView imageWithUrl:@"" completedBlock:^(UIImage *image, NSError *error) { 
 NSLog(@"%@",weakSelf.imageView.description);
 }];

The crash of setter in multithreading

There are too many getters and setters. In the case of single thread, there is no problem. But in the case of multithreading, it may crash. Because[_ ImageView release]; this code may be executed twice, Oops!

UIKit is not a thread, so it is possible to call UIKit in a place that is not the main thread. It may not be a problem at the development stage and is directly free from testing. All of your online logs may crash. Holy shit!

The solution is to check whether the thread currently called is the main thread through hook setNeedsLayout, setNeedsDisplay and setNeedsDisplayInRect.


- (void)setImageView:(UIImageView *)imageView
{
 if (![_imageView isEqual:imageView])
 {
 [_imageView release];
 _imageView = [imageView retain];
 }
}

More setter type crashes

Property property, the most written property is nonatomic, and generally there is no problem!


@interface ViewController ()
@property (strong,nonatomic) NSMutableArray *array;
@end

Run through the following code and you’ll see:
malloc: error for object 0x7913d6d0: pointer being freed was not allocated


for (int i = 0; i < 100; i++) {
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  self.array = [[NSMutableArray alloc] init];
 });
 } 

The reason is: the object is relaese repeatedly. Take a look at the runtime source code

terms of settlement:Property is declared atomic

A more common example is:


if(handler == nil)
{
 hander = [[Handler alloc] init];
}
return handler;

If two threads a and B access the if statement at the same time, thenhandler == nilIf the condition is satisfied, both threads go to the next initialization instance

At this point, thread a completes initialization and assignment (in this instance, we call it a), and then goes back to other logic. At this time, thread B starts to initialize and assign values (we call it B in this instance), and handler will point to the object initialized by thread B. the instance a initialized by a is released because the reference count decreases by 1 (to 0), The code also tries to access the address where a is located. The contents of this address become unpredictable due to the release, resulting in a wild pointer

Another key point is that during the call of a method of an object, the reference count of the object will not increase, so that if it is released, the access to this object in the subsequent execution process may lead to a wild pointer [1]


Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x12345678
Triggered by Thread: 1

Simply add a lock to solve the problem


 @synchronized(self){
 if(handler == nil)
 {
  hander = [[Handler alloc] init];
 }
 }
return handler;

Access to variables under multi thread


if (self.xxx) {
 [self.dict setObject:@"ah" forKey:self.xxx];
}

When you first see this code, do you think it is correct? Because the key has been set in advance self.xxx For the judgment of non nil, the subsequent instruction will be executed only when the non nil is obtained. However, the above code is correct only under the premise of single thread.

Let’s assume that the thread that the above code is currently executing is thread a, and when we are finished executing itif (self.xxx)After the statement, CPU switches the execution power to Thread B, and at this time Thread B invokes a sentence.self.xxx = nil。 Using local variables can solve this problem


__strong id val = self.xxx;
if (val) {
 [self.dict setObject:@"ah" forKey:val];
}

In this way, no matter how many threads try to self.xxx In addition, the Val in essence will keep the existing state, which is consistent with the judgment of non nil.

0x4 dispatch_ The collapse of group

dispatch_ group_ Enter and leave must match, otherwise it will crash. When downloading multiple resources, it is often necessary to use multi thread concurrent download, and the user will be informed after all downloads are completed. Start downloading, dispatch_ group_ Enter, download and complete the dispatch_ group_ leave 。 The process is very simple, but when the code is complex to a certain extent or some third-party libraries are used, problems are likely to occur.

dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
 NSLog(@"Finish downloading :%@", downloadUrls);
});
//T is an array of strings 
[downloadUrls enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
 dispatch_group_enter(serviceGroup);
 SDWebImageCompletionWithFinishedBlock completion =
 ^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  dispatch_group_leave(serviceGroup);
  NSLog(@"idx:%zd",idx);
 };
 [[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString: downloadUrls[idx]]      options:SDWebImageLowPriority      progress:nil             completed:completion];
}];

Use multithreading for concurrent download until all the images are downloaded (can fail) to call back. The image download uses sdwebimage. The crash scenario is: there are 10 images, which are downloaded twice (A & B). Among them, there is a picture in group B which is repeated with the picture downloaded from group A. Suppose group a downloads corresponding to GroupA and group B, groupb

The source code of sdwebimage is intercepted as follows:

dispatch_barrier_sync(self.barrierQueue, ^{
 SDWebImageDownloaderOperation *operation = self.URLOperations[url];
 if (!operation) {
  operation = createCallback();
  //Pay attention to this line****
  self.URLOperations[url] = operation;
  __weak SDWebImageDownloaderOperation *woperation = operation;
  operation.completionBlock = ^{
   SDWebImageDownloaderOperation *soperation = woperation;
   if (!soperation) return;
   if (self.URLOperations[url] == soperation) {
    [self.URLOperations removeObjectForKey:url];
   };
  };
 }
//Pay attention to this line****
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}

Sdwebimage’s downloader will do the download task according to the URL, corresponding to the nsoperation mapping, and the same URL will map to the same unexecuted nsoperation. When group A’s images are downloaded, the same URL callback is groupb instead of group A. At this point, the count of group B is 1. When all pictures of group B are downloaded, the end count is 5 + 1. Because the number of enter is 5 and the number of leave is 6, it will crash!

0x5 crash after last holder released

The object A is held by manager and is invoked in A.[Manager removeObjectA] 。 Object aretainCount -1When retaincount equals zero, object a has started to be released. After calling removeobjecta, the[self doSomething]It will collapse.


- (void)finishEditing
{
 [Manager removeObject:self];
 [self doSomething];
}

This usually happens when an array or dictionary contains an object and is the last holder of the object. When the object is not handled well, there will be a crash above. Another case is that when the objects in the array or dictionary have been released, when traversing the array or fetching the values in the dictionary, it will crash. In this case, people will collapse, because sometimes the stack is like this:


Thread 0 Crashed:
0 libobjc.A.dylib     0x00000001816ec160 _objc_release :16 (in libobjc.A.dylib)
1 libobjc.A.dylib     0x00000001816edae8 __ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv :508 (in libobjc.A.dylib)
2 CoreFoundation     0x0000000181f4c9fc __CFAutoreleasePoolPop :28 (in CoreFoundation)
3 CoreFoundation     0x0000000182022bc0 ___CFRunLoopRun :1636 (in CoreFoundation)
4 CoreFoundation     0x0000000181f4cc50 _CFRunLoopRunSpecific :384 (in CoreFoundation)
5 GraphicsServices    0x0000000183834088 _GSEventRunModal :180 (in GraphicsServices)
6 UIKit       0x0000000187236088 _UIApplicationMain :204 (in UIKit)
7 Tmall4iPhone     0x00000001000b7ae4 main main.m:50 (in Tmall4iPhone)
8 libdyld.dylib     0x0000000181aea8b8 _start :4 (in libdyld.dylib)

The possible scenarios for this stack are:

When releasing a dictionary, a value becomes a wild pointer because it is released in advance by other codes, and then it is released again to trigger crash. If you can type all the keys / values when each dictionary is released, and if a key / value happens after a key / value is typed, the crash occurs, then it is hung on the newly typed key / value

The release thread of the object should be consistent with that of the thread it handles

Object a listens for notification events in the main thread, if the object is released by other threads. At this point, if object a is performing notification related operations, and then accessing the object related resources, it will be a wild pointer and crash

0x7 performSelector:withObject:afterDelay:

If this method is called, if it is not in the main thread, you must ensure that the ruuloop of the current thread exists_ xxx_ Afterdelay relies on runlopp to execute. Additional useperformSelector:withObject:afterDelay:andcancelPreviousPerformRequestsWithTarget Be careful when you combine.

  • After delay will increase the reference count of the receiver, and cancel will decrease by one
  • If the reference count of the receiver is only 1 (delay only), the receiver will be destroyed immediately after the call to cancel, and the method of the receiver will be crashed after calling the receiver again
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//Nslog (@ "self destroyed");
 return;
}
[self doOther];

summary

The above is the whole content of this article, I hope that the content of this article has a certain reference value for your study or work. If you have any questions, you can leave a message and exchange, thank you for your support to developeppaer.