# 复盘一次令人头大的上线

> 原文：https://pjhubs.com/2018/11/02/%E5%A4%8D%E7%9B%98%E4%B8%80%E6%AC%A1%E4%BB%A4%E4%BA%BA%E5%A4%B4%E5%A4%A7%E7%9A%84%E4%B8%8A%E7%BA%BF/
>
> 作者：PJHubs · 发布于 2018-11-02 · 标签：滴滴、实习

> 昨晚我负责的产品 1.6.0 正式版上线，距离正式版上线前已经上了两个灰度测试版，每次版本上线前都会遇到一些或大或小的阻断性 bug ，用我们从淘宝新来 android 老大哥的话来说：“看来 app 不论大小，上线前都是一样的难受”。

## 遇到的问题
### 第一个问题
昨天中午遇到的第一个情况是这样的，报表列表的置顶数据不正常，每当一个报表被置顶且成功后，再取消置顶，则会顺带把报表的收藏状态给取消了，而且“收藏”、”有权限“、”申请中“三个接口拿到的报表数据实体置顶字段居然不一致 = =。真是头大，做了确认后给相关 RD 发了接口错误信息，给的答复是跟着下周的例行迭代一起修复，也就是说，这个版本我们要带上错误的接口上线了，问题解决，接着上课。

### 第二个问题
第二个问题，刷新“有权限” tab 时因为线上数据量大，接口有点慢，这就导致部分用户（PM）等不了（一般 3s 后如果数据未展示用户量会流失 50% ），此时切回了“收藏” tab，最后再切回到“申请中中” tab 时，列表数据居然是“收藏”的 = =，改了改，解决。

### 第三个问题
第三个问题，在“报表搜索” vc 中的“有权限” tab 收藏报表时，侧滑 cell 后点击收藏，没有提示也不知道是收藏没收藏上，此时去“报表超市”查看 “收藏” tab 时，发现实际上并没有收藏，这个问题之前有遇到过，实际上是报错了的，因为收藏了重复的报表，但因为在“报表搜索”中 cell 没有收藏 icon ，错以为没有收藏导致重复收藏失败。最后发现其实判断报表是否收藏的字段用错了，改了一波，解决。

### 第四个问题
第四个问题一直搞到今天中午，Jenkins 上打出来的 release 包推到下载平台上，1.5.0 升级上来后居然是 debug 环境 = =，刚开始觉得不应该，但是又不想解决了，到了昨晚九点时已经不想做了，就直接把 `#ifdef DEBUG`  下替换的 hostName 改为了线上地址，重新打包，发现问题还在。leader 怀疑是 1.5.0 上的 cache 问题，而我怀疑是 `DEBUG` 宏的问题，我们一直围绕在 1.6.0 本身的问题上，因为当时的情况是，拿到升级包的下载地址后，我用 safari 打开后下载是正常的，但通过 1.5.0 升级下载的包居然还是 debug 环境，一直懵逼到夜里 12 点，因为 `DEBUG` 这个宏不可能有错啊，差点陷入了深深的怀疑当中。

### 第五个问题
一直到今天中午，无论在在 1.6.0 代码上做的任何操作，都无效，其它同事因为不是做 iOS 的，给的意见虽然也都还行，但是实际上我还是觉得 `DEBUG` 这个默认提供的宏没啥问题，但突然猛的发现，我应该去调 1.5.0 的代码啊，因为问题是出在 1.5.0 升级上来，而不是 1.6.0 本身，突然发现了有这么一段代码，通过 `NSUserDefault` set 了取的 hostName 宏，`NSUserDefault` 是用户偏好设置啊 = =，升级不会被清空，能够被备份，而我在 1.6.0 中同样的方法中做了类似的 set hostName 的替换 debug 与 release 环境域名的操作，但是因为原 `NSUserDefault` 的 `firstInitLocal` key 对应的 value 已经备份了，且已被设置为了 `YES`，并没有因为 app 的升级而被清空，根本没能进入到重新 set hostName的代码， = =，最终的做法是在 1.6.0 上注释掉这个 set hostName 的方法，重新在预编译文件中设置唯一 hostName。

### 第六个问题
本以为能够高高兴兴的发了版，但却发现从 mdm（一个统一登录权限管理 app）唤起 app 时居然闪退了，重现的步骤是得先把 app 退出，然后通过 mdm 唤起 app，但是如果 app 还在后台，直接唤起没有问题，此时已经是中午 12 点多，脑子有点懵逼了（饿了），但是问题没解决这么能去吃饭呢？但是这个问题没法调试，因为 app 已经被杀掉了，看不到调试信息。想在我的手机上操作复现一下，发现 mdm 无法安装，用 leader 的手机进行调试发现该设备未在对应证书下注册，因为之前我们的企业证书下的注册设备量满了，曾经给了一段时间让大家把活跃设备重新注册，但当时 leader 的设备并没有进行注册，此时突然想到 PM 的手机注册过了，拿来一调，确实是能够重现这个问题，但却发现连上 Xcode 后居然没拿到对应的 crash log。

接着又发现就算设备不能使用 Xcode 进行调试，也能够拿到 log，还是通过 Xcode 获取，也可以通过设置-隐私-分析数据里看到所有 app 的 crash log，又把 leader 的设备拿过来连上了 Xcode 格式化了 log，变成了如下：
```
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   DiDiData                      	0x0000000100ff9974 0x100f54000 + 678260
1   DiDiData                      	0x0000000100ff84d8 0x100f54000 + 672984
2   DiDiData                      	0x0000000100ff3fc8 0x100f54000 + 655304
3   DiDiData                      	0x0000000100ff57cc 0x100f54000 + 661452
4   UIKitCore                     	0x00000001e9bd1380 -[UIViewController loadViewIfRequired] + 1000
5   UIKitCore                     	0x00000001e9bd17b0 -[UIViewController view] + 28
6   UIKitCore                     	0x00000001e9b2a114 -[UINavigationController _startCustomTransition:] + 1136
7   UIKitCore                     	0x00000001e9b3ebd4 -[UINavigationController _startDeferredTransitionIfNeeded:] + 716
8   UIKitCore                     	0x00000001e9b400a8 -[UINavigationController __viewWillLayoutSubviews] + 164
9   UIKitCore                     	0x00000001e9b22298 -[UILayoutContainerView layoutSubviews] + 224
10  UIKitCore                     	0x00000001ea637f44 -[UIView+ 14184260 (CALayerDelegate) layoutSublayersOfLayer:] + 1380
11  QuartzCore                    	0x00000001c1971a34 -[CALayer layoutSublayers] + 184
12  QuartzCore                    	0x00000001c19769c4 CA::Layer::layout_if_needed+ 1317316 (CA::Transaction*) + 324
13  QuartzCore                    	0x00000001c18d59d4 CA::Context::commit_transaction+ 657876 (CA::Transaction*) + 340
14  QuartzCore                    	0x00000001c19042f4 CA::Transaction::commit+ 848628 () + 608
15  QuartzCore                    	0x00000001c190515c CA::Transaction::observer_callback+ 852316 (__CFRunLoopObserver*, unsigned long, void*) + 92
16  CoreFoundation                	0x00000001bd31db94 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
17  CoreFoundation                	0x00000001bd318828 __CFRunLoopDoObservers + 412
18  CoreFoundation                	0x00000001bd318dc8 __CFRunLoopRun + 1264
19  CoreFoundation                	0x00000001bd3185b8 CFRunLoopRunSpecific + 436
20  GraphicsServices              	0x00000001bf58c584 GSEventRunModal + 100
21  UIKitCore                     	0x00000001ea194bc8 UIApplicationMain + 212
22  DiDiData                      	0x0000000100fc8778 0x100f54000 + 477048
23  libdyld.dylib                 	0x00000001bcdd8b94 start + 4
```

只需要看主线程报的 log 就好了，根据堆栈提示可以猜出是 `UINavigationController` 下的 `UIViewController` 初始化错误（如果你的 log 看不到格式化好的信息自行搜索一番如何格式化），而用到了的 `UINavigationController` 的地方是在 `AppDelegate` 中的 `[self initTabBarController]` 方法，刚开始我还真以为就是这个问题，然后把 `tabBar` 四大入口的 vc 都一行行代码注释掉查看，最后折腾了一圈发现其实跟这个问题没有关系。

又陷入了苦闷之中，在 mdm 对接群中请教了相关 RD，我的问题是：“iOS app 在前台的时候，mdm 唤起正常，app 被杀掉后，通过 mdm 唤起闪退”，他给我的回答是“应该是处理openURL回调和didFinishLaunchingWithOptions回调事务处理逻辑问题”，后来仔细一想确实是这两个方法中才能引发这个问题，首先确保了 mdm 唤起 app 时走的 `openURL` 回调方法逻辑正常，已经做了 scheme 判断，那问题就出在了 `didFinishLaunchingWithOptions` 方法中。

又采用了最原始的方法，把这部分的代码全部注释掉，一段一段代码块打开注释并执行，最后发现原来又是上一个实习同学挖的坑，每次 app 重新加载后都注册一遍友盟的消息推送的方法（这个方法是用来初始化消息推送 push 的），1.5.0 写的时候估计这个同学没有考虑到我们的 app 会存在被其它应用唤起的情况，只考虑到了点击消息通知栏内容唤起的情况，直接从键值对中取值，但是从 mdm 唤起 app 带上的数据并不符合他写的解析代码，也没有判空，最后对空值操作，引发了 crash

#### 怎么解决的？
![WechatIMG4.jpeg](https://i.loli.net/2018/11/02/5bdc7032d95e4.jpeg)


### 第七个问题
最后一个问题，SSO 团队收回了一个字段，导致 app 的一个三方 H5 应用入口 ticket 验证失败，疯狂递归唤起 mdm 进行用户登录授权，允许授权后，更新的 ticket 又因为 SSO 的问题，导致 ticket 验证失败，又唤起 mdm 进行用户登录授权......，最后这个 H5 应用的团队把负责 SSO 团队的同学骂了一顿，重新上线

## 想骂人
OK，解决完了以上问题，除了睡觉的八个小时还有一些零零碎碎的时间，从昨天早上十点到今天下午四点，今天一来还被“关进”一间会议室五个人陪着做封闭式开发，嗯，这个版本迭代周期从七月份到现在，不但长还臭。
