首页 > 代码库 > Effective Objective-C 2.0重读笔记---1

Effective Objective-C 2.0重读笔记---1

上次看这本书的时候匆匆走了一遍,最近不太忙,重温了一遍,把笔记写出来~..

有兴趣的可以去买一本,感觉这本书还是挺不错的

由于大部分是在坐车的时候用手机写的,所以代码很少,图也很少

 

1. 尽量使用向前声明,延后引入头文件的时间,这样可以减少编译时间
2. 使用arraywithobjects:....如果遇到为空的变量,就会自动终止,后面的变量便会添加不上,切不会报错,会造成数据不一致问题,所以尽量使用字面量语法创建相关对象,减少出错且减少代码量
3. 使用字面量创建的对象都是不可变的,如果要获得可变对象,需要调用mutableCopy方法
4. static声明的变量(imple块和interf块中间)只会在本编译单元内可见。如果不加static,编译器便会为其创建一个外部符号,此时如果另一个编译单元里也声明了同名变量则会抛出异常信息
5. 定义常量时应该少用预处理指令,多用static const,这样的常量带有类型信息,可以减少出错机会。另外如果一个变量声明为static const编译器不会为其创建符号,而是直接替换
6 .如果要让外部使用你定义的常值变量,就要用在头文件声明extern nsstring *const variable          在实现文件里写nsstring const variable = @"kkkkk",这样外部就能访问这个变量,另外这种供全局访问的变量命名时最好使用该类类名开头,避免有重复声明的冲突
7. 使用枚举时,如果枚举不需要组合,应该是用NS_ENUM来定义枚举,这种形式的枚举编译时会自动添加相关的判断宏定义,使其在新环境中使用新语法,旧环境使用旧语法
8 .如果要使用组合枚举,最好使用NS_OPTIONS来定义。因为C++中,枚举运算后所得变量应为其底层数据,不应该是枚举型变量,所以要转换,使用上述方法可以自动判断是否需要实现显示转换 
 9. C++访问成员变量是是利用在编译期确定的偏移量去访问的,如果增加了一个变量,位于其后的变量将会访问出错,因为偏移量已经确定了,所以增加后会出错。OC的做法是将偏移量存在类对象里面,访问的时候回去累对象里查询,所以此时如果再新增加变量总是能正确访问。见书22页(ABI和API)
10. @synthesize方法命 = 变量名 在编译期间就会自动生成get和set方法@dynamic声明的变量系统不会为其创建实例变量和存取方法,而且在编译器访问属性代码时编译器即使没找到存入方法也不会报错,因为他相信在运行期可以找到,CoreData.就用到了这个技术,因为他的属性是来自数据库的不是实例变量
11 .属性后面的小括号里写的是属性的特质,属性的特质分为四类①原子性②读写权限③内存管理语义④方法名
12. 内存管理语义weak  strong  assign   copy    unsafe_unretained(该语义不会在其指向的对象销毁时会自动清空,weak是会的)
13. 在写带参数的初始化方法时,如果直接给属性赋值要注意赋值时内存管理语义,定义为copy的,记得要cooy 。
14. 在类对象内部直接访问实例变量的速度比用点语法访问快,因为直接访问不经过方法派发。直接访问不会调用存取方法,如果在存取方法里做了处理就要注意了并且直接访问不会调用内存管理语义。直接访问变量不会触发KVO,通过属性访问有助于排查错误。
两种访问机制各有其优缺点,折中的方法是获取时直接访问,设置时调用方法(保证内存管理语义)
15. 如果获取方法中写了懒加载,那么访问变量时应该用点语法访问,在初始化方法中应该总是用变量直接访问。因为,,这个不解
16. 抛出异常NSException raise: format:
17. 判断同等性如果是同一个类可以调用isEqual:如果是比较字符串同等性应该用起特定的方法以减少性能消耗。数组和字典也有自己的等同性比较方法
18. 如果要给自己定义的类写等同性判断方法,也应该复写isEqual方法,判断是否为本类,如果是本类则调用自己实现的判断方法,如果不是则交由父类判断
如果对象有自己的特定id则直接判断id即可。
19 .类族可以隐藏抽象基类背后的实现细节,UIButton就是其中之一。类族定义时一般需要提供枚举的子类和创建子类的类方法
20. 判断类族时应该用isKindOfClass:如果比较的是确定类实例应该用isMemberOfClass
21. oc可以让一个对象动态关联其他对象。objc_setAssociatedObject()函数可以给对象设置关联对象。objc_getAssociatedObjects()函数可以根据给定的键将之前存入的关键对象取出来。objc_removeAssociatedObject()此方法可以移除指定的关联对象
但是关联对象不宜滥用,因为很有可能引入保留环,而保留环不易调试解决问题
22. OC中的方法最终都会转换为底层的objc_msgSend()方法实现。选择子和参数合起来称为消息,objc_msgSend()能接受两个或以上的参数。objc_msgSend() 会依据接受者与选择子的类型调用适当的方法,该方法会在接收者所属的类中搜寻其方法列表,如果找到就执行,如果没找到就沿着类的继承体系向上查找,等找到这个方法之后再跳转,如果没有一直找到,就会执行消息转发操作。执行一个方法似乎要很多步骤,但是首次执行完一个方法后objc_msgSend()会将匹配结果缓存在快速映射表里,但是尽管如此还是不是静态函数绑定操作那样迅速
23. objc_msgSend_stret()用于处理返回量为结构体的方法,这个函数只能处理能被cpu寄存器容纳的结构体,如果结构体太大则会用另一个函数调用
objc_msgSend_fpret()处理返回数据为浮点型的方法。objc_msgSendSuper()用于给超类发送消息项
尾调用优化技术可以对消息机制进优化,当且仅当一个消息的最后一行调用了另一个函数切不将其返回值另做他用。
24 .消息转发分为两个阶段,首先会征询接受者看其是否能动态添加方法来处理这个未知的选择子。第一阶段响应完毕,此时运行期系统会请求接受者看看有没有其他对象可以处理这条消息,如果可以则交由那个对象处理,如果不行则会启动完整的消息转发机制,这时运行期系统会把消息相关细节封装到NSInvocation对象中,再次给接受者一个机会令其设法解决当前这条消息

 

 

25. 对象收到无法解读的消息后,首先调用下面的方法
-(BOOL)resolveInstanceMethod:(SEL)selector
如果寻找的是一个类方法,找不到会调用-(BOOL)resolveClassMethod:(SEL)selector
如果上面的方法返回假,则代表类不会新增方法来处理上面的消息则会进去下一步
如果上面的方法返回NO则代表本类不会新增方法来处理这个消息,下一步调用
-(id)forwardingTargetForSelector:(SEL)selector
这个返回值代表将会把消息派送给其进行处理,利用这个特性可以模拟多重继承的一些特征,在类内部指定执行的对象,外界看来好像是该对象亲自处理其实是他的内部对象处理
如果上面返回nil代表该对象内部没有可以处理改消息的对象,运行时系统此时就会调用完整的消息转发执行
-(void)forwardInvocation:(NSInvocation *)invocation
这个也是指定接受者来执行这个方法,实现此方法时若发现调用操作不应由本类处理,则需要调用超类的同名方法如果追溯到NSObject类还是处理不了此方法,则会调用doesNotRecognizeSelector:抛出异常
p49有例子
class_addMethod()可以动态添加方法

26. 当执行方法时,系统会到该类的方法列表上去查找该方法,这些方法均以IMP指针形式来表示例如

修改方法名和函数的映射表即可实现方法的互换
method_exchangeImplementations()这个方法可以互换两个函数
Method m1 = class_getInstanceMethod(即可获取相应的方法。
将自定方法跟系统定义的方法互换以后即可增加黑盒日志功能,在自定义的方法里做一些事情,然后在调用自定义的方法,看似会产生死循环,但需要注意的是运行期他们指向互换,调用的时候其实是调用原方法

 
26. 每一个类都有一个isa指针,Class类也是也是OC类的对象,类对象的isa指针指向一个元类用来表述类对象本身所具备的元数据,类方法就定义与此



 

27.因为OC没有命名空间,所以给类命名时容易发生冲突,一般都用前缀解决这个问题,Apple宣称其保留两字母前缀的使用权利,一般其提供的框架都是两个字母的前缀,所以如果你的项目中使用了两个字母前缀的命名方式,有可能会跟框架产生冲突
此外C语言函数和全局变量那些顶级符号都要写上前缀以免冲突
28. 一般对象的初始化都要求外界传入一部分数据,(UITableViewCell).所以一般我们会提供一个全能初始化方法,让其他的初始化方法来调用这个初始化方法.数据相关的存储在全能化初始化方法中执行,这样的话如果要改变存储机制,只需要改变该方法即可.这样便于程序的控制,如果要强制使用自己定义的全能初始化方法可以再init等不可控制的方法中写上抛异常
@throw [NSException exceptionWithName:reason:userInfo:]
也可以在init方法中写上默认值
另外如果有继承体系,一定要维系全能初始化方法的调用链条,这样才能保持程序的健壮性.
此外如果父类的全能初始化方法不适用于子类(矩形和正方形),应该复写该方法,并在其中抛出异常
29. 如果需要经常查看一个类的相关信息,我们需要复写该类的description方法
[NSString stringWithFormat:@“%@:%p…..”,[]]
如果经常要在调试框中查看对象的相关信息,则需要覆写debugDescription方法
此外NSArray和NSDictionary等类打印的时候不是调用description方法而是调用-(NSString *)descriptionWithLocale:(id)locale方法
 
30. 尽量创建不可变的对象. 若某属性仅可用于内部修改,则在类扩展中将其readonly属性改为readWrite. 不要把可变的collection作为属性公开,而应该提供相关方法,以此修改对象中可变的collection
 
31.给类内部使用的私有方法加上前缀字母+_,不要直接使用下划线,容易跟苹果提供的冲突. 给私有方法加前缀可以很明显看到区别并且察觉共外界使用的方法一般不要轻易改动.
 
32.OC默认不是异常安全的,如果想使用异常安全代码需要打开编译器标志-fobjc-arc-exceptions
即使不使用.....OC现在的做法是只有在极其罕见的情况下才抛出异常,抛出异常后无需考虑恢复问题,而应用程序此时也应该退出,异常属于极其严重的错误,比如编写了一个抽象类,它的正确使用方法应该是从中集成一个子类,然后使用这个子类,对于默写必须覆写的方法,可以再父类里抛出异常,这样子类就必须覆写,否则不能执行
对于非致命错误,可以通过返回nil/0来处理或者使用NSError,如果初始化方法无法根据传入的参数来初始化当前实例,那么就可以令其返回nil
33. 若想让自定义的对象具有拷贝功能,则需要实现NSCoying协议. 如果自定的对象分为可变版本和不可变版本,需要同时实现NSCopying和NSMutableCopying协议,一般情况下执行的都是浅拷贝,深拷贝需要一些设置或者自己定义(也可以用Runtime实现深拷贝)
 
34. OC不支持多继承,但是支持协议(类似于JAVA中的接口),对象之间经常需要通信,OC中对象之间通信模式之一就是委托模式,数据源(dataSource)和代理(delegate)可以分别处理数据和事件处理,这样可以达到很好的解耦效果.,代理协议的命名方法通常是类名+Delegate,其方法名一般是去除前缀的类名+相关操作命名.
本类的代理成员变量一般声明为weak,否则容易产生保留环.(例如tableView,如果tableView的delegate是Strong,他就会保留拥有它的控制器,而控制器也拥有tableview的强引用,这样就产生了保留环)
 
35.代理协议中的方法有时不会要求代理者全部实现,只实现一部分即可,在方法声明上加上@optional关键字即可标注为可选实现,加上@required标注为必须实现,一般在调用可选方法时我们需要查询一下代理对象是否已经实现了要调用的方法(用responseToSelector).
 
36. 如果一个可选协议方法需要经常调用,可用”位段(bitfield)"来标识代理者是否已经是实现了相关方法,因为查询方法是否实现是个比较费时的操作,如果执行较为频繁,用标志位来保存可以节省性能开销.
struct data{
unsigned int filedA : 8;
unsigned int filedB : 4;
unsigned int filedC : 2;
unsigned int filedD : 1;
}
结构体中fieldA占8个二进制位,可以表示0~255之间的值,而fieldD可表示0-1
Struct{
unsigned int didReceiveData : 1;
unsigned int didFailedWithEorror : 1;
unsigned int didUpdateProgressTo : 1;
} _delegateFlags;
然后覆写setDelegate:方法,设置代理的时候就去查询一遍看看代理对象有没有实现可选方法,同时设置标志位,后续直接按照标志位来看是否可以调用相关方法
 
37. 使用分类机制可以把类的实现代码划分成易于管理的小块,并且调试的时候会打印出分类信息,易于调试.如果一些方法不需要公开,可以写在Private分类里面,然后不再头文件中公开(静态库)
 
38. 如果两个分类中出现了同名方法,分类是不会报错的,会根据加载顺序以最后一个加载的为准,覆盖掉之前加载的方法.所以写方法时最好也加上前缀,这样会大大减少分类方法的覆盖,…此外分类方法的优先级比直接写在类里面的优先级高,如果你在分类里面写了跟类里面同样的方法,分类里的方法会覆盖类里面的方法
 
39.一般不要再分类中添加属性,虽然可以使用关联对象的方法实现分类中属性的存取,但是这种事不安全的,有可能丢失内存管理语义.有时候如果分类中的属性是独立的,不跟本类里面的数据交互是可以定义的.但是即使是这样页最好不要使用分类属性.
 
40.class-continuation是一种特殊的分类,也叫类扩展,这种分类没有自己的名字,直接写在类的实现文件里面,写在这里的属性等是不会对外公开的,可以用来声明或存储私有的变量方法等(其实OC里面没有真正的私有方法,任何变量和方法都可以用runtime那一套东西来访问)
 
41.如果使用OC类里面使用OC++代码,如果引入了这种头文件,则该文件也要按C++方式编译(后缀为.mm)所以使用分类可以不再.h中引入c++的头文件,这样的话暴漏给外界的就是一套OC的接口,隐藏了实现细节.WebKit和CoreAnimation等框架均用到了此种方法
类扩展的另一种用法就是将头文件中声明为readonly的属性改为readWrite,这样在类的内部,这种属性是可读写的,类的外部是只读的
想遵循私有或者不需要给外界知道的协议可以再类扩展中遵守
 
42.使用ARC,系统可以自动检测生成代码时成对的retain的release操作,如果有多余的会自动取消.使用ARC调用的是objc_autoreleaseReturnValue方法,该方法可以检测返回对象后即将要执行的一段代码,如果跟刚才执行的有成对的操作,则会设置一个全局数据结构的一个标志位(数据结构根处理器有关)而不执行release操作,objc_retainAutoreleasedReturnValue,检测标志位要比执行retain和release快很多,所以最好使用ARC
 
43. 如果以alloc ,new ,copy ,mutableCopy开头返回对象的,系统不会做什么操作,若是以其他形式开头的,系统会自动在返回的对象上调用autoRelease
 
44.dealloc方法里应该做的事情就是释放指向其他对象的引用,并取消原来订阅的键值观测和通知,一般不要做其他事情,因为此时对象的生命已经接近尾声
如果对象持有的是文件描述符等系统稀缺资源,那么应该编写一个专门的方法来释放这种资源,这样的类要与其使用者约定,用完资源后一定要调用close方法.可以给类添加一个标志位属性,如果已经执行了close方法则将标志位置成1,然后再dealloc里面判断标志位是否为1,如果不为1应该跑出异常
执行异步任务不应该在dealloc里面调用,只能在正常状态下执行的哪些方法也不应该在dealloc里面调用
 
45.捕获异常时一定要把try里面所创建的对象清理干净.默认情况下ARC不生成异常处理所需要的清理代码,开启编译器标志以后可以,但是这会导致应用程序变大,降低运行效率
 
46.合理利用自动释放池可以降低程序的内存峰值
 
47.编写程序时最好打开僵尸对象调试功能,这样尅帮主们快速定位崩溃所在,如果系统发现有_NSZombie——开头的实例发送消息,就会中断
 
48. Class cla = object_getClass(p); object_getClass()可以获取类的相关信息

    Class superCla = class_getSuperclass(cla); class_getSuperclass()可以获取一个类的父类信息;

    const char *name  = class_getName(cla);    class_getName(cla)可以获得一个类的类名

    Class baseZombi = objc_lookUpClass([@"_NSZombie_" UTF8String]); objc_lookUpClass可以查找

当前系统有没有指定的类

    zob = objc_duplicateClass(baseZombi, [@"_Zombie_Person"  UTF8String], 0);

objc_duplicateClass可以动态添加类,以指定的模板添加类

    objc_destructInstance(p); objc_destructInstance可以销毁一个实例;

 

abort();可以中断应用程序的执行

49. 应该尽最大的努力避免使用retainCount,因为你不知道他外面还有多少个自动释放池,以及系统还会为这个对象执行多少次release操作
 
49 block块会保留块内使用的对象(不管是方法还是变量),如果块实在对象内部,并且块内使用了类的成员变量不管有没有使用self。块都会保留self、,因为就算使用_XX这样直接访问成员变量其实是通过self->XX访问的
 
50. Block其实也是OC类,只不过他是一种特殊的类,Block类分为三种,分为堆Block栈Block和全局Block
Block创建的时候默认是栈上的,通过copy函数等会拷贝到堆上,MRC下Block对象也是需要手动管理的

 Block_copy(<#...#>) Block_release(<#...#>)分别执行拷贝和释放操作;

全局块是不访问外界变量的块,这样的块执行copy操作是空操作,因为他一成不变,没必要拷贝(可以优惠程序执行)

 

51. 如果必须产生保留环则应该在功能执行完毕时通过XX = nil等方法将某一方的引用置为空,解除保留环。很多网络框架都是先保留后解除