项目常见崩溃4(陆续更新)

前情提要, 上回书说UIAlertView会在iOS9以下的系统上产生内存泄露, 解决方案是要么退出VC的时候直接关闭alertView要么全局控制alertView. 上回在讲到关闭alertView的时候谈到了崩溃, 今天重点说下UIAlertView退出时候的崩溃.

崩溃重现

显示alertView
    UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@""
                                                        message:@"您的号在另一终端登录"
                                                       delegate:self
                                              cancelButtonTitle:@"确定"
                                              otherButtonTitles:@"取消", nil];
    [alertView show];
    self.alertView = alertView;
    [self.navigationController popViewControllerAnimated:NO];

这里显示之后用一个强指针指着, 防止直接释放, 然后立即pop掉当前controller, 并在dealloc中执行如下方法

- (void)dealloc {
    [self.alertView dismissWithClickedButtonIndex:0 animated:YES];
}

好么, 这不就是上回书说的方案1吗?怎么还会崩溃!
先看下崩溃日志:

2017-01-18 09:28:59.667 28-UIAlertViewCrash[1023:16310] Trying to dismiss the presentation controller while transitioning already. (<_UIAlertControllerAlertPresentationController: 0x7874eab0>)
2017-01-18 09:28:59.668 28-UIAlertViewCrash[1023:16310] transitionViewForCurrentTransition is not set, presentation controller was dismissed during the presentation? (<_UIAlertControllerAlertPresentationController: 0x7874eab0>)

WTF, 你试图dismiss一个已经在过渡的VC了, 这个VC是_UIAlertControllerAlertPresentationController, 貌似有点眼熟, _UIAlertControllerShimPresenterWindow上回提到UIAlertView被添加的window就是这个window, 那看这架势_UIAlertControllerAlertPresentationController必然是这个window内的一个controller了, 为了验证我们的说法, 打印下面的日志

    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    NSLog(@"keywindow:%p:%@", keyWindow, NSStringFromClass([keyWindow class]));
    
    UIViewController *viewController = keyWindow.rootViewController;
    NSLog(@"keywindow's root vc:%p:%@", viewController, NSStringFromClass([viewController class]));

结果是:

2017-01-18 09:38:29.478 28-UIAlertViewCrash[1086:21182] keywindow:0x7b94a900:_UIAlertControllerShimPresenterWindow
2017-01-18 09:38:29.478 28-UIAlertViewCrash[1086:21182] keywindow's root vc:0x7b839ba0:_UIAlertShimPresentingViewController

没错了, _UIAlertControllerAlertPresentationController就是_UIAlertControllerShimPresenterWindow的rootViewController. 继续说崩溃, 崩溃发生在iOS8.X系统上, 来看看iOS9以上运行的结果

结果是不会崩溃, 而且是在VC被干掉的时候也关闭了UIAlertView, 这不正好是我们想要的结果吗, 实际上iOS9以上已经做到了, 而且我们的代码一点都没变啊! 良心企业有木有, 但是我们还是要兼容iOS8, 因为iOS现在出到10, 一般的公司都会兼容3个版本, 所以iOS8上的崩溃那也是崩溃, 还是要处理的.

矛盾

对于iOS8.X系统如果不在dealloc的时候dismiss会有内存泄露, 如果dismiss就可能会崩溃, 这可如何是好啊! 从崩溃日志上看, alertView在显示的时候是创建了一个VC的, 然后这个VC是通过pressent的方式添加到当前keywindow上的, 那么我们尝试hook presentViewController

代码如下

NSString* const finishPresenting = @"finishPresenting";

@implementation UIViewController (hook)

+ (void)load {
    Class class = [self class];
    SEL oriSel = @selector(presentViewController:animated:completion:);
    SEL newSel = @selector(myPresentViewController:animated:completion:);
    Method fromMethod = class_getInstanceMethod(class, oriSel);
    Method toMethod = class_getInstanceMethod(class, newSel);
    if (!class_addMethod(class, oriSel, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
        method_exchangeImplementations(fromMethod, toMethod);
    }
}
- (void)myPresentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^ __nullable)(void))completion {
    self.presenting = YES;
    NSLog(@"myPresentViewController called:%p: %@", viewControllerToPresent, NSStringFromClass([viewControllerToPresent class]));
    __weak typeof(self) weak_self = self;
    [self myPresentViewController:viewControllerToPresent animated:flag completion:^{
        weak_self.presenting = NO;
        if (completion) {
            completion();
        }
        [[NSNotificationCenter defaultCenter] postNotificationName:finishPresenting object:nil];
    }];
}

- (void)setPresenting:(BOOL)presenting {
    objc_setAssociatedObject(self, @selector(isPresenting), @(presenting), OBJC_ASSOCIATION_ASSIGN);
}

- (BOOL)isPresenting {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}
@end

这样在present开始的时候去设置present的状态, 结束后重置状态并抛通知, 这个通知在你的自定义导航控制器中监听

- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    NSLog(@"popViewControllerAnimated:animated:%d", animated);
    if ([UIApplication sharedApplication].keyWindow.rootViewController.isPresenting) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onFinishPresenting:) name:finishPresenting object:nil];
        });
        return nil;
    }
    return [super popViewControllerAnimated:animated];
}

- (void)onFinishPresenting:(NSNotification *)notification {
    [self popViewControllerAnimated:NO];
}

这样当你进行pop操作的时候先看下是否有正在presenting, 如果有就监听这个结束通知, 等结束后再继续执行, 这里只是简单的代码, 实际pop的时候还要进行其它的判断, 用队列比较合适, 这里为了简单说明问题, 只是简单处理下, 方便读者阅读.

好了, 到这里应该就没崩溃了, 我们再重新验证下下面几个问题
1 还有内存泄露吗?
2 还崩溃吗?
3 在其它系统上有没有问题?

答案是:
1 还有内存泄露(这里真的不知道为什么会这样, 只能如实禀告大家)
2 没有崩溃了
3 在其它机器上是没有问题的, 没内存泄露也没有崩溃, 异常日志都没有.

这里还是有一个关于逻辑的问题, 如果有弹窗, 那就不允许pop还是等弹窗出来再pop, 实际上, 大部分情况是, 弹窗都是会指定页面的, 只有被强制退出这种弹窗才会伴随pop, 而这种弹窗的数量应该是很少的, 应该是做成全局的. 对于页面内的弹窗, 个人感觉如果此时恰好要pop是可以选择不进行pop的, 你可以继续操作弹窗.

总结

对一个废弃的东西UIAlertView说了这么多, 总结一下这里涉及到很多我们平时不太关注的东西, 作为一个开发, 应该是要了解更多的, 不能只是会用.
1 AppDelegate的window和keyWindow不一样, keyWindow是当前正在显示的window, 而AppDelegate里的window是我们应用真切能够用到的window
2 UIAlertView实际通过present的方式添加到keyWindow上, 在present的时候进行去执行dismiss是会崩溃的, 这点即使使用UIAlertController也是需要注意的.
3 在有present进行的时候尽量不要进行push或者pop造作, 因为这样很容易导致一些UI的释放操作, 比如dismiss, 进而可能会崩溃.
4 许多诡异的崩溃可能往往就是我们不懂其中的原理导致的, 如果大家都知道UIAlertView是通过present方式添加并且present不允许dismiss我想就不会有这种崩溃了, 但是, 显然大部分开发者都是不清楚的.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 上回书说animated隐式动画的血与泪, 今天要说的可能和崩溃无关, 但是又是很多诡异的崩溃的根源: 内存泄露....
    bigParis阅读 863评论 0 2
  • UIWindow是Cocoa框架的重要组件之一,所有的UIView都要通过UIWindow来进行展现,没有UIWi...
    文兴阅读 4,920评论 3 34
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 从去年下半年帮哥们顶了几个月也算干了小半年了,在口碑时代公关先行的趋势也越来越明显了。而公关有众多分支但最有挑战的...
    贱鸡行事阅读 410评论 0 0
  • “狗子,你是去吃饭吗?给我带个晚饭吧!” “大黄,我手机停机了,帮我交下话费,我给你现金。” …… 我得承认这是我...
    波浪卷儿阅读 1,024评论 2 17