转载

iOS开发者如何提高自己的水平?

(原文: Leveling Up 作者:Mark Dalrymple 译者:xiaoying )

iOS开发者如何提高自己的水平?

不知道你有没有参与或是旁观过iOS开发的黑客马拉松,我觉得这是非常好的事情,在这里人们几乎不睡觉,一起脑洞大开,在Objective-C运行时环境下,利用iOS的相关知识攻城略地,度过激情四射的72个小时。对于我来说,它们让人心潮澎湃,我的所有平台知识都在头脑风暴下接受考验,我的技能也得到锤炼。

在某场活动里有一次我和我们组的一个伙伴在聊天的时候,他问我:“MarkD,我要怎么样才能学到所有那些知识的细节?我觉得我现在有点迷茫,我的编程工作好像就是使用tableview和一些其他有的没的。”这是个让人不敢轻易回答的问题,尤其当这个问题是从一个经验丰富的工程师口里问出来的时候,像“呃,你只要不断地去实践就好了”这种答案肯定是不负责任的。

他的问题让我不禁联想到,到现在为止,我已经专职编程(其实也是为了生计)23年了,这着实很吓人,因为这个时间比我的一个同事的年龄都大——你们这些小屁孩快离开我的领地啦,开玩笑的,呵呵。不过,经过这么些年里的学习和探索,我也确实得到了很多很宝贵的东西。当然,每个人的想法可能都是不同的,但这里是我对于他的问题的回答。

阅读

大量的阅读,把大量的信息塞进脑子里。现在外界有大量的书和博客可以阅读,尤其是和过去相比,那时所有关于Mac编程的书加起来估计也塞不满书架上的一层。现在我最喜欢的书是Rob Napier和Mugunth Kumar的《 iOS 6: Pushing The Limits 》。此外我还要必须推荐下《 Advanced Mac OS X Programming : The Big Nerd Ranch Guide 》,因为这本书深入到了很多OS X系统底层的细节,这些细节很多也都是同时适用于iOS的。当然,也不能忘了Amit Singh的巨著《 OS X Internals book 》(如果你将这本书的电子版装进自己的Kindle,你的Kindle也会变得重很多^_^),这本书里的有些部分有点过时了,但在这本书里能看到很多一个真实操作系统内核的实现细节,这还是挺吸引人的。

我们接触到的网络世界也有很多信息值得阅读。除了当前你在阅读的这个不起眼的网络博客,你还应该读一下Mike Ash的 NSBlog 博客。同时我也经常去 NSHipster 和 Jeremy W.Sherman 的博客 。

另外,也别忘了去读一下官方文档,在Xcode里浏览一下Cocoa或者UIKit的文档。我在我的iPad上装了一个 DocSets ,这样我就能利用等待事情时候的间隙去阅读一些东西。不要把自己限制在一个你一直处在的圈圈里,如果你整天都在使用NSTableView,可以抽时间看看 IOService ,也许你永远也用不到它,但是你也许会发现一些能启发你未来学习的课题。

我会尝试定期的阅读编译器和调试器的文档。 gdb 的文档很完整,而且它现在仍然是和iOS编程息息相关的——因为lldb在设备上用还是有些不可靠。 LLDB 的网站也有很多支持文档,所以可以去看一下它内建的帮助系统去学习所有可用的命令。 阅读所有可阅读的东西 ,如果你看到一个很难看懂的功能特性,可以尝试去搞懂它存在的价值。

关于编译器的内幕知识可以在 这里 找到,同时不要忘记看一下 头文件的相关知识 ,那里也有很多有用的信息。

当然,我不可能全部记住所有的知识,实际上我已经忘记了大部分我看过的东西,但是我时常会碰到这样的时刻,“我想我之前一定在什么地方看到过与这个相关的信息,那么是在哪里看到的呢”或者忽然想到“哦,对了,Jeremy曾经写过一篇博客,里边他用12行代码实现了Cocoa的所有功能,而且他有一个很好的高阶消息处理类别(category),我可能能用到。”

分析

为了学习事物的工作原理,我喜欢首先去了解它们互相之间的调用关系,这往往能让人得到一些启发。比如在本篇文章描述的这个问题里, 解决问题 的关键是搞清楚“究竟是谁创建了NSUndoManagers的实例”。或者,为了了解MPMusicPlayerController到底是怎么切换歌曲的,你可以通过持续观察在你的应用里不断被发送出来的notification来分析其中的工作原理 。

同时,有很多很强大的工具可以用来帮助你监听并分析你的程序和库。你可能不会每天都用到它们,可能有些工具一个月甚至三个月才能用到一次,但是把它们放在手边会对你很有帮助的。下边是一些相关的工具介绍:

源文件:clang和lldb的源文件都是很容易得到的,那么你可以把他们下载下来并且编译通过。当你对这个语言的某个地方的工作原理不清楚时,就可以打开源文件找找看。你也可以在 http://www.opensource.apple.com 得到一份相当不错的Core Foundation框架的源文件(和其他一大堆东西在放一起),其中我觉得真正有意思的是这么几部分:“xnu”——内核,“cf”——Core Foundation,还有“objc”——Objective-C运行时环境。另外,一定要去仔细看看Cocoa的头文件,里边可能会有很多你不是很了解的东西,像NSAserrt, 然后搞懂它们。

编译器:你可以在任何 可见的符号 处设置断点,不论是是你自己写的代码行里,或者在二进制的库里的函数和方法处。你可以直接在Xcode里添加一个符号断点,下图是我在UIApplication里的私有方法_performMemoryWarning上面设置的一个符号断点。

iOS开发者如何提高自己的水平?

你也可以在模拟器里出发一次内存警告,来看看它是怎么被处理的:

iOS开发者如何提高自己的水平?

Notification监视: Notification 常常用来对代码进行解耦,当一些事件发生时,我们可以发送一个notification通知另外一个对象去完成其它动作。NSNotificationCenter和与之相关的一些方法太有用了,几乎所有的事情都会用到它,如果想对所有系统中发出的notification进行监视,你可以添加一个 notification监视器 。

DTrace:我知道很多人都希望我不要再谈论 DTrace 了,哈哈,但是没有它我怎么才能找到诸如_performMemoryWarning这种符号?很显然,你不可能在任何苹果的官方文档找到这个方法,因为所有带有前导下划线的方法都是你不能访问的“私有API”。这里有一个DTrace的使用实例,在Clash of the Coders中我也想回答另外一些问题,比如“UITouche是在那里被创建和分发的?”,还有“当加载一个nib文件时,到底是什么东西在文件系统定位到了这个文件?”。这也促使我写了一个工具脚本,spy.d(可以在 这里 找到)。

spy.d是一个简单的脚本,它利用objc provider来跟踪Objective-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得到这个内存警告,然后它发送了一个notification,作为响应,UIImage清掉了它的缓存,之后,UIViewController的类方法按照层级遍历它的控制器,告诉它们它收到了一个内存警告,然后它传送到了我的RMScheduleVC代码里。

class-dump:Objective-C的运行时环境包含了很丰富的元数据,这些元数据使得你可以在运行时查看类和方法,也能找一些其他的好工具来操作他们。这些元数据(和目标文件,还有库)实际上是存储在程序的可执行文件里的,它们是可以被导出的,Steve Nygard的 class-dump 可以提取出这些元数据然后对于所有的类生成它们的接口。例如,这是从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 Disassembler: 这是一个读取目标代码并且显示相应的汇编代码的 工具 。下面是用它来查看UIKit中UIApplication类的_performMemoryWarning方法的实现。

iOS开发者如何提高自己的水平?

Hopper的厉害之处在于,它可以将汇编代码转换成伪语言,这样开发者可以更好的看到代码流程。

iOS开发者如何提高自己的水平?

同时,这也是一个学习汇编语言的好机会。你今后很可能会需要读很多编译器生成的汇编代码,尤其是当编译器对代码做过优化时,汇编代码会更不容易看懂。

Instruments: Instruments不仅可以用来处理性能问题,它也可以用来在这个程序的所有代码里寻址,不论是你自己写的代码还是库里的代码,用它和其他工具一起,你就可以攻击别人的程序了。比如如果你想搞清楚UISlider(滑块控件)是怎么来做实时跟踪的,在一个包含滑块控件的应用里打开Instruments,拖动滑块来回移动大约10秒钟,然后查看在Instruments的调用堆栈的最上层是什么,你还可以在模拟器里使用DTrace,或者进行反汇编,看看会发生什么。

小小的提醒:当你想要把自己从这些工具中学到的知识应用到你的程序中去时,一定要小心。这些工具对于调试程序和理解程序工作原理很有帮助,但是里边会有很多是依赖系统实现细节的或者是一些私有权限的API,如果把这些东西应用到你要发布的应用程序中去的话,很可能会使你的程序变得很脆弱,或者不被新版本的系统支持。如果你的程序是给那些付费用户使用的,或者用户会使用你的程序做一些很重要的事情,那么可能会造成一些不好的后果,这样就不专业了,努力去做一个专业的开发者。

实验

现在你有了一套工具,是时候该去真正地学些东西了,我觉得最好的学习方法就是去 真正的实践 ,不断地去犯错误!你想深度地学习UITableView?那么就去写大量的关于表视图的代码。多看文档,去阅读那些你可能还没用到过的一些API的头文件,写代码去使用它们。读一些博客,用class-dump去测试它们,从结果中找到那些有趣的私有API,看看你是否能用普通的接口去触发它们。用DTrace测试它们,看一下消息的流向。把notification监视器放进去,看看你能得到什么信息。如果实验的时候出现了问题,就用你平常使用的调试方法去搞清楚问题出在哪里,也可以试着自己写代码去重新实现它的功能。

我自己喜欢在UIKit/AppKit的代码世界里四处玩耍,所以我写了大量的小程序去实现这两个框架里的数据结构和一些 语言特性 。Steve Sparks是在参加Clash of the Coders时我的队友,他也有他很多自己的爱好,随便在Xcode里创建一个工程,拖进去一个标签试图(tab view)和导航控制器(navigation controller),他就能搞出一个新实验来。如果你想要玩一玩UIScrollView,那么把它拉到一个新的视图控制器(view controller)里,连接到工程里去,你就可以在模拟器或者真实设备上运行了,记得在工程加入 代码版本控制 ,你就可以轻易的记录下来你做过的所有实验。

同化

到现在为止,你确定了要学习的东西,写了一堆的代码,它们也都调试通过了,可能你还记了很多 笔记 ,那么现在是时候消化一下,把它们都同化为自己知识体系的一部分了,现在你可以好好睡一觉,去骑会儿自行车,或者洗个澡,放松下来。然后把这些知识在脑海里归类,看看能否得到一些抽象的概念,然后把它们用到其他的类里。现在的软件开发有时就像机械化装配——有一堆的功能要去实现,还要把很多类组装在一起。

这个同化知识的过程是很有趣的,因为你的大脑开始建立一些概念间的关系。这里我讲到的很多例子都是来源于在Clash of the Coders上编程时碰到的一个简单的问题:“我们想要手动地去触发内存警告”,来调试内存和缓存方面的问题。整个研究过程大致是这样的:

  • 我们在模拟器里触发内存警告

  • 使用notification监视器查看是否有notification被发出,为什么会有notification发出呢,我们可以接收并且对这些notification进行反馈。

  • 这个notification并没有触发别的任何行为,该死,我在想这是为什么。

  • 内存警告触发时,什么方法被触发了呢?用spy.d来查看到底发生了什么,哦,看,_performMemoryWarning看起来很有意思嘛。

  • 是否还有其他的内存警告方法我应该去看一下的?拉出来class-dump看看里边有什么。

  • 发送_performMemoryWarning消息给UIApplication对象好像没有触发内存警告,怎么回事,为什么没有发出notification呢?

  • 好吧,好奇害死猫,我又用Hopper看下它到底做了什么。哦,原来如此,那里有一个视图控制器树和SQLite的东西,如果仅仅只有内存警告的notification被发出,它们是不会被执行的。

  • 最后我在调试控制台里加入了一个触发内存警告的按钮,同时为了不让它最终显示出来,用#if块把它保护起来。

总结

差不多以下就是我用来磨练自己技艺的奥义:

  • 阅读。 把一大堆的知识塞进脑子里。随着时间流逝,终归有一些会留在脑海里。我觉得有些东西读起来还挺有意思,那么也能算作一种愉快的消遣。

  • 分析。 多去熟悉并了解一些工具,从高层的到底层的,不要害怕去使用他们,也不要害怕用各种各样古怪的方法使用他们。例如DTrace就像一把大锤子,有时你可以用它把一个bug分解成很多小片段。使用这些工具去深度挖掘软件背后的原理和其运作方法。但是请保证你不会用这些知识来作恶。

  • 实验。 一定要写代码,写大量的代码。另外通过做实验去接触新发现的编程方法,并且不断地梳理这些知识。

  • 同化。对于学到的知识,不断的进行反省,洗涤,过滤,重复,让他们真正成为自己知识体系的一部分。然后在接下来的20年里持续的去这么做。学习更加高深的知识是一个长期的过程。

(本文为CocoaChina组织翻译,本译文权利归译者所有,未经允许禁止转载。)

正文到此结束
Loading...