乐趣区

关于ios:001-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 的评估,说不定某功力不 …

万一,没发现其中的 …

退出移动版