Quick Fix For Can't add self as subview

| /

I believe you can always find a lot of this kind of Crash: Can't add self as subview.

We also know that we will never write code like add self as subview.

In fact, in most cases, the push and pop pages with animation and no animation occur at the same time.

Because we all know that this is often caused by the complexity of the page hierarchy and the unpredictable page jumps. So often choose to shelve this Crash helplessly.

But we always think about how to eliminate this Crash. Whether it is from the perspective of user experience or from the aspect of Crash rate management.

Crash is often caused by the rapid simultaneous execution of the following similar codes:

1
2
3
4
5
6
[navi popViewControllerAnimated:YES];
[navi popToViewController:vc];

[navi pushViewController:vc2 animated:NO];
[navi pushViewController:vc2 animated:YES];
// ...

The push/pop with animation is not completed instantly, and includes a lot of intermediate state processing, animation processing, etc., so when there is push/pop without animation at the same time, it is easy to cause inconsistency.

Usually in the same block of code, we do not write such code. But when the page jump is driven by the routing system, and the page technology stack is composed of native, H5, RN, etc., things will become quite complicated.

Our team has tried a relatively complete solution. The main thing is to start with the routing system and build a sufficiently sequential and controllable routing system, including encapsulating all page jumps, using CATransaction to try to solve the consistency problem caused by push/pop animation, and so on. I will try to introduce it later when I have time.

In fact, it is very effective. Although our goal is a controllable routing system, because it is sufficiently serialized, routing concurrency is reduced, which greatly reduces the occurrence of this Crash.

But it needs to be admitted that there are still a small number of such crashes. The main possible reasons are:

  1. Some newly introduced technology stacks use a lot of present as a page jump scheme. And this one is harder to control.
  2. The newly introduced company-level frameworks and libraries are not under our routing system, thereby bypassing the control.

In the end, in order to reduce more and more such Crash, we have to take violent protection measures, the code is as follows

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
27
28
29
30
31
32
// UIView+CKQSafeUtil.m

@implementation UIView (CKQSafeUtil)

// Here is our team's own method swizzle approach.
ckq_swizzleInstanceMethod([UIView class], @selector(addSubview:), @selector(ckqSafe_addSubview:));

// fix for Can't add self as subview
- (void)ckqSafe_addSubview:(UIView *)view {
@try {
[self ckqSafe_addSubview:view];
} @catch (NSException *exception) {
[self ckqSafe_handleException:exception];
} @finally {
}
}

- (void)ckqSafe_handleException:(NSException *)exception {
NSString *reason = exception.reason;
if ([reason isEqualToString:@"Can't add self as subview"]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CKQLogF(@"add subview exception: Can't add self as subview");
[[CKQRouterUtil shared] openMain];
});
} else {
// Other exceptions only report errors first
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CKQLogF(@"add subview exception: %@", reason);
});
}
}
@end

As a defense, it is concise and violent enough. At least this solution does protect against many extreme asynchronous routing scenarios.