10个迷惑新手的Cocoa,Objective-c开发难点和问题
原文地址: http://www.cocoachina.com/bbs/read.php?tid=74564
首先请谅解我可能使用很多英文,毕竟英文资料将来会是你的主要资料来源。
在你继续深入学习之前,请停下脚步弄清这些问题。如果你是新手,这个教程不要希望一次能看的非常透彻,学一定阶段反回来再看看又会有新的体会的。
1. c,c++ background
很多人问 “没有任何语言基础,我不想学c直接学objective-c”
这里简单几句,objc 90%代码是c、众多开源代码是c,c++。你不学好c在unix世界里只能是个二流开发者!也许说得过于严厉,不过自己斟酌把。
2. Runtime(运行时)
Objective-c是动态语言, 很多新手或者开发人员常常被Runtime这个东西所迷惑。而恰恰这是一个非常重要的概念。 为什么重要呢!?我可以这么问:“如果让你(设计、)实现一个计算机语言,你要如何下手?” 很少程序员这么思考过。但是这么一问,就会强迫你从更高层次思考(1)以前的问题了。 注意我这句话‘设计’括起来了,稍微次要点,关键是实现。
我把实现分成3钟不同的层次:
1. 传统的面向过程的语言开发,例如c语言。实现c语言编译器很简单,只要按照语法规则实现一个LALR语法分析器就可以了,编译器优化是非常难的topic,不在这里讨论范围内,忽略。 这里我们实现了编译器其中最最基础和原始的目标之一就是把一份代码里的函数名称,转化成一个相对内存地址,把调用这个函数的语句转换成一个jmp跳转指令。在程序开始运行时候,调用语句可以正确跳转到对应的函数地址。 这样很好,也很直白,但是。。。太死板了。everything is per-determined
2. 我们希望灵活,于是需要开发面向对象的语言,例如c++。 c++在c的基础上增加了类的部分。但这到底意味着什么呢?我们再写它的编译器要如何考虑呢?其实,就是让编译器多绕个弯,在严格的c编译器上增加一层类处理的机制,把一个函数限制在它处在的class环境里,每次请求一个函数调用,先找到它的对象, 其类型,返回值,参数等等,确定了这些后再jmp跳转到需要的函数。这样很多程序增加了灵活性同样一个函数调用会根据请求参数和类的环境返回完全不同的结果。增加类机制后,就模拟了现实世界的抽象模式,不同的对象有不同的属性和方法。同样的方法,不同的类有不同的行为! 这里大家就可以看到作为一个编译器开发者都做了哪些进一步的思考。但是。。。还是死板, 我们仍然叫c++是static language。
3. 希望更加灵活! 于是我们完全把上面哪个类的实现部分抽象出来,做成一套完整运行阶段的检测环境。这次再写编译器甚至保留部分代码里的sytax名称,名称错误检测,runtime环境注册所以全局的类,函数,变量等等信息等等,我们可以无限的为这个层增加必要的功能。调用函数时候,会先从这个运行时环境里检测所以可能的参数再做jmp跳转。这,就是runtime。编译器开发起来比上面更加弯弯绕。但是这个层极大增加了程序的灵活性。 例如当调用一个函数时候,前2种语言,很有可能一个jmp到了一个非法地址导致程序crash, 但是在这个层次里面,runtime就过滤掉了这些可能性。 这就是为什么dynamic langauge更加强壮。 因为编译器和runtime环境开发人员已经帮你处理了这些问题。
好了上面说着这么多,我们再返回来看objective-c. 现在你是不是能理解这样的语句了呢?
复制代码
记不记得上学时候学得操作系统这门课,里面都会有专门一章介绍任务调度和生产者消费者的问题。 这就是为了今后使用进程、线程开发打基础。概念很简单,但是心知肚明的人很少。难点在synchronization(同步),因为1. There is no 100% deadlock detection algorithm. If there is, no deadlock at all. 2. 往往这类错误很隐晦,静态分析很难找到。 3. 抽象度较高需要经验去把握。
总体来说,我见到的在这方面的问题可以分为一下几点:
1. 不知道多线程开发的几个基点,看别人代码越看越糊涂的。一会NSThread、一会*****、block等等。。。Apple封装了很多线程的api, down to core多线程的结构基本是
可以看到在多线程开发中你可以选择这几种不同的方式。Mach是最和心的操作系统部分,你可以用但是没必要,太累。
pthread灵活、轻巧,但是需要理论基础还是开发复杂,最主要的POSIX开的线程不能使用cocoa根据apple文档只在pthread下使用cocoa需要先detach at least one NSThread object. 这样确定[NSThread isMultiThreaded]才可以使用。
NSThread是Mac OS 10.0后发布的多线程API较为高层,但是缺乏灵活性。
Grand Central Dispatch 10.6引入的开源多线程库, *****介于pthread和NSThread之间。比NSThread更灵活,小巧但有不需要像pthread一样考虑很多lock的问题。而objective-c 2.0发布的新语法特性之一blocks也正是根据这种多线程需求推出的。
在你写多线程代码或者阅读多线程代码时候,心理先明确了这是那种。
2. thread和runloop造成的问题
其实thread和runloop放在以前开发者根本不太当成一个问题。因为没有runtime能力,runloop就是固定的线程执行loop。而现在cocoa开发新手搞不明白的太多了。 NSRunloop和NSThread啥关系?由于这个问题比较多,我单独列到第4点里讲解把。
3. thread和Reference Counting内存管理造成的问题。
引用
线程里面的方法都要放到NSAutoreleasePool里面吗
这类问题很常见,主要原因是 NSAutoreleasePool 到底是干什么用得不明白。 NSAutoreleasePool跟thread其实关系并不显著,它提供一个临时内存管理空间,好比一个沙箱,确保不会有不当的内存分配泄露出来,在这个空间内新分配的对象要向这个pool做一下注册告诉:“pool,我新分配一块空间了”。当pool drain掉或者release,它里面分配过的内存同样释放掉。可见和thread没有很大关系。但是,我们阅读代码的时候经常会看到,新开线程的函数内总是以NSAutoreleasePool开始结束。这又是为什么呢!? 因为thread内恰好是最适合需要它的地方! 线程函数应该计算量大,时间长(supposed to be heavy)。在线程里面可能会有大量对象生成,这时使用autoreleasepool管理更简洁。所以这里的答案是,不一定非要在线程里放NSAutoreleasePool,相对的在cocoa环境下任意地方都可以使用NSAutoreleasePool。如果你在线程内不使用NSAutoreleasePool,要记得在内部alloc和relase配对出现保证没有内存泄露。
这里还有一个值得提出的是autorelease. NSObject为何会有autorelease这个方法? 它是根据什么auto的?
4. mainthread和secondary thread疑惑
引用
NSThread的detachNewThreadSelector和self的performSelectorOnMainThread方法有什么不同
5. Asynchronous(异步) vs. Synchronous(同步)
引用
我在一个view要显示多张web图片,我想问一下,我是应该采用异步一个一个下载的方式,还是应该采用多线程同时下载的方式,还是2个都用,那种方式好呢?
大家可以看一下这个问题。这句有一点在我看来是非常奇怪的,因为我觉得问问题的人并不理解同步异步是什么意思。"一个一个下载的方式"是同步的行为,“多线程同时下载”是异步的行为。 都搞混了把!
4. runloop
现在说说runloop为何会成为cocoa开发中迷惑的点。因为很多新手没有从动态角度看它。 首先回想一下第2点介绍的runtime的概念。 接着我出一个题思考一下。
现在我有一个程序片段如下:
复制代码
while([object retainCount]>0){ [object release];}当然了,我也做过类似的动作,那种希望一切尽在掌握中的心态。但是你会看到其他人告诉这么做完全没有意义。rc does not work this way. 也许这样的暴力释放会起作用,但是retainCount并不是用来做这个的。每个数字意味着有其它对象引用该资源,这样的暴力释放很容易导致程序崩溃。这个数字也许并不是你心目中的哪个。因为你很难跟踪到底哪些对象引用的该资源。你用代码建立的资源不光只有你的代码才会用到,你调用的各种Framework,Framework调用的Framework,都有可能改变这个资源的retainCount. 所以去验证rc规则不是明智之举。
你能做的就是理解规则,使用规则,读文档了解container的引用特性。或者干脆移到arc上面,让runtime环境处理这些问题。
最后说一下不用arc的情况。目前情况来看,有不少第三方的库并未支持arc,所以如果你的旧项目使用了这些库,请检查是否作者发布了新版本,或者你需要自己修正支持arc。
8. class heritage
9. English
10. Just trying to be smart
其实剩下这个有好几点要说,但综合一下把。思路有些相似
例如刚看到这个问题:
引用
现在有A *a;A*b
[NSMutableArray addObject : a];
[NSMutableArray replaceObjectAtIndex:0 withObject:b]
执行完这两个之后,拿可变数组里面的0 的位置 就是b元素了,那这个时候a到哪里去了??是否还占用着内存,如果占用内存的话,又如何去释放??
It's kind of silly. 我并不是想讽刺问问题的朋友。其实如果你真的了解了上面这些知识点,就不会再问这种问题的。 为什么不多思考一层呢,在问这个问题之前想想,到底为什么会问出这个问题? ”如果让你给NSMutableArray实现一个replaceObjectAtIndex函数你会怎么写?“ 难道连个[obj release]都考虑不到么?然后根据ARC,它到底释放了没不言自明了把。
其实这种问题论坛里很多的。不妨在迷惑的时候,先问问自己为什么会迷惑。
(1)这里其实很有意思,为何我用“更高层次思考”,而不是“更底层次”。作为一个编译器和语言开发人员,面对的问题确实更底层没错,但是他们思考的维度更高,更抽象,这样子。一个不算恰当的比方就好像一个三维世界的人处理二维世界的一条线的问题。