原文链接

我们都知道ARC中weak与assign或者说unsafe_unretained最大的不同就是设置weak属性后,系统会在对象被释放后自动将指向对象的指针置为nil,而assign则会产生一个悬空指针,那么系统是如何实现这一机制呢?我们能否自己模拟系统对weak的实现呢?

通过查看runtime源码中objc-accessors.h和objc-weak.h部分,我们大概可以了解系统针对weak的实现方式与strong或者copy的实现方式是不同的。

对于注册为weak的对象,系统会以weak指向的对象内存地址作为key,将之放入到一个hash表之中,当此对象的引用计数为0时会调用dealloc,之后遍历hash表中此key所对应的对象,将之置为nil,关于这部分详细内容可以自行查看源代码,在此不进行扩展。我们这里重点介绍下如何自行模拟系统这一机制。

分析

首先我们回想一下weak机制的几个步骤,其中真正与assign不相同的部分在于当对象调用dealloc之后对对象指针置空操作。那么我们很容易联想到Objective-C中关联对象的使用,关联对象也是在对象调用dealloc之后被移除,这样我们是否可以通过对对象添加一个关联对象来模拟weak的实现呢?

探究

下面我们通过代码来说明下如何自行实现一个类似weak机制的对象管理机制,本文中所用到的代码存放在https://github.com/samwei12/WeakDemo :

unsafe_unretained和weak具体使用中的差异

  1. 首先,创建一个ClassA类,
  2. 创建一个ClassB类,并在里面添加一个测试方法print,用于等下向ClassB实例对象发送消息,确认对象是否仍在使用

    - (void)print {
    NSLog(@"Object is %@", self);

    }
  3. 给ClassA添加一个ClassB对象属性,并设置为unsafe_unretained

    @property (nonatomic, unsafe_unretained) ClassB *objectB;
  4. 在main函数中添加如下代码:

    @autoreleasepool {
    ClassA *instanceA = [ClassA new];

    ClassB *instanceB = [ClassB new];

    instanceA.objectB = instanceB;

    [instanceA.objectB print];

    // release instanceB

    instanceB = nil;

    [instanceA.objectB print];

    }

    // prevent process be killed immediately

    sleep(3);

    return 0;
  5. 运行一下代码,不出意外的话,程序会挂掉,因为instanceA.objectB所指向的内存已经在instanceB = nil;时候被释放掉,instanceA.objectB仅指向一个悬空指针:

    2016-03-09 15:25:10.427 WeakDemo[98402:1613532] Object is 0x7fa080d0f100
    2016-03-09 15:25:10.432 WeakDemo[98402:1613532] Object is 0x7fa080d0f100

    Process finished with exit code 139

    当然,也有可能不会挂,这取决于执行print函数时,instanceB所在的内存是否完全释放掉。

  6. 如果将ClassA中的属性改为@property (nonatomic, weak) ClassB *objectB;则不会出现crash,这就是weak的作用了,objectB对象如果被释放掉,则该指针变为nil,而向nil发送消息是不会出现问题的。

如何给unsafe_unretained添加weak功能

下面我们就使用unsafe_unretained来一步一步模拟weak修饰符:

  1. 创建一个DeallocBlock类,代码如下:

    typedef void (^voidBlock)();
    @interface DeallocBlock : NSObject

    @property(nonatomic, copy) voidBlock block;

    - (instancetype)initWithBlock:(voidBlock)block1;

    @end

    @implementation DeallocBlock

    - (instancetype)initWithBlock:(voidBlock)block1 {

    self = [super init];

    if (self) {

    _block = [block1 copy];

    }

    return self;

    }

    - (void)dealloc {

    if (_block) {

    NSLog(@"DeallocBlock dealloc!");

    _block();

    }

    }

    @end

    覆盖它的dealloc函数,这样就实现了该对象被释放时执行外部传入的block功能。

  2. 添加一个NSObject的category,如下:

    #import <Foundation/Foundation.h>
    #import "DeallocBlock.h"

    #import <objc/runtime.h>

    @interface NSObject (deallocBlock)

    - (void)runBlockOnDealloc:(voidBlock)block;

    @end

    @implementation NSObject (deallocBlock)

    - (void)runBlockOnDealloc:(voidBlock)block {

    if (block) {

    DeallocBlock *deallocBlock = [[DeallocBlock alloc] initWithBlock:block];

    objc_setAssociatedObject(self, _cmd, deallocBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    }

    }

    @end

    这里主要实现的功能是,给所有的NSObject对象添加一个传入block的方法,这个方法的作用就是在其中根据传入的block生成一个DeallocBlock实例,并使用关联对象将这个实例关联到自身。之后当这个对象被释放时候,会自动移除所有的关联对象,也就会触发DeallocBlock实例的dealloc方法,执行我们传入的block。

  3. 接下来,我们重载ClassA的setObjectB方法:

    - (void)setObjectB:(ClassB *)objectB {
    _objectB = objectB;

    // 仅对objectB != nil case做处理

    if (_objectB) {

    [_objectB runBlockOnDealloc:^{

    NSLog(@"_objectB dealloc");

    _objectB = nil;

    }];

    }

    }

    这段代码主要作用就是给_objectB添加一个deallocBlock,这样,如果_objectB所指向的内存已经被释放掉,则会调用我们传递进去的block来将此悬空指针置为空

  4. 再次运行程序:

    2016-03-09 15:53:36.881 WeakDemo[99270:1639867] Object is 0x7fad79c0f310
    2016-03-09 15:53:36.882 WeakDemo[99270:1639867] DeallocBlock dealloc!

    2016-03-09 15:53:36.882 WeakDemo[99270:1639867] _objectB dealloc

    Process finished with exit code 0

    由此可知,在main函数中执行instanceB = nil;之后,开始执行deallocBlock,之后由于instanceA.objectB已经为nil,print方法不执行操作。

总结

至此,我们已经实现了自己模拟系统weak机制的运行,给一个本会产生悬空指针的unsafe_unretained修饰符加上了weak的功能。大家可以自行尝试https://github.com/samwei12/WeakDemo,如果有任何问题,欢迎指正。

参考文档

如何实现ARC中weak功能?的更多相关文章

  1. block使用小结、在arc中使用block、如何防止循环引用

    引言 使用block已经有一段时间了,感觉自己了解的还行,但是几天前看到CocoaChina上一个关于block的小测试主题: [小测试]你真的知道blocks在Objective-C中是怎么工作的吗 ...

  2. iOS开发学习-类似微信聊天消息中的电话号码点击保存到通讯录中的功能

    类似微信聊天消息中的电话号码点击保存到通讯录中的功能,ABAddress的实现在iOS9中是不能正常使用的,点击完成后,手机会非常的卡,iOS9之后需要使用Contact新提供的方法来实现该功能.快捷 ...

  3. [转]iOS ARC机制 weak strong

    写在开头 虽然距离WWDC2011和iOS 5已经快一年时间,但是很多开发者并没有利用新方法来提高自己的水平,这点在ARC的使用上非常明显(特别是国内,基本很少见到同行转向ARC).我曾经询问过一些同 ...

  4. @autoreleasepool在MRC和ARC中的区别

    对于@autoreleasepool {} (1)在ARC中会销毁所有在里面创建的对象,即使你用外面的Strong指针指向他 (2)在MRC中如果有外部的强指针指向,不会销毁对象,retainCoun ...

  5. ARC中KVO开发注意

    1 在ARC 中 KVO开发 添加监听和去掉监听必需 一一匹配,不要有过的去掉监听否则会有可能导致对象无法释放. 例如,在一个viewcontroller中添加webview 并监听webview的c ...

  6. Qt调用dll中的功能函数

    声明: 事先我已经自己动手写了一个简单的dll文件(myDLL.dll),C版接口的.并且用我前两篇有关DLL文章里面的方法,从dll中导出了导入库(.lib)文件,dll中有两个函数,原型如下:   ...

  7. OpenGL中的功能与OSG对应功能 (摘)

    将OpenGL中的功能与OSG对应功能进行列举: OpenGL function OpenSceneGraph implementation glClear( GLbitfield mask ) os ...

  8. oracle数据库不支持mysql中limit功能

    oracle数据库不支持mysql中limit功能,但可以通过rownum来限制返回的结果集的行数,rownum并不是用户添加的字段,而是oracle系统自动添加的. (1)使查询结果最多返回前10行 ...

  9. ppt画笔标记在哪里|ppt中画笔工具功能怎么用?

    一.ppt中画笔工具功能在哪里? 这个画笔工具其实就相当于我们的一个标记工具,要实现标记功能首先将需要演示的PPT按住F5进入到放映状态,然后在右击ppt上的空白处就会弹出衣蛾对话框,在对话框中选择“ ...

随机推荐

  1. Base64补充

    1.Base64简单说明 描述:Base64可以成为密码学的基石,非常重要. 特点:可以将任意的二进制数据进行Base64编码 结果:所有的数据都能被编码为并只用65个字符就能表示的文本文件. 65字 ...

  2. Java Thread.currentThread()和This的区别

    感谢原文作者:王婷婷-Smily 原文链接:https://blog.csdn.net/dfshsdr/article/details/92760135 缘由 很多人认为多线程中的Thread.cur ...

  3. uniap tab list 滑动

    效果如下 <uni-popup ref="bankListAll" type="dialog"> <ty-mutiple-select :mu ...

  4. 关于linux shell编程,alias rm='cp $@ ~/backup; rm $@'

    书上的这个例子需要在ubuntu的低版本的系统才支持,现在基本上都不支持了,想实现也很简单自己写一个脚本先备份再删除. alias也只是做了一次替换alias rm='cp $@ ~/backup; ...

  5. Linux基础:初识shell之系统命令基础

    一 shell介绍 shell Shell 中文意思贝壳,寓意类似内核的壳.Shell是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务,简而言之就是只要能够操作应用 ...

  6. LAMP环境下部署项目管理软件--禅道

    禅道与Jira的对比 禅道最大的特色是创造性的将产品.项目.测试这三者的概念明确分开,互相配合,又互相制约.通过需求.任务.bug来进行交相互动,最终通过项目拿到合格的产品. Jira设计以项目为主线 ...

  7. 新手菜菜之2020Kubernetes详细介绍大全

    前文 Kubernetes笔记(一):十分钟布置一套K8s环境 介绍了怎么快速建立一个k8s体系.为了持续运用k8s来布置咱们的应用,需要先对k8s中的一些根本组件与概念有个了解. Kubernete ...

  8. [VSCode] Todo Tree VSCode插件 待办事项树

    Todo Tree 一款待办事项插件 我们写程序的时候,难免会遇到一些情况需要标记或者搁置,在写代码的时候会用一些特殊的注释来表示不同的内容,使我们可以快速的定位我们注释的位置. 主要有以下几种: T ...

  9. C++ 序列操作函数最全总结

    标准库定义了许多用于操作序列的算法,大多在algorithm和numeric文件中,大多数函数的原理并不复杂,但是在很多情况下可以替代手写的情况,甚至更加优秀. 这类算法函数非常多,但是他们都有共同的 ...

  10. Paxos 学习笔记2 - Multi-Paxos

    Paxos 学习笔记2 - Multi-Paxos 图片来自 John Ousterhout 的 Raft user study 系列课程 Multi-Paxos 论文里对很多问题并没有描述清楚,所以 ...