共计 5876 个字符,预计需要花费 15 分钟才能阅读完成。
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;
}
}
@end
const 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;
}
@end
2,引出关联对象的原理
也是哈希表,跟 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
的评估,说不定某功力不 …
万一,没发现其中的 …