effective oc笔记
熟悉OC
-
了解OC的起源
- oc由smalltalk演化而来,使用’消息结构‘而非函数调用
- ’消息结构‘其所运行时所之行的代码由环境来决定 ((fn))
- ’函数调用‘由编译器决定,按照编译后的‘virtual table’来查找到底该之行哪个函数实现
- oc是c的‘超集’。
- oc由smalltalk演化而来,使用’消息结构‘而非函数调用
-
在类的头文件中尽量少的引入其它头文件
- 头文件只是声明,并不需药知道细节,所以使用‘{forward declaring}’有助于减少由于引用的类改变而造成的重新编译
- I还会阻止循环引用问题
- 有时候无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,第一步要判断 该类遵循的某项协议是否是要放在头文件中?考虑将其一道?????todo
-
多用字面量语法,少用与之等价的方法
要用
NSString * str = @"somestring";
NSArray *arr = @[@"1",@"2"];
代替
NSArray *arr = [NSArrayWithObjects:@"1",@"2",nil];
...代替原来的通过比较老旧的方法 -
多用类型定义,少用#define预处理指令
- 预处理指令只是文本替换,无法了解类型信息
- 预处理指令定义的变量可能会被再次通过define替换,而static const不会如果需要全局性的常量 ,可以使用extern static const来声明)
-
用枚举来表示状态,选项,状态码
- 使用NS_ENUM(type,enumname)和NS_OPTIONS (type,enumname)来制作枚举
- 可以使用FLAG枚举类做组合操作
- 在处理美剧类型的switch语句中要处理所有的状态,并且不要事先default分支。这样在新加入一个枚举状态的时候能够获得编译器的通知。
对象,消息,运行期
-
理解属性 todo
- 属性是为实例变量封装了自定义存取方法的语法糖
- 属性特质
- 原子性->nonatiomic
- 读写权限
- readwrite
- readonly
- 内存管理语义
- assign
- strong
- weak
- unsafe_unreatined
- copy
-
在对象内部尽量直接访问实例变量
- 优点
- 不经过‘method dispatch’步骤,代码直接访问实例变量的内存,访问速度快
- 可以绕过为相关属性定义的‘内存管理语义’,例如在ARC下访问一个copy属性的实例变量,那么并不会拷贝该属性,只会保留新值并释放旧值?
- 争议点
- 不会触发KVO通知,好坏看具体情况
- 折中实践
- 在对象内部读取数据的时候,直接通过实例变量来读取,写入的时候通过set方法。
- 在初始化方法以及dealloc中,总是通过实例变量来读写
- 在lazy load的情况下只能通过属性访问
- 优点
-
理解对象等同性这一概念
- == 操作符比较的是两个指针本身,而不是所指的对象
- 要判断两个方法相等请自己定义相等条件(按照实际需求来),重写并使用NSObject isEqual方法,并且实现hash方法。
hash方法实现的技巧,最好要根据对象的实例来进行生成,不要相同类型的对象的hash码都是一样。(不要先将所有实例属性放到string中再进行hash,会增大内存占用率)
~~ -(NSUInterager)hash{
~~ NSUInteger firstNameHash = [_firstName hash];
~~ NSUInteger lastNameHash = [_lastName hash];
~~ NSUInteger ageHash = _age;
~~ return firstNameHash ^ lastNameHash ^ ageHash;
~~ } - 等同性约定:若两对象相等,但是两个hash相等的对象却未必相等
- 特定类实现的Equal方法
- ~~NSString isEqualToString
- ~~NSArray isEqualToArray
~~NSDictionary isEqualToDictionary
- 编写特定实现的Equal方法的时候也要一并复写‘isEqual’方法
- 特殊情况:容器中可变类的Equal!
当容器中放入可变类对象的时候,如果某个对象放入collection中后,再修改这个对象,那么很可能会两个相同对象的hash值不同(set中存放NSMutableArray对象)。[尽量使用不可变对象]有探讨这个问题。
-
以"{class cluster}"隐藏实现细节
- 用于隐藏{abstract class}下面一个系列的实现细节,通常使用xxxWithType:工厂方法来进行实例化(ButtonWithType)
- 由于oc没有办法指明某个类是‘abstract’,所以应该在文档中写明,并且在接口中不提供initXXX的成员方法,暗示该类的实例不应该由用户直接创建。
- 类族中的类在查询{type instrospection类型信息}的时候要小心,创建的可能是子类实例
- //todo关于NSArray 的系统类族
-
在既有类中使用关联对象存放自定义数据
-
理解objec_msgSend的作用
- 调用方法的原理[Object MessageName:parameter]会被编译器转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,也就是 objec_msgSend
~~ void objc_msgSend(id self,SEL cmd,...) - **//todo 详细描述 **
- 调用方法的原理[Object MessageName:parameter]会被编译器转换为一条标准的C语言函数调用,所调用的函数乃是消息传递机制中的核心函数,也就是 objec_msgSend
-
理解消息转发机制
- 在无法接收到消息的情况下
-
用"方法调配技术"调试"黑盒方法"
-
理解“类对象”的用意
接口与api设计
-
用前缀避免命名空间冲突
- 由于OC没有namespace,所以在{static link}和{dynamic loader}的时候(更加麻烦)可能会出现‘{duplicate symbol error}’
- 唯一办法就是给所有名称都加上适当前缀
- Apple保留使用所有‘两字母前缀’的权利,所以我们只能使用三个+
- 不仅是类名,应用程序中的所有名称都应该加前缀。
- category 要在category和实现的方法中加上前缀(25条解释原因)
- 在实现文件中的纯c函数以及全局变量比较容易忽略,要注意!因为即使在实现文件中,它们也算作是Top Symbol
- 若自己所开发的项目库中用到了第三方库,并且自己的项目也准备座位第三方库发布出去,则应为其中的名称加上前缀。因为如果其它引用你的项目等人也引用了你引用的第三方库的话,就会引起冲突
- 规定了,我的前缀为ZYS!
-
提供“{designated initializer}”
- 就是定义一个最全面的初始化方法,拥有所有条件作为参数,其它有固定条件的初始化方法对它调用就行了。
- 要在文档中指明这个是designated initializer
- 如果超类的初始化不适用于子类,那么应该覆写这个超类方法,提供一个默认实现,或在其中抛出异常
-
实现description方法
- description方法所返回的描述信息将取代format string里面的%@
- 最好把指针地址也打印出来
- 可以借助Dictionary来进行对属性的打印
- debugDescription是用于调试打印的时候调用的方法。LLDB po命令
-
尽量使用不可变对象
- 尽量把对外公布出来的属性设为之都,而且只在的确有必要的时候才对外公布
- 若某属性仅可于对象内部修改,则在 class-continuation分类中讲起由readonly扩展为readwrite属性//todo 27
- KVC技术仍然能修改readonly的属性
- 不要把可辨的collection作为属性公开,只能在内部用可变的collection来维护,只对外提供相关方法。
-
使用清晰而协调的命名方式
- 给方法起名的第一要务就是确保其飞哥与你自己的代码或所要集成的框架相符。
- 如果方法的返回值是新创建的,那么方法的首词应该是返回值的类型,除非前面还有修饰语(eg:localizeString)。属性的存取方法不遵守这种命名方式,因为认为这些方法不创建新的对象,即便创建了新对象,我们也认为那相当于原有对象。这些存取方法应该按照其所对应的属性来命名
- 应该把表示参数类型的名词放在参数前面
- 如果方法要在当前对象上进行操作,那么就应该包含动词;若执行操作时还需要参数,则应该在动词后面加上一个或多个名词
- 不要使用str这种简称,应该使用string这样的全称
- Boolean属性的取方法加上is。如果方法返回非属性的Boolean,那么根据功能选has或is
- 将get这个前缀留给那些借由“输出参数“来保存返回值的方法。(ref参数)
- 委托代理是类名+方法
-
为私有方法名加前缀
- 内部方法名称加上前缀,有助于调试,因为可以和共有方法区分开
- 因为OC无法将方法标识为私有!!!详见11,12,14条。
-
理解Objective-c的错误模型
- 忘记c#、java的异常处理方式
- ARC在默认情况下不是{exception safe}。如果抛出异常,本应该在作用域结尾处释放的对象现在不会自动释放了。
- 生成{exception safe}的代码可以通过设置编译器的标识 -fobjc-arc-exceptions来实现,不过将引入一些额外的代码,在不抛出异常的时候,也照样要执行这部分代码。
- 只在及其严重的情况下才使用异常,非严重错误的情况下的编程范式为
- 令方法返回1
- 使用NSError
- 结构
- Error domain(string)
eg:NSURLErrorDomain - Error code(int)
eg:http error code - User Info(dict)
- Error domain(string)
- 用法
- 通过委托协议来传递错误
- 通过输出参数返回给调用者
- 结构
-
理解NSCopying协议
- 要支持拷贝操作,就要实现NSCopying协议,该协议只有下面一个方法
~~- (id) copyWithZone:(NSZone *)zone //zone 无须担心
~~//实现
~~- (id) copyWithZone:(NSZone *)zone{
~~ ClassName *copy = [[[self class] allocWithZone:zone]initWithXXX...];
~~return copy
~~- 深复制(指针指向的对象都复制一遍)、浅复制(只复制指针)
- 可变版本的拷贝应该实现NSMutableCoping协议
- 系统中一般都是浅拷贝,不要自以为是深拷贝
协议与分类
- 要支持拷贝操作,就要实现NSCopying协议,该协议只有下面一个方法
-
通过委托与数据源协议进行对象间通信
-
将类的实现代码分散到便于管理的数个分类之中
- 分类不仅可以用来做扩展方法,还可以按照需求来进行分类管理
- 分类还利于调试,因为分类名称会出现在其符号中
- 将应该视为私有的方法归入名为Private的分类中,以隐藏实现细节
-
总是为第三方的分类名称加前缀
- 如果有多个分类都实现了同一个方法,按照加载最晚的哪一个为准。为了防止这种情况,要在名称加前缀
-
勿在分类中声明属性
- 类所封装的全部数据都应该定义在主接口中!分类只是一种扩展手段,而非封装数据
-
使用“class-continuation分类”隐藏实现细节
- class-continuation就是实现文件中的@interface,用于隐藏私有变量和方法
-
通过协议提供匿名对象
- 不同于其他语言的{anonymous object},在OC中是指纯id类型的对象,这样就可以不被任何具体的类来替换(通过Delegate来规定要实现的行为)。PS:当做C#里面的接口
内存管理
-
理解引用计数
- like智能指针,创建一个对象,每被指向一次就reatin,每个指向被释放就release
- 原则
- 类自己控制+ -
- {retain cycle} ,在c#、java中会被垃圾回收器判定为island of isolation,会被直接回收;但是在OC中的refercence count中需要我们采用弱引用来解决这个问题,或是修改对象,让其不在保留另外一个对象。
-
以ARC简化引用计数
- CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CGRelease
-
在dealloc方法中只释放引用并解除监听
- 释放引用减少计数器已经在ARC环境下由编译器做了,我们要做的只是解除监听
- 开销较大或者系统内稀缺资源例如:file descriptor、socket、大块内存等,不能期待它们的自动dealloc。要单独编写一个方法来释放此种资源。
- //todo 异步
-
编写“异常安全代码”时留意内存管理问题
-
以弱引用避免保留环
-
以“自动释放池块”降低内存峰值
-
用“僵尸对象”调试内存管理问题
-
不用使用retainCount
块与大中枢派发
-
理解“块”这一概念
-
为常用的块类型创建typedef
-
用handler块降低代码分散程度
-
用块引用其所属对象时不要出现保留环
-
多用派发队列,少用同步锁
-
多用GCD,少用performSelector系列方法
-
掌握GCD以及操作队列使用的时机
-
通过Dispatch Group 机制,根据系统资源状况来执行任务
-
使用dispatch_once来之行只需运行一次的代码
-
不要使用dispathc_get_current_queue##系统框架
系统框架
-
熟悉系统框架
-
多用块枚举,少用for循环
-
对自定义其内存管理语义对collection使用无缝桥接
-
构建缓存时选用NSCache而非NSDictionary
-
精简initialize与load的实现代码
-
别忘了NSTimer会保留其目标对象
未整理