0.0.1, dealloc block, 讲好一个故事,iOS 面试题

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 的评价,说不定某功力不...

万一,没发现其中的...

你可能感兴趣的:(ios)