1.OC使用动态绑定的消息结构,在运行时才会检查对象类型
接受消息后,要执行什么代码,由运行期环境而非编译器决定
对于多态的方法,将在运行期按照虚方法表查出到底应该执行哪个函数的实现
分配在堆中的内存(eg.对象)必须直接管理,分配在栈上用于保存变量的内存在栈帧弹出时自动清理
2.多使用字面量语法创建 @表示字面量
字符串 @“1” 数值@1 数组@[@"1",@"2"] 字典@{@"age":@28}
多使用取下标操作访问数组或字典元素,如array[1]、dictionary[@"age"]
3.使用NS_ENUM 与 NS_OPTIONS 宏定义枚举类型
指明其底层数据类型,可确保枚举为所选的底层数据类型实现的
NS_ENUM 枚举间不需要相互组合
NS_OPTIONS 按位或操作来组合的枚举,各选项定义为2的幂
在处理枚举类型的switch语句中不要实现default分支
//预定义将源代码中的ANIMATION_DURATION替换为 0.3
#define ANIMATION_DURATION 0.3
//这样声明的变量带有类型信息,描述了常量的含义
static const NSTimeInterval kAnimationDuration = 0.3;
常见关键字:
const 声明只读变量,表示将对象转换成一个常量,若试图修改由const 修饰的变量,编译器会报错
static 声明静态变量,意味着该变量仅在定义此变量的.m文件中可见
extern 声明变量会出现在全局符号表中,其他文件可见
typedef 用以给数据类型取别名
union 声明共用数据类型
volatile 说明变量在程序执行中可被隐含地改变
4.向前声明
在A中引用B,在A的头文件中写 @class B;实现文件中再#import "B.h"
5.对象 — 基本构造单元
@property = ivar + getter + setter;
除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。
通过对象,存储并传递数据,在对象之间传递数据并执行任务叫做消息传递
将所需数据保存为各种实例变量,属性用于封装对象中的数据
@synthesize 关键字用来指定实例变量的名字
@dynamic 关键字告诉编译器不要自动创建实现属性所用的实例变量,也无需创建存取方法
6.直接访问实例变量 _firstName 不会调用其“设置方法”,不会触发KVO
访问属性 self.firstName
写入实例变量时,通过其“设置方法” self.
读取实例变量时,直接访问实例变量 _
若采用懒加载(属性不常用,且创建属性的成本较高,所以延迟加载)
必须通过读取属性 self.
7.对象等同性
== 操作比较的是两个指针本身
若想检测对象等同性,重写 isEqual:和 hash 方法
8.类族模式可将实现细节隐藏在一套简单的公共接口后面
9.在既有类中使用关联对象存放自定义数据
单纯用继承的方法扩展属性,有时可能无法创建自己所写子类
给对象关联其他对象,用键区分属性
//存取方法
void objc_setAssociatedObject(idobject,void*key,idvalue,objc_AssociationPolicypolicy);
objc_getAssociatedObject(idobject,void*key);
10.objc_msgSend
OC动态性:所有的方法在底层都是普通的C语言函数,对象收到消息之后,究竟该调用哪个方法,完全在运行期决定,也可在运行期改变
此函数会依据接收者与选择子的类型来调用适当方法
现在接收者所属的类中搜索其方法列表,找到则执行,找不到沿着继承体系向上查找,找到后执行;若还是找不到,执行消息转发
每个类中都有一张表格,选择子为查表时所用的键
11.消息转发 objc _msgForward
12.类的方法列表会把选择子的名称映射到相关的方法实现上 SEL —> IMP
13.程序运行到断点处,开发者可向console输入命令,po 对象打印
14.每个OC对象实例都是指向某块内存数据的指针,所以声明变量时,类型后跟一个 * 字符
在运行期检视对象类型 —> 类型信息查询,可查出对象是否能响应某个选择子,是否遵从某项协议,位于类继承体系的哪一部分
每个对象结构体的首个成员是Class类的变量,定义了对象所属的类,称 isa 指针,类型信息查询中使用isa指针获取对象所属的类
15.为类提供一个全能初始化方法,其他的初始化方法都要调用它
提供类的全能初始化方法,当底层数据存储机制改变时,只需修改此代码,无需改动其他初始化方法
每个类的全能初始化方法都应调用其超类的对应方法
16.尽量创建不可变的对象
为私有方法名加前缀,将其同公共方法区分开
OC的动态性使其无法再编译时就确定哪个方法公用,哪个方法私有,要在运行时,运行期检查对象能响应的方法
17.只有发生了可使整个应用崩溃的严重错误时,才应使用异常
在出现非致命错误时,处理为让方法返回nil / 0
用NSError处理
Error domain 错误范围 、产生错误的根源
Error code 错误码
User info 用户信息
通过委托协议来传递此错误,或经“输出参数”返回给调用者
- (BOOL)doSomething:(NSError**)error; //error是个指针,且该指针本身又指向另外一个指针
NSError *error = nil;
Bool ret = [object doSomething:&error];
if(error){...} //若不关注具体出错原因,直接判断ret即可if(ret){...}
18.NSCopying协议/NSMutableCopying协议
遵循这个协议,对象应该实现如下方法
-(id)copyWithZone:(NSZone*)zone
//可变版本
-(id)mutableCopyWithZone:(NSZone*)zone
对于一些不可变对象,copy直接赋予对象的引用,保证一致性,因为不重新开辟空间,节约内存;但是对于可变对象,copy将重新生成一个一模一样的对象,各改各的,防止造成数据错乱。
简单做个试验,对字符串进行copy、mutableCopy,对可变字符串进行copy、mutableCopy后,输出其地址
验证copy、mutableCopy
可以发现,对于不可变对象,copy会直接赋予对象的引用,不生成新对象,而对于不可变对象,copy、mutableCopy都会生成新对象。
19.通过委托和数据源协议进行对象间通信
用 respondsToSelector: 来判断委托对象是否实现了相关方法
调用 delegate 对象中的方法时,应该把发起委托的实例也一并传入方法中
20.分类的目的在于扩展类的功能,而非封装数据
- 向无源码的既有类中新增功能
- 分类不支持添加属性和成员变量,勿在分类中声明属性,应把封装数据所用的全部属性都定义在主接口里
- 若在分类中声明属性,需要将其声明为@dynamic,存取方法等到运行期再提供
- 分类中的方法是在运行期系统将分类方法加入类,直接添加在类里面的,类似于类的固有方法
- 将类的实现代码分散到便于管理的数个分类之中
- 通过给分类加前缀、给分类中的方法名加前缀的方法,避免分类与分类,分类与本来的类方法覆盖
21.m 文件中的@interface 叫 class-continuation分类
将私有方法的原型、类中所遵循的不为人知的协议 在class-continuation中声明
.mm 扩展名表示编译器将此文件按Objective-C++来编译
在Objective-C中,runtime会自动调用每个类的两个方法,两个方法都只会被调用一次。
+(void)load会在类初始加载时调用
+(void)initialize惰性调用。
保证在足够早的时间点被调用,将
didFinishLaunchingWithOptions 中的第三方中的注册代码、推送初始化方法挪到+ (void)load 中