Clash of the Coders 这样的比赛,很有意思。在72小时不休不眠的时间内显示精神力量,在Objective-C运行时,UIAplication,堆栈层进行暴力行为。这是个创举,为了颠覆它而使用我所有平台的知识。
那天我和我一个 Ranch 同事聊天,他说,“Hey MarkD,怎样让我学习到所有这些细节?我觉得我好像困在地狱的土地上,只会使用 tableviews 和诸如此类的东西。“ 说一个艰巨的问题,特别是来自一个有大量技能的工程师。“嗯,你刚才做的东西?”,不是一个好的答案。
我在编程行业(实际上是靠它谋生)23年了。这比我的一些同事活的时间还长。这是一个很可怕的事情,滚开。多年来,我已经养成一种学习和探索的习惯,这是一种很不错的事情。当然,每个人是不一样,但以下是我如何回答他的问题。
阅读
大量阅读。在你的大脑中填充信息。有很多的书籍和博客,特别是相比于所有 Mac 编程书只能装在一个宜家书架上的一层的时代。那时我最喜欢的书是 Rob Napier 和 Mugunth Kumar 的 iOS 6: Pushing The Limits。我必须也提下Advanced Mac OS X Programming : The Big Nerd Ranch Guide,因为它挖掘了 厚实的 OS X internals(这本书的电子本足以让你的 Kindle 变重)。有一些已经过时了,但一个实实在在的操作系统在意的各种细节是让人着迷的。
我们生活在一个信息丰富的网络时代。除了这个不起眼的博客,你也应当阅读 Mike Ash 的 NSBlog。我也经常阅读 NShipster 和 Jeremy W. Sherman 博客。
别忘了要阅读官方文档。浏览 Xcode 中的 Cocoa / UIKit 文档。我在我的 iPad 上装了 DocSets,这样在我等待的时间里我可以略读这些文档。别把自己限制在你一直使用的类上。把你的时间都花在 NSTableView?读下 IOService 吧。你可能永远不会使用它,但你可能会发现一些能够激发进一步学习的主题。
我尝试定期通读编译器和调试器文档。gdb 有很好的文档。gdb 至今仍然相关-lldb 在设备上运行仍然有些不可靠。lldb 网站上有一些支持文档,所以坚持它的内置帮助来学习所有有用的命令。确保阅读所有的东西。如果你看到一个深奥难懂的特征,尝试揣摩它为什么存在。
编译器内幕可以在 http://clang.llvm.org 网站找到。也别忘了头文件,它们包含很多有用的信息。
我记不住所有东西。现在我记不住大部分东西。但我有印象“我觉得我之前读了这类东西。它在哪里?” 深思下就提醒了我 “是的,Jeremy 在他的博客上用12行代码重新实现了 Cocoa,他有一个很酷的 higher-order-messaging 分类,可能会有用。
剖析
要学习东西是如何工作的,我喜欢研究东西是如何工作的。知道谁调用谁是个关键。“谁创建了这个 undo 管理器?” 这是解决 bug 的关键。当 MPMusicPlayerController 在播放列表中一首一首播放的时候,检查应用程序中到处飞的通知可以帮你发现它在做什么。
我们有很多强大的工具来窥视我们的程序和库。你很可能不会每天都使用它们中的一个。有些你可能一个月或一季度才使用一次。但在你们的工具包中有他们是好事。
源码
clang 和 lldb 的源码很容易得到,你可以下载它们并编译。想知道这个语言中某个东西如何工作?在源码中找找看是否能发现它。你也可以在 http://www.opensource.apple.com 上得到 Core Foundation 源码中的一个适当的子集(其中包含了很多其他东西)。真正有趣的部分是“xnu”-内核,“CF”-Core Foundation,还有 objc-object-C 运行时。当然,钻研 Cocoa 头文件,发现一些你可能不理解的,比如 NSAssert。然后找出它是如何工作的。
调试器:你可以在任何可见的变量符号上设置断点,不管是你自己的代码,还是二进制库中的方法和函数。你可以在 Xcode 中通过添加一个符号断点直接进入断点。下面是我在 UIApplication 中的 _performMemoryWarning: 私有方法中添加了一个断点。
你可以在模拟器中触发一个内存警告,看看它是否被处理了:
通知 spying:通知是常用的去耦代码。一些有趣的事情发生,然后通知被发送了。NSNotificationCenter 和 朋友是如此便利以致几乎所有的东西都使用它。你可以添加一个通知探测来打印所有漂浮的通知。
DTrace:我知道人们希望我停止谈论 DTrace。我如何发现 _performMemoryWarning 符号?它显然不在任何苹果的文档中,下划线开头表明这是“私有 API”,这是别插手的标志。我在模拟器中 DTraced 一个问题,触发了一个内存警告,然后看到了所发生的。程序崩溃时我想再解答其他一些问题,比如“UITouch 在哪里被创建并分发?”,还有 “当我加载一个 nib 文件时出发了文件系统的什么?”。这样诞生了一个小的工具脚本:spy.d(点击这里)。
spy.d 是一个简单的脚本,可追踪 objc 提供的 Object-C 消息,忽略常用消息列表上的消息,然后为“特别有趣”的消息列表上的方法显示完整的栈追踪信息。我运行 spy.d 然后在模拟器上触发了一个内存警告。
# ./spy.d 61271
...
UIApplication -didReceiveMemoryWarning
NSNotificationCenter +defaultCenter
NSNotificationCenter -postNotificationName:object:
...
UIImage(UIImageInternal) +_flushCache:
UIViewController +_traverseViewControllerHierarchyWithDelayedRelease:
UIViewController -didReceiveMemoryWarning
UIViewController -_traverseViewControllerHierarchyFromLevel:withBlock:
RMScheduleVC -didReceiveMemoryWarning
UIApplication得到一个内存警告。它发送一个通知。UIImage 刷新它的缓存作为响应。然后 UIViewController 的类方法遍历控制器的继承并告诉每个类它得到一个内存警告,然后它冒泡到我代码中的 RMScheduleVC。
class-dump: Objective-C 运行时元数据相当丰富。你可以在运行时浏览类和他们的代码然后发现所有可以玩的很酷的玩具。这个信息实际上存储在应用程序二进制中(还有对象文件和库),然后可以被抽取。Steve Nygard 的 class-dump 会提取这个元数据并将他找到的所有类生成 @interfaces。例如,从 UIApplication 中:
...
- (void)_purgeSharedInstances;
- (void)setReceivesMemoryWarnings:(BOOL)arg1;
- (void)_receivedMemoryNotification;
- (void)didReceiveMemoryWarning;
- (void)_performMemoryWarning;
- (BOOL)_isHandlingMemoryWarning;
- (void)_processScriptEvent:(struct __GSEvent *)arg1;
- (void)_dumpScreenContents:(struct __GSEvent *)arg1;
- (void)_dumpUIHierarchy:(struct __GSEvent *)arg1;
- (void)setSystemVolumeHUDEnabled:(BOOL)arg1;
...
这里是所有进行中的有趣的事情。这里你可以放断点,并在调用堆栈中浏览。你也可以潜入到实现中。
Hopper Disassmbler:Hopper Disassembler 是个阅读对象代码并显示相应的汇编语言的工具。这里它正在查看 UIKit,UIApplication 的 _performMemoryWarning 实现方法。
Hopper 简洁之处在于,它能将汇编语言转换成伪代码,这样你可以更好的理出代码流程。
这也是通过例子学习汇编语言的一种灵巧方式。需要注意的是你在查看的是编译器产生的代码,所以当优化时它并没有太大意义。
Instruments:Instruments 不仅仅用于发现性能问题。它可以用于寻找所有有问题代码中的位置,包括你自己写的代码还有库中的代码,你可以在其他工具开始进行攻击。想知道 UISlider 如何做现场追踪?在一个有 slider 的应用程序中用 Instrument,移动10秒,然后看看调用堆栈的顶部是什么。然后你可以开始在模拟器中应用 DTrace,或者 disassembling,然后看发生了什么。
一个简短警告:-小心应用你从这些工具中获取的信息。他们对于调试和发现事情如何工作是非常棒的。依赖这些工具中的实现细则和私有 API 可能导致一个脆弱的软件,会破坏主要或次要的 OS 版本。如果你正在编写的软件是针对付费用户,或者针对要用这个软件做重要事情的用户。你是一个专业的程序员。确保要像一个专业的程序员。
实验
现在你有一个工具舰队,是时候学习一些东西。通过动手操作学习最好。弄脏和犯错误!有兴趣深入学习 UITableView?写大量 table view 代码。浏览文档。查看你之前可能没用过的 API 头文件,并使用这些看看会发生什么。阅读一些博客,使用 Class-dump,寻找一些令人好奇的私有 API,然后看你是否能在常规接口中触发它。使用 DTrace 查看消息流。添加通知监测看看它试图告诉你什么工具包。如果事情不能正常工作,使用你常用的调试技巧,然后找出为什么不能工作。看看你是否能自己重新实现它。
我倾向于在 UIKit / AppKit 下浏览代码,因此我写了很多联系数据结构和语言特征的小程序。Steve Sparks,我在 Clash Crime 中的拍档,有它的游乐场,一个有 tab view 和 导航控制器的 Xcode 项目,他可以在任何时候在这个项目中做实验。想玩玩 UIScrollView 暴力?折腾一个新的视图控制器,启动它,现在你可以在模拟器或设备中玩它。保持代码版本控制,然后你可以有一个你所有实验的记录。
消化
你决定开始学习一些知识。你写了大量代码。你调试了。可能你也做了大量笔记。现在是时候消化所有这些了。带着它进入梦乡。来个骑行。去洗个长时间的澡澡。让所有你接触的新知识形成模式。看看你是否能从这些模式中建立一些抽象概念,然后在其他类中应用它。现今的许多软件开发都可以机械化,它们有一些特征需要被实现,你整合在一起的常用类集合。
同化步骤最有趣的是,在你的大脑中开始建立关系。例子中的很多都来自于我们在 Clash 入口中出现的一个简单的问题:“我们想在应用程序命令行触发内存警告”,来帮助调试内存并缓存问题。调查过程如下:
- 我们可以在模拟器中触发内存警告
- 使用通知监测看看是否有通知被发送。为什么有。我们可以 hook 进去并对它们作出响应
- 发送不会实际触发东西的通知。没用。我想知道为什么。
- 是否有我寻找的其他内存警告方法?使用 class-dump,看看在哪里
- 发送 _performMemoryWarning 给当前陷入内存警告机制的应用程序实例。为什么它没有发送通知?
- 只为了有趣,使用 Hopper,看看实际发生了什么。噢,看,它有视图控制器继承和 sqlite,在发送内存警告通知时它们没有被激发。
- 添加一个内存警告 button 来调试 console,嵌套在 #if 块中来防止它被释放到外面。
总结
这里有很多我学习 nerdskills 的秘密策略:
- 阅读-往大脑中塞很多事实。有些事实会一直在,我发现那个东西很有趣,所以这是个愉快的消遣。
- 剖析-舒适地使用大范围的工具,从高级到低级水平,别害怕使用它们。别害怕怪异地使用它们。DTrace 会是个大铁锤。但有时它能在小地方用并粉粹一个 bug。使用这些工具来研究软件如何工作。保证你会使用这些知识做好事,而不是坏事。
- 实验-写代码。写大量代码。实验。玩所有发现的新玩具。梳理出模式。
- 吸收-反省。激动,冲洗,重复。并在接下来的20年继续从事它。建立深度的技能是一个长期的工作。
本文翻译自 Mark Dalrymple 的博文 LevelingUp
网友评论
为什么看不懂啊。。
龙王传说http://www.52zwxs.com/book/18628/