Effective Objective-C在每一个Item之后的Things to remember的翻译,以及一些重要的知识点笔记, 这本书讲述了很多关于Objc的语言特性, 只可惜今后Swift要替代OC了, 发上来算是纪念吧.
Item 1:
1.OC是C的超集,增加了面向对象的特性.OC用动态绑定来使用消息传递机制,这意味着一个对象的类型是在运行时确定的. 一个消息传递所执行的代码在运行时确定而不是编译时.
2.理解C的原理有助于编写高效的OC代码,值得一提的是你需要理解内存模型和指针.
Item2:
- 在尽可能深的节点中导入头文件, 这通常意味着传递声明类的头文件和其实现文件导入的头文件.这样做可以极可能避免连接2个类.
- 有时候不可能传递声明, 例如声明一个协议. 在这种情况中, 应该尽可能考虑把协议的声明移到类的category中. 相反地, 导入一个只有协议定义的头文件.
Item3:
- 使用标记语法来创建字符串,数字对象,数组和字典. 这样比使用常规的对象构造方法要更简单明了.
- 使用下标方法来访问数组或字典对象.
- 使用标记语法插入nil对象会抛出异常,因此,时刻确保插入值不为nil
Item4:
- 避免预处理器定义(#define). 因为它们不包含任何类型信息并且仅仅是在编译前做简单替换. 它们可能被重新定义而没有任何警告(目前是有的, 可能以前的编译器版本不支持), 从而在应用程序中产生一个不一样的值.
- 特定的编译单元常量在类实现文件(.m)中用static const来定义. 这些常量不会暴露在全局符号表中, 所以它们的名字不需要在命名空间中.
- 在头文件中声明全局常量, 在实现文件中定义它们. 这些常量将会出现在符号表中, 所以它们的名字将会在命名空间中, 通常为它们加上相对应类名的前缀.
Item5:
- 使用枚举来给状态或者状态机命名, 或给方法传递状态值, 或标识出错误码
- 使用枚举来定义可以同时使用多个的状态值, 将它们定义为2的幂, 以使位的或运算来整合它们
- 使用宏NS_ENUM或NS_OPTIONS来定义有显式类型的枚举. 这样做可以保证是自己选择的类型而不是编译器选的类型.
- 使用枚举类型就不要在switch语句中实现default case. 这将有助于你增加枚举类型, 因为编译器会警告你没有处理所有选项值
Item6:
- @property语法提供了对象数据的封装形式
- 使用属性来为数据存储提供正确的语义
- 确保@property后面的属性都已设置好
- 在iOS上使用 nonatomic, 因为如果使用atomic的话会极度地影响性能(编译器会为其加锁)
Item7:
直接访问成员变量和用点语法访问的区别:
a. 直接访问会更快
b. 属性为copy的变量,直接访问不会copy, 仅仅是retain一个对象
c. KVO在直接访问时不会被触发
d. 通过属性访问变量在调试时会更容易发现问题, 因为你可以在getter和setter函数中加断点
- 通过直接访问来读, 通过点语法来写.[在init方法中,要一直使用直接访问]
- 在init方法和dealloc方法中, 变量的读写均用直接访问
- 有时候需要数据延迟初始化, 就需要通过点语法访问数据
Item8:
- 如果需要比较对象相等就实现isEqual和hash方法
- 相等的对象一定有相同的hash值, 但是有相同hash值的对象不一定要相等
- 对测试相等性而言, 要决定什么是必要的, 而不是直接测试每一项属性相等
- 编写hash方法可以很快比较, 但是也可能会造成hash冲突
Item9:
- 类簇模式可以把实现细节隐藏在简单的公共表象下
- 类簇广泛应用于系统框架
- 继承类簇的公共抽象类要注意, 如果有文档, 仔细阅读之.
Item10:
- objc associated 提供了一种链接2个对象的方法
- objc associated 定义了一套模拟持有和非持有关系的内存管理语法
- objc associated 最好在万不得已的情况下使用, 因为他们出现bug很难被发现
Item11:
- 消息由接收者, 方法名和参数构成, 发送消息与调用一个对象的方法是一个意思
- 当消息需要发送时,所有的消息都会经过动态消息分配系统,在这里找到对应实现并执行
Item12:
要点:
@dynamic变量意味着,这些变量的getter和setter函数不会由编译器生成,而是自己手动完成,常见于coredata.
- 消息转发是一个对象无法找到对应的方法时所经过的过程
- 将一个方法在运行时加入一个类中称之为动态方法方案
- 对象可以声明自己无法处理的函数, 但是其它对象可以处理
- 全部转发只有在前面所有的方法都没能找到合适的函数时才被触发
Item13:
交换2个方法的实现:
<code>
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(originalMethod, swappedMethod);
</code>
- 类的方法的实现是可以在运行时动态增加或者替换的
- 用一个方法实现与另一个互换的过程称之为swizzling, 通常是增加一个方法替换原来的那个
- 通常在有益于调试的时候才在运行时互换方法实现, 不要仅仅因为你想炫技就这么做
Item14:
检验一个对象是不是某个类的对象可以用: isMemberOfClass:
检验一个对象是不是属于其父类可以用: isKindOfClass:
- 类的层级通过Class对象来建立, 每一个对象都有一个指针来指明其类型
- 当不知道对象的类型是不是某种类型时, 需要在编译时就用审查来检验之
- 尽可能使用审查而不是直接比较类型对象, 因为这个对象可能实现了消息转发
Item15:
- 选择一个适合公司,应用,或者兼顾二者的前缀, 并全面使用之
- 如果自己的库依赖第三方库,考虑给它加上自己的前缀
Item16:
- 实现你指定的(designated)init方法, 并在文档中写出. 所有其他的init方法都应该通过调用它来实现
- 如果指定的init方法与父类不同, 确保指定的init方法被重写了
- 如果子类不想要父类中的designated init方法中, 那就重写这个designated init方法, 并抛出异常
Item17:
- 实现description方法来为对象提供一个有意义的字符串描述
- 如果对象描述对调试很有用, 就实现debugDescription吧
Item18:
- 如果可以的话, 创建不可变的对象
- 如果需要在内部修改只读变量的值, 则在类的匿名category中拓展属性到读写
- 提供方法来使对象持有的集合可变, 而不是直接暴露可变集合的属性
Item19:
- 遵从OC中已经成为标准的命名方法来创建合适的接口
- 确保方法名是足够清晰的, 不用太考虑精简的问题, 让它们从左至右读起来像一句话
- 避免在方法名中使用类型的简写
- 最重要的是, 确保方法名与你自己的代码或即将集成的代码风格一致
Item20:
- 为私有方法加一个前缀, 这样可以很容易与公共方法区分出来
- 避免为方法加上下划线前缀, 因为这样是苹果保留的前缀
Item21:
- 只有在足以摧毁整个应用的致命错误下才使用异常
- 对于非致命错误, 可以提供delegate方法来处理或者在输出参数中提供NSError对象
Item22:
- 如果你的对象需要被copy, 就实现NSCopying协议
- 如果你的对象有可变和不可变的变量, 实现NSCopying和NSMutableCopying两个协议
- 决定用深拷贝或浅拷贝时, 如果可以的话, 在一般拷贝中使用浅拷贝
- 如果深拷贝在你的对象里有用, 考虑增加一个深拷贝方法
Item23:
- 使用Delegate模式来提供接口一个对象, 使它可以通知其他对象相关事件的发生
- 用隐式可选方法定义协议, 用来定义你的delegate需要支持的接口(感觉就是用@optional就好了)
- 当一个对象需要从另一个对象获得数据时, 用delegate模式. 这个做法通常被成为"data source 协议"
- 如果有必要, 实现位域结构方法来缓存delegate是否响应协议中的方法. (在需要频繁调用isrespondsSelector这个方法时, 加入一些标志位来直接判断是否实现)
Item24:
- 使用category来分割一个类, 把方法实现在不同的片段中, 使其更容易管理
- 创建一个Private的category来隐藏那些被定义为私有方法的实现细节
Item25:
- 在为不属于你的类中增加category时, 要给category的名字加上一个前缀
- 在为不属于你的类中增加category时, 要给category中的方法名字加上一个前缀
Item26:
- 所有的封装数据的属性声明都要在主接口中定义(主header, 而不是category的header)
- 在category中声明属性的访问方法更好, 除非是一个匿名category.
Item27:
- 使用匿名category来为类增加实例变量
- 如果需要在类的内部setter访问一个在外部声明为readonly的属性, 可以在匿名category中再次声明这个属性为readwrite(前面已经提过)
- 在匿名category中声明私有方法的方法原型
- 在匿名category中声明你需要保持私有的协议
Item28:
- 协议可以用来提供某些匿名类型. 这些匿名类型可以简写为那些实现了协议方法的id类型
- 当类型需要隐藏的时候使用匿名对象
- 当类型无关紧要且对象响应某个特定方法更为重要时使用匿名对象
注:所谓匿名对象应该就是id类型的对象
Item29:
- 引用计数的内存管理基于一个增减的计数器, 一个对象被创建出来计数器至少是1, 一个对象的计数器为正数时说明它是活跃的, 当计数器为0这个对象就会被销毁
- 对象被其它对象引用和释放贯穿整个对象生命周期. 引用和释放会对应增加或减少引用计数.
Item30:
- ARC把开发者从大多数内存管理的忧虑中解放了出来, 使用ARC可以减少类中的死板代码
- ARC通过在合适的地方增加retain和release掌握了对象的几乎整个生命周期, 变量修饰词可用来指示内存管理语义, 正如之前所说的手动retain和release一样
- 返回对象的方法名总是需要用来指示内存管理语义的. ARC已经巩固了这个概念并且基本上已经不可能不遵从整个规则了
- ARC只能处理Objective-C对象. 这也意味着CoreFoundation对象是无法处理的, 并且必须在合适位置调用CFRetain和CFRelease
Item31:
- dealloc函数应该只能被用作释放对其它对象的引用和注销一切需要注销的东西, 比如KVO和NSNotification的通知
- 如果一个对象持有了系统资源, 比如文件描述符, 应该要有一个方法来释放这些资源. 在使用完资源之后, 应该要有一个被成为close的方法来与他人协定释放资源.
- dealloc函数中应该避免调用其它方法, 防止这些方法试图异步执行, 或者在假设状态下一个对象是正常状态, 但其实它已经不是了
Item32:
- 当捕获到异常时, 小心确认try代码块中创建的对象是否需要清除
- 当抛出异常时, ARC默认不处理对象清理工作. 可以通过增加编译器标志来处理但是这会增加编译器产出的代码量和增加runtime负载
Ite33:
- 可以使用weak引用来避免造成循环引用
- weak引用有时会有时不会自动置空. 自动置空是ARC引入的新特性并且用runtime实现. 自动置空的弱引用是读取安全的, 因为它永远不包含一个已释放对象的引用
Item34:
- 自动释放池安排在栈中, 一个对象发送了autorelease消息就会被添加到最顶端的释放池
- 对自动释放池的合理应用可以减少应用的内存高线
- 现在的自动释放池使用性能更加优良的@autorelease语法
Item35:
- 当一个对象被释放, 可以选择成为僵尸对象而不是被真正释放. 这个特性可以通过环境标记NSZombieEnabled来开启
- 一个对象成为僵尸对象之后, isa指向对象本身的class结构体会被修改为特殊的zombie class结构体. 一个僵尸类可以响应所有的消息, 不过只是打印一条指示什么消息被发送给什么对象之后的信息之后就会退出整个应用.
Item36:
- 对象的引用计数可能起来很有用, 但是通常是没用的, 因为给定时间点的绝对引用计数并不能给出一个对象声明周期的完整视图
- 当ARC来到后, 引用计数就被舍弃了, 使用它会造成编译错误
Item37:
- block是C, C++和OC的语法闭包(代码块)
- block可以选择传入参数和返回值
- block可以是栈类型,堆类型和全局类型. 一个栈类型的block可以copy为堆类型的, 这个时候它的引用计数器就和标准的OC对象一样.
Item38:
- 使用typedef以使block的使用更加容易
- 永远遵从命名规则来定义新的类型, 这样就不会与其它类型冲突
- 不需要担心为同样的block签名定义多个类型. 你可能想要在使用某个block类型的地方通过改变block签名来重构, 而不是另外一个block(实在不好理解, 原文:Don’t be afraid to define multiple types for the same block signature. You may want to refactor one place that uses a certain block type by changing the block signature but not another.)
Item39:
- Handler block在与创建对象后与业务内联上非常有用
- Handler block在与对象直接联系这方面比delegation有更多的优势, 在代理里面如果有多个对象被发现需要回调则需要按对象来区分(如tableview的delegate就要把tableview回调回来, 这种做法直接把UI和数据耦合在了一起)
- 当设计一个API要用到Handler block的时候, 考虑传入一个队列的参数, 来指定block需要从哪一队中出队
Item40:
- 小心block的直接或者间接引入对象会对block进行retain, 从而造成潜在的循环引用问题
- 确保retain cycle会在恰当的时候被打破, 不要指望把职责转嫁到使用你API的用户上
Item41:
- 调配队列(Dispatch queue)可以用作同步语义并且可以作为@synchronized和NSLock对象的简单替代
- 混合同步和异步的调配队列可以提供与一般锁相同的同步行为但是不会把调用线程封闭在异步调配中
- 并行队列和隔离锁可以用作更高效的同步行为
Item42:
- performSelector方法族可能会在内存管理上出现潜在的危险. 如果无法确定哪个selector会被执行, ARC编译器就不能正确地插入内存管理代码
- performSelector方法族在传入参数和返回类型上面的限制有很多(最多2个参数, 返回类型只是id类型, 有一种做法是直接再写一个方法, 接受NSArray集合为参数, 在这个方法中调用需要传递多个参数的方法. 话说回来, 使用performSelector与直接调用的区别就是存在一定的延时, 即使delay为0的情况下, performSelector也会延迟在下一个runloop执行)
- 允许selector在其它线程执行的方法最好还是用GCD调用block来实现
Item43:
- 调配队列不是多线程和任务管理的唯一解决方案
- 操作队列(Operation queue)提供了高级的Objective-C API, 它们能做到大多数普通GCD可以做到的事. 这些队列也可以做到很多复杂的事情, 而不需要像GCD一样增加额外的代码
Item44:
- 组调配(Dispatch group)用来分组一系列的任务. 当组任务执行结束后你可以选择接受通知
- 组调配可以通过并行调配队列来执行多任务. 在这种情况下, GCD可以在同一时间基于系统资源来处理多任务的安排. 自己来实现就需要写大量的代码
Item45:
- 仅仅执行一次且线程安全是一个常有的任务. GCD提供了一个简单易用的工具--dispatch_once函数--来完成这项任务
- 令牌(token)应该被声明为static或者global, 以使得每次执行这一块代码时都是传入了同一个token
Item46:
- dispatch_get_current_queue函数并不总是像你期待那样执行. 它已经被弃用应该仅用作debug
- 调配队列以分层方式组织, 因此当前队列不能简单地用单个队列对象来描述
- 特定队列数据可以被用来解决寻常的用dispatch_get_current_queue的问题, 这样可以避免非可重入代码的死锁
Item47:
- 许多Framework都是可用的. 最重要的Foundation和CoreFoundation提供了组建应用最核心的功能
- Framework为很多普通任务而生, 比如音视频处理, 网络和数据管理
- 记住, 用纯C写的framework对你来说比Objecttive-C写的framework更重要, 成为一个好的Objective-C开发者你需要理解C的核心概念
Item48:
用block来遍历集合:
[anArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop){
// Do something with 'object'
if (shouldStop)
{
stop = YES;
}
}];
有几点好处:可以同时得到index和object, 如果不想继续遍历则stop=YES就好. 同时不需要进行一次转换, 如果明确集合中的元素类型可以直接^(NSString *str, NSUIntger idx, BOOL *stop){...} 这样来转换
同样可以在dictionary和set中用.
更多选择:
(void)enumerateObjectsWithOptions: (NSEnumerationOptions)options
usingBlock:(void(^)(id obj, NSUInteger idx, BOOL *stop))block(void)enumerateKeysAndObjectsWithOptions: (NSEnumerationOptions)options
usingBlock:(void(^)(id key, id obj, BOOL *stop))block
- 有四种方法可以实现集合元素遍历. for循环是最基本的, 随后的是用NSEnumerator遍历和快速遍历. 最现代和高端的方法是用block遍历方法
- block遍历允许你通过GCD并行实现遍历而不需要增加任何代码. 用其他的方法遍历并不能轻易做到
- 如果你知道遍历元素的类型可以更改block签名来指明它们.
Item49:
- Toll-Free Bridging可以让你在Foundation框架下的Objective-C对象和CoreFoundation框架下C数据结构之间转换
- 降级到使用CoreFoundation来创建集合让你你使用特定的变种回调函数来处理集合的元素. 通过toll-free bridging可以让你规避Objective-C集合需要的一些内存管理语法
Item50:
- 考虑使用NSCache来替换NSDictionary来缓存数据. NSCache有良好的裁剪行为,线程安全,并且不会像dictionary那样复制key
- 使用数量限制和负载限制来操作需要从缓存中裁剪对象的数量. 但是永远不要依赖这些量成为硬限制, 它们仅仅是缓存的一个指导而已
- 使用NSPurgeableData对象来缓存提供自动清除的数据(这些数据会在缓存清除中被自动移出)
- 如果缓存使用得当会使你的程序响应更加快速. 只需要缓存那些重新计算非常耗时的数据, 比如那些需要从网络或者磁盘中获取的数据
Item51:
- 类实现后会调用load方法. load方法可能会出现在category中, 类的load总是category的load之前. load方法不像其它方法, 它并不会被重写
- 实现load和initialize方法必须要非常简洁, 这有助于保持应用的快速响应并且减少可能引入的内部依赖循环
- 在initialize方法中设置不能在编译期间就设置好的全局状态. (原文:Keep initialize methods for setting up global state that cannot be done at compiletime)
Item52:
- NSTimer对象会retain它的目标(target), 直到定时器因被触发或者显式调用invalidate而失效
- 使用重复定时器很容易引入循环引用(如果定时器的目标retain定时器). 这在对象视图中可能会直接或者间接出现.
- NSTimer使用block拓展可以打破循环引用. 在这个方法成为NSTimer接口之前, 我们可以通过category来实现之:
- (void)startPolling {
__weak EOCClass *weakSelf = self;
_pollTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0
block:^{
EOCClass *strongSelf = weakSelf; [strongSelf p_doPoll];
}
repeats:YES];
}