兼容c,c++
前置声明;@import module; pch 文件
@[@1, @2, @3], @{@"Matt":@"firstName"}
预定义#define无类型,副作用问题多;尽量用 static const,external等方式取代
可以继承类型了
enum EnumType : NSInteger;
NS_ENUM 传统的,累加型
NS_OPTIONS 位运算类型
这两个 Macro 向后兼容旧式写法(C++)
- 自动合成的成员变量,影响到内存布局,类大小,二进制兼容
- atomic/nonatomic
- readonly/readwrite
- assign/strong/weak/copy/unsafe_unretained
- getter/setter
考虑要点
- 直接访问ivar速度快,但普通情况下可以忽略(除非密集计算等)
- copy语意以及MRC下的内存语意被忽略
- KVO不会被触发
- 调试时无法在@property处或者setter处下断点
当然,在init和dealloc里应该尽量直接访问ivar,避免子类重载以及类信息被破坏self不完整等问题。
而在延迟初始化/Lazy Initialization里必须使用getter方法。否则无法初始化。
- isEqual 的 前提是 hash 相等
- 不同对象如果 hash 一样,在collection里会出问题
- hash最好是轻量级函数,否则在collection里会有性能问题。
- isEqualToString 针对 isEqual 优化,其他类似。
NSArray 跟 NSMutableArray 的class对象是一样的
为一个已有的class添加成员变量
消息传递 函数原型
id objc_msgSend(id self, SEL cmd, ...)
同时还有辅助函数 objc_msgSend_stret(返回结构体), objc_msgSend_fpret(返回浮点数)
objc_msgSendSuper(以及另两个返回值版本)可以和超类发消息
所有的 Objective-C 对象的每个方法都可以类比为
<return_type> Class_selector(id self, SEL _cmd, ...)
这样的结构方便尾递归优化,避免栈溢。
message forwarding
-
dynamic method resolution
-
full forwarding mechanism
第一步寻找有没有指定的类,它能否添加新的方法来响应。如果失败则走第二步。具体方法有
+(BOOL) resolveInstanceMethod:(SEL) selector
和类似的 resolveClassMethod, 在里面添加支持函数
第二步,先让接收者查看是否有其他对象能处理这条信息;如果还是没有,则启动full forwarding mechanism, 把详细的信息都封装到 NSInovaction 里,再给接收者最后一次机会,让它来处理这个message。
在下面的方法里检查有没有 replacement receiver
-(id) forwardingTargetForSelector:(SEL)selector
最后的机会
-(id) forwardInvocation:(NSInvocation*)invocation
这三步,越往后代价越高
动态插入自己想要执行的函数。一般在 +(void)load 或者 + (void)initialize 里做实现,交换系统或者库函数和自己实现的函数。各种实例,不赘述。
注意*isa 和 *super_class, 分别指向meta class 和 super class。当然最后会在 NSObject 上打圈圈。
- isKindOfClass 是否为某个类或者派生类
- isMemberOfClass 是否为某个特定类的实例
注意 class cluster的问题:
NSArray * e1 = [NSArray new];
NSArray * e2 = [NSMutableArray new];
BOOL b1 = [e1 isKindOfClass:NSArray.class];
BOOL b2 = [e2 isKindOfClass:NSMutableArray.class];
BOOL b3 = [e1 isMemberOfClass:NSArray.class];
BOOL b4 = [e2 isMemberOfClass:NSMutableArray.class];
结果是(测试环境Xcode 6.3.2, iOS simulator iPhone 5, iOS 8.3)
(__NSArrayI *) e1 = 0x7986d6a0 @"0 objects"
(__NSArrayM *) e2 = 0x7986e930 @"0 objects"
(BOOL) b1 = YES
(BOOL) b2 = YES
(BOOL) b3 = NO
(BOOL) b4 = NO
NSDictionary 和 NSMutableDictionary一样。
另外注意 NSProxy 的实例调用class方法和调用动态查询返回的结果不一致,后者能返回"真正"的干活的类型;前者返回 NSProxy。
- Apple 保留了两字前缀的权利,所以尽量三个(大写)字前缀
- 小心 C 类全局函数
- 小心第三方库的相互包含以及冲突(libA 包含v1的libC, libB包含v2的libC)
- 一个类里的多个初始化函数应当指定一个 designated initializer, 其他初始化函数调用它来实现
- 子类重载实现新的 designated initializer, 必须重载父类里的对应方法。
- 如果父类的初始化方法不适用于子类,则应该重载并在里面抛出异常。
NSLog 用 %@ 输出的实例,是 [NSObject description]的结果,所以适当地实现它。可以用NSDictionary格式方便整理格式。 而 po 的结果,是 [NSObject debugDescription],往往就是简单调用[NSObject description]
如果把可变对象放入collection再作修改,则会破坏里面的结构(比如set会包含两个相同元素)
如果希望某个属性只允许内部修改,可以用 class extension 重新声明为 readwrite
不要吧可变的 collection 作为属性公开,应该对它封装。
就是有时会冗长点
- 区分公开方法和私有方法。修改公开方法的代价很高。
- 不要简单地在前面添加下划线,可能 Apple 会占用
- ARC 不保证异常安全。如果需要,使用 -fobjc-arc-exceptions 标志编译。总的来说,并不推荐使用异常
- 更偏向于用 NSError, error domain/error code/user info来报告,类似c
- 注意传入的NSError ** 的内存语意是 * __autoreleasing *
留意 - copy, - mutableCopy, -immutableCopy 的区别;注意浅拷贝和深拷贝的使用场景。
理解 protocol
- 调试时方法的 symbol name 包含 category 名字
- 需要隐藏细节时创建 private category
即使用 associated object 实现,也得留意内存语意等细节。不建议这么做。
- 比如引入c++时,避免了一堆文件都要声明class 关键字然后统统用.mm文件实现
- 可以声明类变量和 property
- 可以覆盖 property 的 部分声明(readonly 改成 readwrite)
- 可以声明遵从某些 protocol
用protocol避免继承基类。这种方式类似于 c++ 的抽象基类,轻量。
比如 NSDictionary的set方法,key部分要求是 id , 支持copy。
建议实现 -cleanup 函数,在调用 dealloc之前就释放大部分资源。
典型的是在finally里保证释放资源。然而ARC下并不手动释放,所以问题更大
delegate一般声明为weak
以及weakself(某些情况下可以使用__block变量)
- 内存多释放一次: crash 用zombie调试。
- 内存少释放一次: leak
- 捕获
比如网络请求,需要处理 didReceiveData, didError, didFinish等事件,可以分别挂block,也可以挂一个block整体都做处理。
- weakself
@synchronization, NSLock,等等减弱性能 合理安排dispatch queue避免竞态条件,死锁。 比如dispatch_barrier_sync用于写同步
- performSelector 参数受限,特别时延迟和在主线程上的操作
- 函数命名时的alloc等内存语义会被忽略
- 返回对象只能是 id
NSOperationQueue 除了重一些,优势有:
- 取消某个操作
- 指定操作的依赖关系
- 通过 KVO 监控 NSOperation 对象的属性
- 指定操作优先级
- 重用 NSOperation 对象,继承之
任务分组,可以并发也可以序列操作,完成后可以带个notify block操作
dispatch_apply 展开循环
典型的如实现 singleton
单独判断这个避免不了死锁。比如queueA在queueB里,串行依赖,这俩会互相死锁,在queueB里判断不在queueA里没有作用。解决办法是dispatch_queue_set_specific设置标志后检查。
-
Foundation
基础数据结构, NS开头
-
CoreFoundation
配合Foundation,对象桥接出来。大量有用的库AVFoundation, CoreGraphics, CoreData, 等等
快速枚举, 特别是NSDictionary同时给出key和value
- bridge, bridge retain, bridge transfer
- CFRelease, CFRetain
- NSCache自动在lowmemory warning时释放资源
- NSCache线程安全,而且不会拷贝key
- NSCache的上限只是建议
- NSPurgableData 和 NSCache搭配使用可实现自动清除功能
- 挑选那些值得缓存的数据
- 注意不要产生循环依赖
- load是在第一次把class(subclass)或者category加载到runtime时调用(细节见文档)
- initialize是class(或subclass)第一次接收到message时调用的。仅调用一次,但如果子类没有实现,会调用父类的(实质上的多次)。线程安全。(细节见文档)
如果使用了self作为target,一定要合理地调用invalidate,否则循环引用导致dealloc无法调用,泄露。