Objective-C 高效编程
ARC
在ARC中,虽然不用手动管理对象的引用计数,但是还是要明确每个变量的所有权修饰符,才能让ARC正常工作。
__strong
是 对象类型和id类型的默认所有权修饰符__strong
修饰的变量 ,ARC 在作用域内,未结束之前,假如让变量指空nil,编译也会马上插入release ,让他立即释放。不然的话 ARC会在这个变量的作用域结束时,大括号结束时
,插入这个变量的 release操作立刻释放。__autoreleasing
修饰的变量,那么他们会在运行时 beforewaiting事件的 autoreleasPool 释放时,自动释放。- 如果仅有
__strong
将无法避免引用循环问题,所以引入__weak
和__unsafe_unretained
两个弱引用所有权修饰符。 - 作为alloc/new/copy/mutableCopy方法返回值取得的对象,可以直接返回,除此之外的方法,编译器都会在返回时插入
objc_autorelaseReturnValue(result)
将对象放入AutoReleasPool 和接受时插入objc_retainAutoreleasedReturnValue(result)
,retain一次这个返回值,这里有一个优化的策略,一旦这两个方法挨在一起成对儿出现 ,他们通过函数栈上的操作,直接将要放回的对象传递给函数的调用方,而省去放入AutoReleasPool的步骤。编译器是根据调用方法的名字来判定如果处理返回值的引用计数,所以遇见 按条件执行 performSetelctor:copy/new/doSomething 时编译器无法插入正确的维护引用计数的代码。会造成内存泄露。 -
__autoreleasing
当使用对象指针的指针时,id * obj 或者 NSObject **Obj 都等同于NSObject *__autoreleasing * obj
,使用例子就是NSError .NSError *error; BOOL OK = [myObject performOperationWithError:&error]; -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
__weak
修饰的变量,并不增加引用计数,但是每当使用__weak
变量时,会用生成一个 __strong
tmp 临时变量,在使用完成后立即将tmp release释放。iOS和OSX的中说那个tmp修饰符应该是__autoreleasing
,被标记成为 autoreleasing 对象,其实是老版的clang对 __weak
的实现方法,现在已经改为,用完及时释放的实现版本了。假如,tmp当做一个参数传入到一个函数中,是可以打印引用计数 ,发现引用计数+1的。
```
{
NSObject* sp = [NSObject new];
NSObject* __weak wp = sp;
NSLog(@"%@", wp);
}
//转换为
id sp = objc_msgSend(NSObject, "new");
id wp;
objc_initWeak(&wp, sp);//初始化weak变量,并将变量放入weak表中,以对象地址为Key,变量地址为Value
id tmp = objc_loadWeakRetained(wp); // 拿出weak变量的真是对象,并retain,引用计数 +1;
NSLog(@"%@", wp); //使用weak变量
objc_release(tmp); //编译器发现使用完成就立刻释放掉临时指针tmp
objc_destroyWeak(&wp); //从weak表中销毁weak变量
objc_storeStrong(&sp, 0); //相当于objc_release(sp)
```
OC中52个有效方法
多用类型常量,少用#define预处理
- 定义常量
- 编译单元(.h.m)可见 : 需要用 static 和 const 一起用。static表示仅编译单元(.h.m)可见. const 表示不能修改。而且添加k前缀
- eg : static const NSTimeInterval kAnimationDuration = 0.3
- 全局可见 :
- eg1
- 在 .m 中 const NSTimeInterval EOCAnimationDuration = 0.3 ;
- 在 .h 中 extern const EOCAnimationDuration ;
- eg2
- 在 .m 中 NSString * const EOCStringConstant = @“Value” ;
- 在 .h 中 extern NSString *const EOCStringConstant ;
- 添加模块
类名
前缀
- eg1
用枚举型表示状态、选项、状态码
- typedef NS_OPTIONS
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageRetryFailed = 1 << 0,
SDWebImageLowPriority = 1 << 1,
SDWebImageCacheMemoryOnly = 1 << 2,
SDWebImageProgressiveDownload = 1 << 3,
};
- typedef NS_ENUM
typedef NS_ENUM(NSInteger, SDImageFormat) {
SDImageFormatUndefined = -1,
SDImageFormatJPEG = 0,
SDImageFormatPNG,
SDImageFormatGIF,
SDImageFormatTIFF,
SDImageFormatWebP
};
使用GCD可以比锁更高效,在保证多线程安全过程中
- dispatch_barrier_async 和并行队列 来处理 读写锁
- 将所有操作都放在一个串行队列里。可以很好滴保护这段代码线程安全。而却还可以异步,不会阻塞代码
少用performSelector 多用 GCD
- performSelector 在内存管理上容易有疏忽,他无法确定究竟要执行哪一个选择子,ARC无法插入适当的内存管理
- performSelector 函数传参合返回值都有局限性
- performSelector 大部分api都能用GCD替代
gcd 与 NSOperationQueue
- NSOperation
- NSOperation 添加依赖Operation
- NSOperation 有queuePriority,可以在NSOperation之间使用,让哪一个NSOperation先执行,而GCD只能针对一个队列,里面的block都是一个优先级。
- 设置最大并行数
- kvo监听isFinished 或者 isCanceled
- NSOperation 有threadPriority,可以更方便的设置线程优先级
- GCD
- 支持的方法更多
- 队列类型
- 主队列 : dispatch_get_main_queue
- 自定义队列 : dispatch_queue_create : DISPATCH_QUEUE_SERIAL / DISPATCH_QUEUE_CONCURRENT
- 全局队列 : dispatch_get_global_queue
- 优先级
- 高 QOS_CLASS_USER_INTERACTIVE : 及时交互 UI相关,交互等
- 高 QOS_CLASS_USER_INITIATED : 用户发起需要马上得到结果进行后续任务
- 中 QOS_CLASS_DEFAULT : 默认的不应该使用这个设置任务
- 低 QOS_CLASS_UTILITY : 花费时间稍多比如下载,需要几秒或几分钟的
- 低 QOS_CLASS_BACKGROUND : 不可见在后台的操作可能需要好几分钟甚至几小时的
- 优先级
- dispatch_apply 比 dispatch_async 有避免线程爆照的优势。
- dispatch_block_notify 可以检测某个block完成的通知
- dispatch_block_cancel 取消未执行的block,假如已经在执行,则不能取消,可以用dispatch_block_testcancel测试
-
dispatch_io_create/dispatch_io_set_low_water/dispatch_io_read 多线程去去读取大数据切片数据。比单线程要快很多
- Dispatch Source 用GCD监视进程
- Dispatch Source 类型
- DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加
- DISPATCH_SOURCE_TYPE_DATA_OR 数据OR
- DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送
- DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收
- DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况
- DISPATCH_SOURCE_TYPE_PROC 进程事件
- DISPATCH_SOURCE_TYPE_READ 读数据
- DISPATCH_SOURCE_TYPE_SIGNAL 信号
- DISPATCH_SOURCE_TYPE_TIMER 定时器
- DISPATCH_SOURCE_TYPE_VNODE 文件系统变化
- DISPATCH_SOURCE_TYPE_WRITE 文件写入
- Dispatch Source 类型
- Dispatch Source 用法
+ dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
- dispatch_source_set_event_handler:设置事件处理handler
- dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
- dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。
- Dispatch Source 几个作用
- 监听进程的退出 利用 DISPATCH_SOURCE_TYPE_PROC
- 监听文件夹内文件的变化 利用 DISPATCH_SOURCE_TYPE_VNODE
- 生成不依赖Runloop的Timer 利用 DISPATCH_SOURCE_TYPE_TIMER
- NSOperationQueue
- api更简洁
- 遍历
- for int i = 0 ;i < array.count ; i = i + 1;
- for object in array
- while ([NSEnumerator nextObject]) { } //set dict array方法统一
- [array enumerateObjectsUsingBlock:^(id _ Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}]; //set dict array方法统一,可以直接修改block参数签名,可以设置并行方式利用的是gcd。
- 精简 initialize和load 的实现代码
- load
- 唯一不遵守继承规则的。子类没实现这个方法,父类不会的load也不会被调用 + ClassA的load 方法和 他的分类load 方法,分别会被调用,而且 ClassA的load方法先被调用
- 最好不要用锁在load里,和大数据量操作。会阻塞app的载入。真有必要的话可以放到initialize里面。
- load里面使用其他类,要十分小心。可能其他类没有被load。
但是我怎么没测出来呢
- initialize
- initialize 和 load 都是线程安全的。在里面不用加锁使用。因为运行时在调用load和initialize时前后都加了锁。
- initialize 方法遵循继承规则,即使initialize里面没有调用 [super initialize],子类调用initialize,父类也会被调用,因为初始化子类,需要先初始化父类,从runtime源码上能开出,是再强制循环调用父类的initialize。如果子类真的不需要父类的初始化,可以在initialize 方法中添加 eg: SuperClass.m if(self == [SuperClass class]){ // do }
- 无法在编译期设定的全局变量可以放在,initialize里。
- load