weakSelf in dealloc

| /

We all know that it is generally recommended to do simple cleanup in the dealloc method.

A very important reason is that in the process of destroying an object, if an operation such as weak is performed, it will cause a crash.

A common crash stack information may be similar to the following

1
2
3
4
5
6
7
8
9
10
11
#0	0x00007ff833e02dba in __abort_with_payload ()
#1 0x00007ff833e04877 in abort_with_payload_wrapper_internal ()
#2 0x00007ff833e04827 in abort_with_reason ()
#3 0x000000010d8508ae in _objc_fatalv(unsigned long long, unsigned long long, char const*, __va_list_tag*) ()
#4 0x000000010d850835 in _objc_fatal(char const*, ...) ()
#5 0x000000010d84d6e1 in weak_register_no_lock ()
#6 0x000000010d84f66c in objc_initWeak ()
...
#8 0x000000010d36a851 in -[A dealloc] at /Users/wangqing/Documents/workplace/studySwift/testObjCForBlog/testObjCForBlog/ViewController.m:31
#9 0x000000010d84e6a1 in objc_object::sidetable_release(bool, bool) ()
...

Generally, when you see words such as weak_register_no_lock, you can immediately react, and a weak reference is created during the process of object dealloc.

If encountered during local debugging, the Xcode console will also display information similar to the following

objc[4271]: Cannot form weak reference to instance (0x60000337c280) of class B. It is possible that this object was over-released, or is in the process of deallocation.

A simple code example that causes a crash is as follows

1
2
3
4
5
6
7
- (void)dealloc {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf func];
});
}

If it were that simple, it would have been obvious at a glance. But the actual situation may be more complicated than we expected, such as

1
2
3
4
5
6
7
8
9
10
11
- (void)func {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf funcA];
});
}

- (void)dealloc {
[self func];
}

Scenarios of this sequential call are still easy to spot.

But in actual projects, if the code is complicated, it may only be triggered when some branches are executed.

And if these logic branches are some extreme scenarios or abnormal scenarios, they may not be found during testing and appear in the production environment.

such as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)doReport {
// do report
}

- (void)reportSth:(NSException *)exception {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
__strong typeof(self) strongSelf = weakSelf;
// build report info
[strongSelf doReport];
});
}

- (void)doCleanUp {
@try {
// do thing clean
// @throw [[NSException alloc] initWithName:@"test" reason:@"reason" userInfo:nil];
} @catch (NSException *exception) {
[self reportSth:exception];
} @finally {
}
}

- (void)dealloc {
[self doCleanUp];
}

To sum up, in the dealloc method, try to keep the logic as simple as possible to avoid complicated logic and difficult to troubleshoot.

On the other hand, it is also very important to do a good job of unit testing and to cover various exception branches.