iOS 诡异横屏 BUG 解决思路全记录 以及一些对软件开发的思考

“如果代码执行结果和预期不一致, 那么不用质疑肯定是你那里写错了”, 前辈当前如是说到
软件行业经过这么多年的变革, 感觉软件质量反而不如当年了(这里我说的软件质量不过说的是应用软件, 还包含操作系统层面), 不过这只是一个前言而已, 抛砖引玉而已, 暂时放下不表, 我留到最后再来谈谈我的看法

整体思路

这里我会分成三个步骤进行处理

  • 列出问题症状
  • 分析问题可能原因
  • 最终找到并解决问题

症状

前置常量

  • PA: 页面 A, 竖屏
  • PB: 页面 B, 横屏
  • PA 是主项目中的页面
  • PB 是 Framework 中的页面
  • PA 以 Link Binary with Libraries 的方式引用 PB

复现 1

变量

  • iOS 10

流程

  • PA 进入 PB, 此时 PB 页面显示正常
  • PB 退出回到 PA, 此时 PA 亦是正常
  • PA 再次进入 PB, 此时 PB 显示异常, 此时为竖屏
  • PB 再次退回到 PA, 此时 PA 还是正常的, 也就是说, 仅是 PB 存在问题
  • 退到桌面再回到 App, 问题恢复到 step 1(这里也是非常诡异, 不过之后我会解释)

PS: 就上面的症状来看, 你是不是认为问题肯定处在 PB 上了, 真是 Naive 啊, 我也差点掉到坑里去了

复现 2

变量

  • iOS 13

流程

  • PA 进入 PB, 此时 PB 页面显示正常
  • PB 退出回到 PA, 此时 PA 亦是正常
  • PA 再次进入 PB, 此时 PB 显示异常, 次数页面显示为横屏, 但是里面的布局却是安装竖屏的方式布局的( PB 使用的绝对布局)
  • PB 再次退回到 PA, 此时 PA 还是正常的, 也就是说, 仅是 PB 存在问题
  • 退入桌面再回到 App, 问题恢复到 step 1

PS: 症状有些不同, 此时你肯定又会去怀疑是不是操作系统的问题了呢, 还是 Native

无法复现

  • 在 iOS 12 中时无法复现
  • 当 PB 不是以 Link Binary with Libraries 的方式引用的时候无法复现

PS: 看似是一个小问题, 坑是真的不少

可能原因

下面按照我当时判断的可能性进行排序, 可能性高大放在前面

  • Library Linker 的问题(我曾经遇到很多诡异的问题都是因为库连接器导致的, 这次我理所当然的也这么认为, 但是如果真是这个原因, 那就很难办了)
  • iOS 系统 BUG(因为在 iOS 12 上是正常的, 但是怀疑是系统 BUG 是没什么意义的, 即便是系统 BUG 你照样需要去解决)
  • 代码层面的问题, 排查个个环节(详见下图)

时序图

解决思路

如果你认为我会安装上面我认为的可能原因去解决这个 Bug 那就大错特错了
上面我认为的可能原因, 不管是按照哪条去排查无疑都是工作量巨大, 特别还是遇上了前人留下来的代码山(PB), 更是难上加难

以堵代疏

俗话说得好堵不如疏, 但是在软件行业恰恰是反其道而行的, 常常是能堵上漏洞就已是大功一件了(虽然我现在也打算这么干, 但是我仍然觉得这是一件很操蛋的事)
这里先不去管可能原因, 仅针对表象进行处理

方案

由上得到可用信息如下:

  • PB 采用固定布局
  • PB 可以横屏, 但布局为竖屏(大雾~)

方案: 找出所用用到屏幕长和宽的代码, 替换为

1
2
3
// 竖屏的屏幕宽和高
width = MAX(view.width, wiew.height)
height = MIN(view.width, wiew.height)

找到宽和高的宏定义 -> 替换代码 -> 重新编译 -> 运行

结果
  • PB 中的代码并不是严格的使用宏中的宽和高
  • 症状并不完全是按照可以横屏, 但布局为竖屏, 这个是致命问题

首先, 这个方案是失败, 但是并不妨碍我认为这个方案是可行的
其次假设如果 有第 2 条这个致命问题的话, 或许我真的会把所用用到屏幕长和宽的代码, 都替换一遍

一些尝试

  • 使用新版本编译系统, 重新编译 PB Framework
  • 调高 PB Target 版本号

以上均失败(能成功就有鬼了)

可能原因排查

能想到的黑招都使用了, 这下就只能老老实实的按照可能原因去排查了, 但不要以为我会按照上面的顺序去排查

标准写法

首先按照苹果的标准来做, 要对页面进行旋转, 就必须在页面中实现shouldAutorotatesupportedInterfaceOrientations这两个方法, shouldAutorotate设置为true, supportedInterfaceOrientations设置为需要旋转到的方向, 横屏UIInterfaceOrientationMaskLandscapeRight, 竖屏UIInterfaceOrientationMaskPortrait

1
2
3
4
5
6
7
8
9
- (BOOL)shouldAutorotate
{
return true;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}

打开 Find Navigator 输入 shouldAutorotate, supportedInterfaceOrientations 疯狂 Next
一通操作猛如虎, 一看结果 0/5

排查流程

按照上图中列出的流程, 根据ViewController的生命周期, 查询是否有代码影响到了屏幕的旋转
这里不卖关子了, 问题找到了, 我大致的罗列一下关键的代码

PA:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}

PB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)viewDidLoad
{
[super viewDidLoad];
[self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self interfaceOrientation:UIInterfaceOrientationLandscapeRight];
}
- (void)interfaceOrientation:(UIInterfaceOrientation)orientation
{
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}

通过上面列出的关键代码, 想必大家已经能看出问题处在那里了
看到上面的代码瞬间就茅塞顿开(至少大部分的问题已经能解释通了), 万恶的黑魔法, 老是有人使用一些奇技淫巧来解决问题(这里我并不是要抨击这种行为, 其实我也经常会这么干, 但是还是那句话我并不喜欢, 这个到最后再来谈谈我的看法)

原因梳理

PA 进入 PB -> PB 调用 interfaceOrientation:
这时还是正常的, 尽管这里使用了黑魔法

PB 返回 PA -> 页面显示正常
其实在这里就已经出现问题了, 通过黑魔法改变的值并没有改回来, 当再次进入 PB, 调用interfaceOrientation:, 黑魔法失效了(我猜测的原因是重复设置同一个值时, 无法触发系统判断此时应该需要进行旋转操作了, 这个没有找到可以验证的方法)

一些未解之谜

  • iOS 10, 和 iOS 13 Bug 表现不一样的问题
  • 在 iOS 12 中时无法复现
  • 当 PB 不是以 Link Binary with Libraries 的方式引用的时候无法复现

上面的原因都可以通过使用了黑魔法这个原因来解释吗?(没有检验的方法)
我在这里持保留意见

一些对软件开发的思考

上面写了这么多, 看似没什么用, 其实暴露出了很多的问题
在我个人来看其实不应存在这种问题, 如果每个人都按照标准流程去处理, 无论是程序员之间的代码通讯还是代码和系统底层进行通讯, 出离奇问题的概率应该会大大减少, 即使出现了网上也大概率能找到相似的问题
那么程序员们到底是为什么要如此做呢? 难道他们就不清楚会导致这个结果吗?
有太多问题需要解释, 但又无从解释, 不过我还是想尝试强行解释一下, 对不对就随他去了

系统层之罪

苹果的软件质量下滑, 想必大家都是有目共睹的, 下面我就随便举一些我遇到过的例子(只讲开发中的问题, 苹果的系统应用问题这里不聊)

  • 在 App 中没法调用 Framework 中的类和方法, 至今我没能找到问题所在(换公司电脑同样的操作却没问题), 即便是强制加载 Framework 也仍然是行不通
  • AutoLayout 在 UITableViewCell 中进行 UILabel 的横向布局, 第一个 UILabel 会被压缩, 但是在 UIView 中确实正常的
  • 在 Storyboard 中的 UITableView 如果 UITableViewCell 的个数超过一屏的话, 超出的 UITableViewCell 的 AutoLayout 的显示会出错, 但是在实际的运行中却是正常的
  • Debug 断点的时候首次触发断点, 你永远不知道它在那里 loading 什么东西, 为什么要那么久
  • Leaks, View UI Hierarchy 等工具, 开发了一万年, 用起来仍然是各种卡, 各种崩溃
  • Xcode 10 导致 iOS 9.2 崩溃的问题, Xcode 10.1 上线了明明写着修复了, 问题依旧(最后的说法貌似是在服务器端修复了, 我没有得到可靠的消息, 无法证实)
  • 相同的代码经常在不同的 iOS 版本中的表现不一致(这里不做详细的描述)

上面我仅仅是列举除了一些影响正常开发的问题, 一些不影响开发的小问题简直懒得说了
这些我想足够能解释为什么我会认为是系统的问题了吧
当然这并不是要把一切问题都甩给系统, 而是当系统层经常性不稳定时, 开发者就会自然而然的对系统产生不信任感, 从而影响到开发者自身对问题的判断, 以至于被引导到一条死胡同里

开发者之罪

试想一下, 当一个不懂互设计, 甚至连UIKit都没听说个的人, 设计出来的页面, 然后再交给一个三流的程序员去开发(没有贬低的意思, 我也是三流程序员), 结果可想而知
问题不在于是否存在问题, 而是在于整个生产环境就是不健康的, 导致整条流水线上的人都很浮躁, 只想着守着自己的一亩三分地, 不愿改变也不想改变, 最后的结果就是, 先来的坑后来的, 后来的继续坑下去, 进入死循环
以至于愿意改变和学习的人也被大环境的洪流给拉下了水, 比如你去接手前辈们留下来的代码(垃圾山), 你会如何做呢, 是重写(重构几无可能, 能进行重构的代码表示还是有大致的设计的), 还是继续往垃圾山中倾倒垃圾呢? 我想答案不言自明了
至于什么代码写的好就随时都能被替代这种说法我就不想说什么了, 随它去了

开发模式之罪

业界从传统软件开发迈向现代软件开发过程中, 疯狂的强调这Change, Runing, More
我不能说这是错的, 其实我反倒是赞同现在软件开发模式的, 但是并不能因为我赞同就不去讨论这里面存在的问题了
当敏捷软件开发大行其道的时候, 效率虽然得到了提升, 各种新的特性被加入到了软件中, 这是一件好事, 但是伴随着这些明显的好处带来的确实软件的质量的不断的下滑
比如什么不影响使用的 Bug 可以暂时性的忽略, 先上线, 但是最后的结果就变成了, 这个 Bug 伴随着软件走向了终结都没能去解决(并不是没能力解决), 新特性却是一大堆, 而新的特性随之又带来了新的 Bug 以至于把开发者带入了 Bug 泥潭, 最终逼迫开发人员用脚投票放弃高质量代码

再次重申一下我是赞同现代软件开发的, 当然我现在也不知道这算不上是一个问题, 因为这完全可以通过人治的方式解决, 比如工作量的精准量化(尽量小的安排工作量, 0.5 天一个迭代), Bug 过多, 可以安排一整个大的迭代周期进行修复, 代码优化重构可以一定的周期进行一次, 或者每个大周期进行一次, 总归来说还是人治的成分在里面, 需要管理人员有很强的业务能力(了解开发和产品细节, 了解开发人员的能力, 能够把握开发的节奏), 在小型的团队中很难找到这样的人来总览全局, 小型团队中更多的是突击型的开发人员, 如何让这种类型的开发者参与到敏捷开发总来, 而且能够高产出和高质量代码都兼顾还不能对管理人员的要求太高
要找出这种情况适用的方案的解, 虽然有点强人所难, 但是我觉得真是因为有困难我们才更应该去解决, 毕竟程序员就是一群专门解决问题的人, 不是吗?

总结

无论软件行业如何变化, 作为程序员的我们应该做也必须做的就不断的学习和思考, 并努力写出优秀的代码去影响后来者, 实现正循环
与君共勉