第三章:战壕里的故事 —— 你的 App 为什么又崩了?

3.1 手动挡的乐趣(与恐惧)

现在的孩子们(指 Swift 开发者)可能无法想象,在 ARC(Automatic Reference Counting)出现之前,我们是怎么活下来的。

那时候,我们是手动的引用计数(MRC, Manual Reference Counting)的奴隶。 每一个对象生下来(alloc),你都要负责对它说“我要你”(retain)。当你用完了,你必须记得说“我不要你了”(release)。

如果你忘了说 retain,对象就会在你转身的一瞬间消失,然后你的程序就会在一个莫名其妙的地方崩溃,报错信息只有冷冰冰的一行:EXC_BAD_ACCESS。这通常意味着你访问了一个还是温热的尸体(Zombie Object)。

如果你忘了说 release,恭喜你,你的 App 会像吃自助餐一样吞噬内存,直到被操作系统无情地杀掉。

那时候的 iOS 面试题,有一半都是关于内存管理的:

  • “请问 retainCount 是几?”(正确答案是:别问,问就是不准。)
  • autorelease 什么时候释放?”
  • “为什么我在循环里创建临时对象会导致内存飙升?”

我们不仅是程序员,我们还是拆弹专家。

3.2 经典的崩溃:Unrecognized Selector

如果你写过 OC,你一定见过这个报错: -[__NSCFString count]: unrecognized selector sent to instance 0x...

这通常发生在你把一个 NSArray 当成了 NSString 用,或者反过来。 因为 OC 是动态类型的,你把一个猫指给编译器看,说“这是狗”,编译器会说“好的,它是狗”。 但是当你试图命令这只“狗”去“汪汪叫”的时候,这只猫就会给你一巴掌(抛出异常)。

这种极其宽松的类型检查,既是噩梦,也是乐子。 它意味着你可以把任何东西传给任何方法。这也催生了一个著名的防崩溃库模式:即使你向我发送了我听不懂的消息,我也假装听懂了,或者把消息转发给那个真正懂的人。

3.3 那些年我们写的宏

为了少写点代码,OC 程序员个个都是宏定义(#define)大师。

#define WEAK_SELF __weak typeof(self) weakSelf = self;
#define STRONG_SELF __strong typeof(weakSelf) strongSelf = weakSelf;
#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]

每一个项目里都有一个 Prefix.pch,里面塞满了各种奇技淫巧。 有的宏甚至能把 OC 伪装成 JS 或者是 C#。

3.4 总结

那是一个充满了危险的年代。代码里到处是地雷。 但当我们成功地在一台 256MB 内存的 iPhone 3GS 上跑起一个复杂的 3D 游戏,且没有内存泄漏时,那种成就感是无与伦比的。 我们是走钢丝的人。

使用 Hugo 构建
主题 StackJimmy 设计