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
的评价,说不定某功力不...
万一,没发现其中的...