1, 开篇
本文试图答复,如下问题:
1, 对象 dealloc 的时候,用一个 block 代替 dealloc 办法
不是类级别管制,是对象级别管制
2,关联对象
3,锁
本文是面试照着念主题,第 2 篇本文有参考 C.../CYLDeallocBlockExecutor
1, 怎么用好 block,让 dealloc 再见
因为对象开释的时候,对象的关联对象也会开释。
把对象开释的时候,要执行的 block ,放在其关联对象的 dealloc 办法中。
附加:
typedef void (^VoidBlock)(void);@interface BlockExecutor : NSObject { VoidBlock block;}@property (nonatomic, readwrite, copy) VoidBlock block;- (id)initWithBlock:(VoidBlock)block;@end@implementation BlockExecutor@synthesize block;- (id)initWithBlock:(VoidBlock)aBlock{ self = [super init]; if (self) { block = [aBlock copy]; } return self;}- (void)dealloc{ if (block != nil) { block(); block = nil; }}@endconst void *runAtDeallocBlockKey = &runAtDeallocBlockKey;@interface NSObject (RunAtDealloc)- (void)runAtDealloc:(VoidBlock)block;@end@implementation NSObject (RunAtDealloc)- (void)runAtDealloc:(VoidBlock)block{ if (block) { BlockExecutor *executor = [[BlockExecutor alloc] initWithBlock:block]; objc_setAssociatedObject(self, runAtDeallocBlockKey, executor, OBJC_ASSOCIATION_RETAIN); }}@end调用
- (void)viewDidLoad { [super viewDidLoad]; NSObject * pikachu = [[NSObject alloc] init]; [pikachu runAtDealloc:^{ NSLog(@"Deallocating pikachu!"); }];}具体三步走:
1, 一个有 block 的关联类,他的 dealloc 办法中,执行 block
2,给 NSObject 增加分类办法,传递 block ,不便调用。
把关联类,关联上察看类。
3,察看类调用,传入 block
优化:CYLDeallocBlockExecutor 的实现
CYLDeallocBlockExecutor 做的是加强版。
每一个 NSObject 都关联一个 CYLDeallocCallbackModel,
CYLDeallocCallbackModel 外面有一把互斥锁,一个事件字典,和调用者
@interface CYLDeallocCallbackModel : NSObject@property (nonatomic, assign) pthread_mutex_t lock;@property (nonatomic, strong) NSMutableDictionary *callbacksDictionary;@property (nonatomic, unsafe_unretained) id owner;@end当 NSObject 销毁的时候,他关联的 CYLDeallocCallbackModel 也会销毁,
把增加进的事件,按程序给执行了
@implementation CYLDeallocCallbackModel- (void)dealloc { NSArray *sortedKeys = [[_callbacksDictionary allKeys] sortedArrayUsingSelector: @selector(compare:)]; for (NSNumber *identifier in sortedKeys) { CYLDeallocSelfCallback block = _callbacksDictionary[identifier]; block(_owner, identifier.unsignedIntegerValue); } pthread_mutex_destroy(&_lock);}@end事件字典的构造是 [Int: Block], 加锁,事件字典是线程平安的。 批改和删除,都用互斥锁爱护
给 NSObject 的分类中,增加事件字典,串行线程队列,保障平安
绝对之前的那个,退出的特效是,
同一个对象,能够屡次增加销毁事件
// 增加一次[self cyl_willDeallocWithSelfCallback:^(__unsafe_unretained typeof(self) unsafeSelf, NSUInteger identifier) { // ... }]; // 增加第二次 [self cyl_willDeallocWithSelfCallback:^(__unsafe_unretained typeof(self) unsafeSelf, NSUInteger identifier) { // ... }];碎语:
心愿一个对象 A 开释的时候,开释 B。把 B 关联到 A 上就好了Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object
这个库,关联援用 runtime, 其作者说,性能耗费低。
有锁、有单例队列,有事件字典,侧面反映出苹果 weak 的优雅和高效
CYLDeallocBlockExecutor 外面有这么一段,
- (NSHashTable *)cyl_deallocExecutors { NSHashTable *table = objc_getAssociatedObject(self, @selector(cyl_deallocExecutors)); if (!table) { table = [NSHashTable hashTableWithOptions:NSPointerFunctionsStrongMemory]; objc_setAssociatedObject(self, @selector(cyl_deallocExecutors), table, OBJC_ASSOCIATION_RETAIN); } return table;}等价于
- (NSString *)cyl_deallocExecutors { NSString *table = objc_getAssociatedObject(self, @selector(cyl_deallocExecutors)); if (!table) { table = @"呵呵"; objc_setAssociatedObject(self, @selector(cyl_deallocExecutors), table, OBJC_ASSOCIATION_RETAIN); } return table;}还能够各种等价
关联对象的键,本质就是惟一就好
弄虚作假
CYLDeallocBlockExecutor 作者说,其性能好。
用锁、串行队列,减少期待与开销
大佬的脑回路...
讲故事:为什么这个库要用锁?
B 1, 不必锁,怎么体现某读过 runtime 源代码
B 2,用锁,能够劝退小白。小白看了容易犯困、降...
B 3, 锁,不必白不必。用不到的性能,上个锁爱护是没问题的。
还能进一步把握死锁相干
A 1, 用锁,保障字典的批改,线程平安。
A 2, 用串行队列,保障键自增的写平安。
这个串行队列,每个对象都会关联一个。
再用一个全局的串行队列,保障每个对象只会实例化一个关联的串行队列
// 全局的串行队列static dispatch_queue_t _deallocCallbackQueueLock = NULL;@implementation NSObject (CYLDeallocBlockExecutor)- (dispatch_queue_t)cyl_deallocCallbackQueue { // 全局的串行队列, 是一个单例 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *queueBaseLabel = [NSString stringWithFormat:@"com.chengyilong.%@", NSStringFromClass([self class])]; const char *queueName = [[NSString stringWithFormat:@"%@.deallocCallbackQueueLock", queueBaseLabel] UTF8String]; _deallocCallbackQueueLock = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); }); // _deallocCallbackQueueLock 全局串行队列中, // 保障每个 NSObject 关联的串行队列,只有一个 __block dispatch_queue_t queue = nil; dispatch_sync(_deallocCallbackQueueLock, ^{ dispatch_queue_t deallocCallbackQueue = objc_getAssociatedObject(self, @selector(cyl_deallocCallbackQueue)); if (deallocCallbackQueue) { queue = deallocCallbackQueue; } else { NSString *queueBaseLabel = [NSString stringWithFormat:@"com.chenyilong.%@", NSStringFromClass([self class])]; NSString *queueNameString = [NSString stringWithFormat:@"%@.forDeallocBlock.%@",queueBaseLabel, @(arc4random_uniform(MAXFLOAT))]; const char *queueName = [queueNameString UTF8String]; queue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); self.cyl_deallocCallbackQueue = queue; } }); return queue;}@end2, 引出关联对象的原理
也是哈希表,跟 weak 的原理相似
每一次设置关联,都是用一个对象附带的关联对象哈希表来治理
- 关联管理者 AssociationsManager, 有自旋锁,和关联对象哈希表
通过自旋锁,保障关联对象哈希表的操作,线程平安
- 关联对象哈希表 AssociationsHashMap , 构造可了解为 [ 对象 : 对象关联表 ]
对象关联表 ObjectAssociationMap,构造可了解为 [ key : 对象关联 ]
对象关联 ObjcAssociation , 蕴含关联的值和内存管理策略 policy
有了构造,就好编
三种操作,
1, 以键值对模式增加关联对象
拿着对象,找对象关联表 ObjectAssociationMap,没找到,就做初始化,填入数据
找到了 ObjectAssociationMap,拿着 key, 找对象关联 ObjcAssociation,没找到,就减少一个 ObjcAssociation
找到了对象关联 ObjcAssociation,就刷新信息
数据结构定了,操作简略,上面两种相似
2, 依据 key 获取关联对象
3, 移除所有关联对象
附加:
上文中的例子,参数顺次是
object, key , value , policy
objc_setAssociatedObject(self, runAtDeallocBlockKey, executor, OBJC_ASSOCIATION_RETAIN);3, 引出锁
常见的锁,也有很多。下文介绍下,自旋锁、互斥锁与信号量
锁,是用来解决并发问题的。
自旋锁和互斥锁,都能做到互斥,mutual exclusion
信号量,还能做到更多。
- 应用自旋锁 spinlock ,没有获取锁的线程,会始终试图获取锁,没做啥有用的事件。
益处是,缩小了线程上下文切换的开销。
如果锁对应的操作不耗时,适宜用自旋锁
如果锁对应的操作耗时,就很节约 CPU 了
- 互斥锁 mutex,
线程对锁,有一个持有关系,ownership
线程 a 获取了锁,其余线程就不能拜访锁对应的资源了,
( 其余线程做别的事去了, 有一个线程的上下文切换 context switching, 就是挪动栈顶指针,拷贝相干参数到函数栈上 )
直到线程 a 开释锁
( 其余线程被唤起,能够获取锁了 )
操作是,lock 和 unlock
- 信号量 semaphore,
信号值为多少,就容许多少个线程同时操作锁对应的资源
semaphore 应用信号机制,同 mutex 相比,线程对锁,没有持有关系
操作是, wait ( 信号值 - 1 ) 和 signal ( 信号值 + 1 ),
信号值为 0, 下一个拜访的线程,挂起 hanging
对方问你,艰深的
把线程获取锁,比做几个人上厕所。
- 自旋锁和互斥锁,都是厕所只有一个
线程应用自旋锁,厕所有人,就始终等在那。
线程应用互斥锁,厕所有人,就回去做事了。 线程睡眠,返回线程队列。该线程对应的 CPU, 没有空耗。
线程获取和持有锁,
实现后,该线程把锁移交给其余线程
- 信号量,就是厕所有多个
信号量设置为多少,就是有多少个厕所
附加
自旋锁,是一种很老的解决方案,
spin, 就是 looping 的意思,始终循环获取锁
自旋锁,是 busy waiting lock
互斥锁, 是 sleeping lock
自旋锁
互斥锁
自旋锁与其余锁的区别
互斥锁与信号量的区别
尾, 本文不...
本文又名,“贤者工夫:xxx 大佬开源库浅读”
本文仅供参考,适宜长期抱佛脚
对于 ...DeallocBlockExecutor 的评估,说不定某功力不...
万一,没发现其中的...