Effective-Objective-C Summary
<Effective Objective-C> 总结
Item 1 Familiarize Yourself with Objective-C’s Roots
- 当你声明一个变量如 NSString *string = @“Hello world” 的时候 永远记住,在OC里 所有的对象( 有一些block 对象分配在栈??)都是分配在heap上的。 这个语句的作用是这样的 ,在stack 上有一个NSString型的指针 指向heap区的NSString object(即"Hello world")。这是很神奇的,对于比较熟悉c/cpp语言的程序员来说,因为我们知道任何在函数内部的局部变量 都是声明在stack上的,当程序执行离开函数的时候,stack会被释放,所有的局部变量都会失效。 而OC能够这么做的原因,是因为有ARC(Automatic Reference Count)也就是我们所常说的垃圾回收。
Item 2: Minimize Importing Headers in Headers
- 尽可能少在.h文件里面引用别的.h文件,这会给compiling 带来极大的不必要的负担,因为当别的文件需要include 这个头文件的时候 会递归地#import 在这个头文件里面的内容。 一个好的做法是 尽可能在.m文件里引入必要的.h文件 和使用forward declaration。
Item 4: Prefer Typed Constants to Preprocessor
- 尽可能用静态全局变量代替#define 来定义constant。这样做的好处是能够提供一定的type information,当被错误使用时 compiler会报错。并且声明在.m文件内的static const var 用k作为变量名前缀,如果需要被外界引用的const var则用class name 作为前缀
|
|
Item 5: Use Enumerations for States,Options, and Status Codes
- 继承于cpp11的 enum 语法,是一种很实用的技巧。
Item 6: Understand Properties
- Why not declare instance variable directly?
- 如果直接声明实例变量的话,对象布局就会在编译器形成。我们如果在runtime对对象布局进行更改的话,就会导致访问实例变量发生错误。
- 使用property的话,则会由class object 来持有offset,当runtime 添加新的实例变量的时候,就会更新class object持有的offset,因此我们就可以在runtime的时候动态添加新的property。
- property的内存处理关键词
- assign :setter 是简单的赋值操作,常被用于scalar value 例如CGFloat or NSInteger
- strong :简单理解就是 当遇到持有操作的时候就会让 refcount ++;
- weak : 简单理解就是 当遇到持有操作的时候 不会refcount ++; 但是一旦指向的对象被dealloc之后,它的值就会设置成nil。总所周知,向nil 发送消息并不会导致错误! 这是一个奇怪的设定
- unsafe_unretained : 类似于assign 不过针对的类型是object type。 和weak一样 持有操作不会refcount++, 和weak不一样的是 当指向的对象被dealloc之后,它的值仍然指向那个地址。这意味着访问一个错误的地址 ,会导致出错!
- copy : 和strong差不多,不过和strong不一样的地方在于当持有操作时,不是refcount ++,而是将整一个对象copy一遍, 你可以认为现在在堆里有两个一模一样的对象,一个是原来的对象持有,另一个被copy对象持有。
Item 7: Access Instance Variables Primarily Directly When Accessing Them Internally
- 作者建议当你需要读一个Instance Variable的时候 最好使用_Ivar的方法(由于不用走方法转发 会快一点),而当你需要设置Instance Variable的时候 使用property的方法去访问。
- 使用_Ivar的时候,不会触发内存管理,也就是说如果使用了copy关键字的话,也不会在heap上malloc 一个新的对象,而是在旧的对象上refcount++
- 使用_Ivar的时候,不会触发KVO通知
- 当使用lazy load的时候 只能使用property 来访问
- 在initial和dealloc的时候都通过_Ivar来访问
Item 8: Understand Object Equality
使用== 进行比较的时候,只会对pointer的值进行比较。
一般来说 为自己的class声明类似于isEqual方法是一种好的code style,(例如NSString的isEqualString 方法),可以减少type checking的开销(对比继承于NSObject的isEqual)
相等的object 一定返回相同的hash值,而hash值相同的object 不一定相等
set会根据object的hash value来确认该object是否在set里是唯一的。
当你实现对象的比较方法之后,最好override 父类的isEqual方法。
最好不要往set里面添加mutable 对象,不然会引发奇奇怪怪的bug
Item 9: Use the Class Cluster Pattern to Hide Implementation Detail
这就是常说的cluster pattern吧,就是对外只暴露基类的interface,然后根据你的需求创建不同的子类(一般来说是一个class method),是一种经常使用的设计模式。
Item 10: Use Associated Objects to Attach Custom Data to Existing Classes
Item 11: Understand the Role of objc_msgSend
不同于静态语言的函数调用,在Objective-C 里面我们称(function call )为,给一个对象发送一条消息(Message Passing)。在C语言中,函数的调用是在编译阶段就已经确定下来的,而在Objective-C中 ,给对象发送一条消息则是在runtime的时候决定的(Dynamic Binding,Objective-C无法在编译期就确定给哪个receiver 发送哪个message, 也就是调用哪个函数),因为receiver很有可能无法响应你的消息,可能是它的superclass 来响应该消息/ 这条消息也有可能被转发给别的object 去处理(message forwarding)。
1 2 3 4
// in cpp object.method(para1,para2); // in Objective-C [receiver message];
上述描述可能听起来很绕口, 但确实是这样的。详细版解释是这样的,[receiver message] 本质上是一个函数调用(编译器会将这条消息传递翻译成objc_msgSend函数调用) 其函数原型是objc_msgSend(id self, SEL _cmd , …) ,但是问题就在于这里的self 和_cmd (分别对应的是receiver和message )可能和这里的[receiver message]不是同一个,(self 可能是receiver的superclass or super superclass) 在runtime中消息传递和转发的目的就是为了找到IMP
Item 12: Understand Message Forwarding
更新于 2.28 重新看了一遍 runtime 疯人院入院指南 感觉颇深,主要是对整个runtime的整个流程有了更加深刻的认识。 毕竟只学了一个月的Objective-C,所以刚开始的认识还是比较局限的。
首先疯人院day01 主要是对class/object的内存布局 进行了详细的解释,为第二天的消息传递(Message passing)和消息转发(Message forwarding)做准备。
Item 13: Consider Method Swizzling to Debug Opaque Methods
Item 29: Understand Reference Count
Item29主要讲述了MRC时代的一些关于内存的东西,现在我们普遍使用ARC,所以无需过于关心MRC的各种细节,有一点要注意的是,我之前一直对retain count 不太能够理解retain的意思,阅读了之后觉得翻译成“持有计数”就比较合适了,类似于reference count翻译成“引用计数”,讲道理在我看来没有啥区别。
还有一点就是了解了autorelease pool,在autorelease pool内的所有object 通常都会在下一次的event loop 中被释放掉。
Item 30: Use ARC to Make Reference Counting Easier
所谓ARC只不过是编译器帮你在适当的时候插入retain &&release 所以并没有什么特别高大上的操作,不过值得关心的是编译器是怎么知道(什么时候是一个合适的时机?)以及具体编译器是怎么去做这件事情的。
首先编译器在静态分析的时候(static analyzer)能够检测到内存存在泄漏的可能,在这个前提下,其实静态分析可以更进一步,比如说通过添加合适的retain 和releases 语句来解决这个问题,于是乎ARC就这么诞生了。就如刚才所说 ,ARC帮你在合适的地方添加内存管理的语句,因为如果这件事交给人来做的话 就是MRC,(程序员都喜欢偷懒,也有很大可能出错,所以这件事交给编译器来做 就很合适)所以现在的问题就是,“如何让编译器在合适的时机合适的地方插入合适的内存管理语句,并且你能完全确信编译器能做好这件事情”。
实际上ARC并不会直接调用 retain/release/autorelease/dealloc ,而是通过更低级C接口来实现内存的管理,这样做的原因是因为这些语句是被高频调用的,如果消息传递的方式去做的话,会有性能上的overhead (因为要走runtime的那一套消息流程),而直接用C接口来实现则简单许多,比如 对应retain的c接口为 objc_retain这个函数。 并且ARC还会适当优化不必要的retain和release,当它发现它这样做不影响程序的正确运行的前提下。感兴趣的可以看看objc_retainAutoreleasedReturnValue
四种不同的qualifier
- __strong : Default , the value is retained.
- __unsafe_unretained : the value is not retained and potentially unsafe, as the object may have been deallocated already.
- __weak : the value is not retained but is safe. it would automatically set to nil if it had been deallocated.
- __autoreleasing : this qualifier is used when an object is passed by reference to a method.
ARC处理实例变量 :和普通的local var 不一样的是,ARC通过Objective-C++的一些feature(Deconstructor) 来对实例变量占用的内存来进行管理。简单一句话概括就是,ARC通过对对象的析构函数来对实例变量进行释放,而不需要有专门的dealloc函数。
ARC 只处理 Objective-C 的对象 对于非Objc对象(如CoreFunction的对象)内存管理是需要手动进行的。
Item 37 : Understand Blocks
- 之前写过关于block底层的一篇文章深入理解block底层 ,简单的分析了block在底层到底是长什么样子的。block经常被用于一些需要异步处理的场景(也就是我们所说的call back),从这个的角度来看block不过就是一个函数指针,但是不同于函数指针在别的语言(c/c++)的实现,block有能力capture 他使用的context(这里的context指的是其传进去的参数和使用到的variable),而Objective-C 不同于c/cpp的地方在于其内存管理,所以导致block在capture context的时候,会对这些被captured的对象进行retain操作(refcount++),这就很容易导致了循环引用,造成内存泄漏。 还有一点就是block的语法比较怪异,需要一段时间去适应
|
|
- block可被用于遍历数组
|
|
关于Block的内存布局 – (Need Update)
Item 38: Create typedefs for Common Block Types
其实对于很多刚学习Obective-C的新人来说,Block的语法都感觉让人看起来很是怪异,如
|
|
https://stackoverflow.com/questions/4295432/typedef-function-pointer 讲了如何typedef 一个函数指针
https://stackoverflow.com/questions/25716232/compare-blocks-and-functions-in-objective-c 讲了 block和函数指针的区别
Item 39 : Use Handler Blocks to Reduce Code Separation
Item 48 : Prefer Block Enumeration to for Loops
Item 49 : Use Toll-Free Bridging for collections with Custom Memory-Management Semantics
Item 50 : Use NSCache Instead of NSDictionary for Caches
Item 51 : Keep initialize and load Implementations Lean
Item 52: Remember that NSTimer Retains Its Target
血泪教训