Effective Objective-C 2.0总结

1. Objective-C是门消息型语言,消息型语言运行时所应执行的代码由运行环境来决定;而使用函数调用的语言则由编译器决定。

2. 尽量使用向前声明(@class),将引入头文件的时机延后。这样可以减少编译时间,降低文件彼此依赖程度。

3. 多用字面量,提高代码可读性。

4. 多用类型常量,少用宏定义。(宏定义没有类型信息)

实现文件内部使用命名:

static const NSTimeInterval kAnimationDuration = 0.3;

全局使用命名:

extern NSString *const EOCLoginManagerDidLoginNotification;

NSString *const EOCLoginManagerDidLoginNotification = "EOCLoginManagerDidLoginNotification";

5. 使用枚举(NS_ENUM,NS_OPTIONS)表示状态,选项,状态码。

枚举switch不要实现default,这样加入新枚举值会提示开发者未处理所有枚举。

6. 属性。

@property (nonamic, getter=isOn) BOOL on;

7. 内部使用属性时,建议set使用属性方法,get使用实例变量(除非get被重写实现懒加载)。

dealloc内读写都使用实例变量

8. hash和isEqual:方法,参考:https://www.jianshu.com/p/306a68df4f7f

NSSet中的元素最好是不可变的。 

9.当抽象基类有众多实体子类不好管理时, 可以用类簇的模式在抽象基类中增加一个快速创建实体子类的工厂方法,可以用枚举区分不同实体子类类型。

Foundation中很多容器类自定义子类会导致crash,因为容器基类只是个抽象类,定义了公共接口,但存储数据的变量和一些方法必须要在子类中实现或覆盖。

抽象基类需要有使用文档,比如表明实体子类中哪些方法必须重写。

10. 灵活使用runtime关联对象。

11. [obj method:param] 本质是 objc_msgSend(obj, @selector(method),param)

[super method:param] 本质是 objc_msgSendSuper(objc_super, @selector(method),param)

12. 消息转发三步骤:动态方法解析->备援接收者->方法签名

13. runtime方法交换(hook, pod引入的第三方库修改)

14. 类对象,元类对象

15. 使用前缀代替命名空间,比如公司通用代码使用公司缩写,项目内部代码使用项目前缀。

使用三字母前缀,避免与Apple重叠。

第三方类的分类,分类名和方法名都需要加上前缀。

16. 在类中提供一个全能初始化方法,其它初始化方法调用它。

超类初始化方法不能用于子类,则在子类只重写并抛出异常。

17. debugDescription默认调用description,调试器po输出debugDescription。

description一般放普通描述,debugDescription更多详细信息。

18. 尽量使用不可变对象。

若属性仅内不可修改,设置为readonly,并在实现文件中改成readwrite。

类可变容器属性不要对外公开,提供方法修改。

19. 方法名要言简意赅,读起来像个句子。

20. 私有方法使用p_开头,_开头是苹果使用,防止重叠。

21. 使用NSError封装错误信息。

22. 容器元素的深拷贝需要自己实现方法,普通对象可通过NSCopying/NSMutableCopying

23. 分类的实现基于ObjC的动态性,其他静态语言不支持。

协议protocol 主要两个功能:1. 实现类似多重继承(公开支持的协议)2. 实现代理模式(一般不需公开支持的协议)

代理模式(委托模式)delegate,通过参数和方法触发传递信息给代理者。

数据源模式 dataSource,通过返回值从数据源获取数据。

某类支持的代理协议方法频繁调用,可通过位域优化responseToSelector。

24. 使用分类将关联性较差的方法分组到不同分类,私有分类名称Private。

比如某个类针对新业务功能需要提供一套新方法,原有类中不依赖这套新方法,可以给这个分类取名为业务功能名称。

25. 第三方类的分类,分类名称和方法名都加前缀。

26. 分类不要定义属性。(配合使用关联对象除外)

27. 使用扩展(匿名分类)实现实例变量,私有方法,不对外公开的协议,打开只读属性。

28. 匿名对象,id,支持协议的对象,不需要关心类型,只关心是否支持协议(响应协议方法,optional需要先判断responseToSelector)。

29. 理解引用计数,循环引用,引用计数为0释放。

30. 使用ARC管理内存,ARC自动补充内存管理代码。

ARC只管理ObjC对象,CF,c语言malloc等不管理。

31. dealloc中解除监听,释放CF,c等内存空间。

文件描述符,套接字,大块内存尽量不要放在对象的dealloc中,因为对象可能未释放(可能代码问题导致),将会导致文件描述符,套接字,大块内存长期占用内存。

dealloc尽量不要执行其它方法操作,因为进入dealloc方法代表对象已经在回收中。

32. fobjc-arc-exceptions

33. weak解决xunhuanyinyong

34. 使用自动释放池降低内存峰值

35. 使用僵尸对象调试内存管理问题。

开启僵尸对象后,对象引用计数降为0时,会判断是否开启僵尸对象,如果开启,会使用交换方法交换dealloc方法,在新方法内,系统会基于旧类动态创建一个新类 __NSZombie_XX (XX指旧类名)(使用objc_dupliateClass())。新类是个基类,没有父类,也没有方法实现,只有一个isa变量。同时将将实例对象的isa指向这个新的类对象。僵尸类收到所有消息都进入消息转发。在消息转发过程中,会判断类名是否包含__NSZombie_,如包括则输出错误详细信息,并终止app。

36. 不要使用retainCount

37. Clang早期版本就支持block,block在c/c++/objective-c中都可以使用。

block可以让代码像对象一样传递,另一方面,block可以捕获定义block处能访问的全部变量。

block也是个对象,一样有isa,类对象,引用计数。

block默认捕获变量是值传递,加上__block变成地址传递。

block捕获非self下的对象类型,会强引用它,并在block释放时候一同释放它。

block对self变量,是通过捕获self间接引用变量(不一定是self.语法,所有归属self对象的成员变量,属性,方法调用都是通过捕获self间接引用)。捕获self容易造成循环引用,在arc下,捕获self时也会捕获self的修饰符(strong或weak)。

block和函数很像,事实上,block本质就是编译时期,编译器将块内的代码转换成了函数,并在此基础对其封装成对象,用invoke指针指向函数地址,必将函数要使用的参数和块信息包装成块对象的成员。

堆block,栈block,全局block。在arc下将栈block赋值给强引用指针会触发栈block copy到堆block。(本书出品时还需要手动copy操作)

38. 使用typedef重新定义块类型。

39. 使用回调block作为方法参数代替代理的好处:1. 可以让业务与回调一并声明。2. 降低代码分散程度 3. 不需要根据传入的对象判断切换 4. 可以把回调时所在queue作为方法参数。

如果有成功数据和失败NSError两种返回情况,尽量在一个block中实现,而不是分散到两个。因为出现NSError可能也会用到成功的数据,或者成功的数据也可能认为是失败,用一个block实现更方面。

40. 避免循环引用

41. 使用gcd队列实现读写锁效果,读或写任务如果耗时,可以异步进行,避免阻塞主线程。

42. performSelector系列方法局限性:1. 不了解方法签名和返回值类型,ARC因此不操作,若调用copy,则返回值不会被释放。2. 限制返回值为id或void,参数为对象,且限制参数个数

使用gcd代替performSelector的线程或延迟操作。

43. NSOperationQueue相比gcd好处:1. NSOperationQueue可以取消未启动的任务(gcd需要开发者自己在应用程序层实现)2. 可以直接指定操作间的依赖关系 3. 可以使用KVO观察任务状态 4. 指定操作的优先级(gcd只能指定队列)5.面向对象,NSOperation子类对象可重用。6. NSOperationQueue可直接设置最大并发数,gcd需要通过信号量实现

NSOperationQueue适合大型或复杂的多线程任务。

44. gcd任务分组两种方式:1. dispatch_group_async(常用) 2. dispatch_group_enter + dispatch_group_leave 2. 等待组任务完毕两种方式:1. dispatch_group_wait(可设置超时,会阻塞当前线程)2. dispatch_group_notify(常用)

组内任务可以存在于不同队列,也可放在多个串行队列。

dispatch_apply 迭代执行任务,可以放在并发队列中并发执行(类似NSArray的NSEnumerationConcurrent),会阻塞当前线程,因此指定队列不能是当前队列,否则死锁。

45. 实现单例使用dispatch_once而不是@synchronized。

dispatch_once是线程安全的,内部使用更底层的原子访问(原子操作一旦开始,就一直运行到结束,中间不会切换到另一个线程,而锁竞争会导致线程上下文切换),性能比锁更加高效。

46. dispatch_get_current_queue容易导致死锁被弃用。

dispatch_queue_set_specific可以把任意数据以键值对的形式关联到队列中。(类似runtime关联对象,线程本地存储TLS)

dispatch_queue_set_specific/dispatch_queue_get_specific可用于判断队列是否是指定队列,比如主队列。

47. 编译四个过程:预处理,编译,汇编,链接。

预处理:宏替换,导入头文件,条件编译,处理一些特殊的预处理关键字

编译:翻译成.s文件汇编语言

汇编:翻译成.o机器语言

链接:将众多的.o合成一个完整的可执行文件,包括静态库.o文件此时一起合并。

程序在运行时由系统动态加载动态链接库到内存中供程序调用。使用动态库系统只需载入一次,不同的程序可以得到内存中相同的动态库,因此节省了很多内存,而且使用动态库也便于模块化管理。

iOS中的动态链接库都是系统框架,不包含在ipa文件里,在iOS设备中。第三方框架都是静态库,链接时合并到可执行文件中。

Cocoa/Cocoa Touch 分别是Mac OS/ iOS中带图形界面的应用程序开发框架集合。

Foundation(ObjC)和Core Foundation 可通过toll-free bridging无缝桥接。

某些数据类型能够在Core Foundation和Foundation之间互换使用,可被互换使用的数据类型被称为Toll-Free Bridged类型。这意味着同一数据类型即可以作为Core Foundation函数的参数,也可作为接收者向其发送Objective-C消息。Core Foundation与Foundation之间交换使用数据类型的技术被称为Toll-Free Bridging 。

但不是所有的数据类型都可以Toll-Free Bridging,比如说NSRunLoop与CFRunLoop,NSBundle与CFBundle,NSDateFormatter与CFDateFormatter都不可以相互转换。

48. 作者推荐使用block枚举法,因为简单方便。但是对于NSArray,for in枚举效率最高。如果枚举后操作耗时,可以考虑并发枚举法;如果枚举后操作简单,线程管理开销则会相对比较大,就使用并发枚举法。

49. CoreFoundation和Foundation中的collection可以无缝桥接(Toll-free bridging)。

NSRunLoop是对CFRunloopRef的封装,但不能进行无缝桥接。

__bridge_retained等同于retain引用计数+1,__bridge_transfer等同于release引用计数-1,__bridge不发生引用计数变化。

CFXX *cfXX = ( __bridge_retained CFXX)nsXX; 代表ARC交出nsXX的对象所有权,需要cfXX调用CFRelease管理内存释放。

nsXX创建时引用计数+1,__bridge_retained+1,CFRelease-1,ARC自动释放nsXX-1。

NSXX *nsXX = ( __bridge_transfer NSXX)cfXX; 代表cFXX将对象所有权交给ARC管理,不需要手动管理内存释放。

cFXX创建时引用计数+1,__bridge_transfer-1,ARC下赋值给强指针nsXX+1,当nsXX销毁-1。

__bridge代表不改变对象所有权。

CFXX *cfXX = ( __bridge CFXX)nsXX; 代表ARC仍然具有nsXX的所有权,不需要手动管理内存释放。

nsXX创建时引用计数+1,当nsXX销毁-1。

NSXX *nsXX = ( __bridge NSXX)cfXX; 代表cFXX仍然具有对象所有权,需要cfXX调用CFRelease管理内存释放。

cFXX创建时引用计数+1,CFRelease-1。

NSDictionary只能对key进行copy,对value进行retain,但是CFDictionary更灵活,可以配置key和value的各种不同处理方式。如果准备使用NSDictionary,但成为Key的对象不支持NSCopying,那么可以使用CFDictionary来进行配置,然后通过__bridge将其转换成NSDictionary。同样,对于其它collection,无缝桥接可以使ObjC collection具备特殊内存管理语义。

50. 构建缓存使用NSCache代替NSDictionary

1. NSCache能在更深层次加入挂钩,监听资源耗尽并按照最久未使用来删减缓存。

2. NSCache对Key是retain,NSDictionary对Key是copy。

3. NSCache是线程安全的。

4. NSCache可以通过设置countLimit和totalCostLimit来控制删减内容的时机。(只是起指导作用)

5. NSCache可以搭配NSPurgeableData使用,NSPurgeableData可以根据需要随时丢弃,也就是说,当系统资源紧张时,可以将NSPurgeableData对象那块内存释放掉。

51. 作者认为在load方法内调用其它类是不安全的,因为其它类可能还未加载。不知道作者笔误还是iOS早期版本不同,现在的iOS版本中,所有非懒加载类的结构初始化完成后,才会去触发类的load方法。

load调用会递归类继承树并在每个类中查找是否存在load方法,找到则通过IMP直接调用,若未找到则无法调用。这样一个类的load只会被调用一次。

initialize调用是消息发送,递归类继承树并对每个类发送消息,这样一些类未实现initialize则会交给父类,可能会导致一个类的initialize被多次调用。

load方法会阻塞程序初始化加载,尽量精简。

initialize是线程安全的。

为了防止load与initialize不同类间初始化互相依赖,导致依赖环,一般load与initialize只用来设置内部数据,比如一些不能在编译时初始化的全局状态(全局OC对象分配在堆区,必须在运行时初始化)。即使调用方法也可能不安全,因为这些方法未来添加的代码可能会调用其它类。

initialize+dispatch_once或者initialize+self类判断 避免多次调用。

52. NSTimer导致循环引用

解决方案:https://www.jianshu.com/p/a3eeddbe04b5

你可能感兴趣的:(Effective Objective-C 2.0总结)