iOS深入学习之Weak关键字介绍

前言

从大二的开始接触OC就用到了weak属性修饰词,但是当时只是知道如何去用这个关键字:防止循环引用。根本没有深入地去了解它。
在刚来北京的时候面试过程中也常常考到该知识点。大点的公司可能会问它如何使用?如何在对象销毁后将对象置nil,小点的公司可能只问一下它的使用。
Now,如果你对它产生恐惧或者曾经对它产生过恐惧(+1),如果你被该关键字弄得整天吃不下饭,睡不着觉,那么可以继续往下阅读,希望读过该博客之后能够帮到你。
废话不多说,开始介绍。

由浅入深

先来看看最简单的一个例子:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,strong)id strongPoint;
@property (nonatomic,weak)id weakPoint;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
//    self.strongPoint = [NSDate date];
    self.strongPoint = [[UILabel alloc] init];
    self.weakPoint = self.strongPoint;
    self.strongPoint = nil;
    NSLog(@"result is :%@", self.weakPoint);
}
@end

我们可以看到此时输出的结果为:

2017-02-07 13:20:41.119278 Test[7341:2187477] result is :(null)

如果我们使用的strong来修饰weakPoint,此时输出的结果为:

2017-02-07 13:23:13.211164 Test[7344:2187993] result is :<UILabel: 0x100206070; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17009a590>>

如果我们使用assign来修饰weakPoint,此时运行程序可能会崩溃(因为如果引用操作发生时内存还没有改变内容,依旧可以输出正确结果,如果引用的时候内存内容发生改变了,就会crash),因为当assign指针所指向的内存被释放之后,不会自动赋值为nil,这样再次引用该指针的时候就会导致野指针操作。
对上述代码运行结果进行分析:
当使用weak关键字的时候,不会增加对象的计数,而且当所指对象置nil的时候,使用weak修饰的指针将被赋值为nil;
当使用strong关键字的时候,会增加对象的计数,也就是说会保持对象值的存在,所以当使用strong的时候weakPoint还会有值。
因此,我们从这里可以得出一个结果:
strong是强引用,它会保持对象值的存在;
weak是弱引用,当weak指针指向的对象摧毁之后,属性值也会清空(nil out)。
(注意:使用 _ _ weak修饰 和在@ property里面设置weak是一样的)
但是当我们执行如下代码的时候:

__strong NSString *yourString = @"Your String";
__weak NSString *myString = yourString;
yourString = nil;
__unsafe_unretained NSString *theirString = myString;
NSLog(@"%p %@", yourString, yourString);
NSLog(@"%p %@", myString, myString);
NSLog(@"%p %@", theirString, theirString);


你会发现只有yourString为空,其他两个都不为空,这个是为什么呢?原因如下



这里是因为字符字面值永远不会被释放,所以你的weak指针还是指向它。
当你使用@""创建一个string对象的时候,它就是一个字面值,永远不会被改变。如果你在程序中很多地方都用到了一样的字符串,那么你可以测试一下,它们都是同一个对象(地址一样),String字面值不会销毁。使用[[NSString alloc] initWithString:@"literal string"]也是一样的效果。因为它指向了一个字面值的string。



那么请问weak指针指向对象被回收的时候该指针是如何被自动置为nil的呢??



首先,大家可以看一下博客最后面的附录,里面有两个文档,严格来说是Apple的opensouce。里面有一个objc-weak的类。这里是一个objc-weak.h类和一个objc-weak.mm类。

扩展常识
.m和.mm的区别
.m:源代码文件,这个典型的源代码文件扩展名,可以包含OC和C代码。
.mm:源代码文件,带有这种扩展名的源代码文件,除了可以包含OC和C代码之外,还可以包含C++代码。仅在你的OC代码中确实需要使用C++类或者特性的时候才用这种扩展名。

从.h中可以看到以下几个关键的两个结构体:weak_entry_t和weak_table_t,以及一些方法。接下来简单介绍一下weak如何自动置为nil。
weak的实现其实是一个weak表,该表是一个由自旋锁管理的哈希表。
以下是从NSObject.mm里面摘出的一些方法:

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

该function的作用是初始化一个新的weak指针指向对象的地址。其中的参数介绍如下:

  • location段:_ _ weak指针的地址
  • newObj:对象的指针地址

这里调用的storeWeak方法,storeWeak方法里面通过template模板的参数进行更新weak操作,看源码可以知道里面会调用weak_register_no_lock/weak_unregister_no_lock等objc-weak.mm里面的方法进行相应的操作。objc-weak.h里面有句话:

For ARR, we also keep track of whether an arbitrary object is being
deallocated by briefly placing it in the table just prior to invoking
dealloc, and removing it via objc_clear_deallocating just prior to memory
reclamation.

对象被废弃时最后调用objc_clear_deallocating。该函数实现如下:

void
objc_clear_deallocating(id obj)
{
    assert(obj);
    assert(!UseGC);

    if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}

也就是调用了clearDeallocating,继续追踪可以发现,它最终是使用了迭代器来取weak表的value,然后调用weak_clear_no_lock,然后查找对应的value,将该weak指针置空:

/**
 * Called by dealloc; nils out all weak pointers that point to the
 * provided object so that they can no longer be used.
 *
 * @param weak_table
 * @param referent The object being deallocated.
 */
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;

    if (entry->out_of_line) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    }
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }

    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n",
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }

    weak_entry_remove(weak_table, entry);
}

objc_clear_deallocating该函数的动作如下:

  1. 从weak表中获取废弃对象的地址为键值的记录
  2. 将包含在记录中的所有附有 _ _ weak修饰符变量的地址,赋值为nil
  3. 将weak表中删除该记录
  4. 从引用计数表中删除废弃对象的地址为键值的记录
    看了objc-weak.mm的源码大概了解了:其实Weak表示一个hash表,然后里面的key是指向对象的地址,Value是Weak指针的地址的数组。
结束

以上便是我个人对weak的理解,查看objc4的源码,发现里面更多的都是结构体、结构体套结构体等等。

附录
  1. Apple的opensource
  2. Apple的github
  3. stackoverflow
原文链接

点此

时间: 2017-02-08

iOS深入学习之Weak关键字介绍的相关文章

开源中国iOS客户端学习 (十二) 用户登陆

上一篇博客  开源中国iOS客户端学习--(十一)AES加密 中提到将用户名和密码保存到了本地沙盒之中,在从本地读取用户名和密码,这是一个怎样的过程? -(void)saveUserNameAndPwd:(NSString *)userName andPwd:(NSString *)pwd { NSUserDefaults * settings = [NSUserDefaults standardUserDefaults]; [settings removeObjectForKey:@"User

IOS基础学习UIButton使用详解

  UIButton按钮是IOS开发中最常用的控件,作为IOS基础学习教程知识 ,初学者需要了解其基本定义和常用设置,以便在开发在熟练运用. 第一.UIButton的定义 UIButton *button=[[UIButton buttonWithType:(UIButtonType); 能够定义的button类型有以下6种, typedef enum { UIButtonTypeCustom = 0, 自定义风格 UIButtonTypeRoundedRect, 圆角矩形 UIButtonTy

iOS开发拓展篇-XMPP简单介绍

iOS开发拓展篇-XMPP简单介绍 一.即时通讯简单介绍 1.简单说明 即时通讯技术(IM)支持用户在线实时交谈.如果要发送一条信息,用户需要打开一个小窗口,以便让用户及其朋友在其中输入信息并让交谈双方都看到交谈的内容 有许多的IM系统,如AOL IM.Yahoo IM. MSN以及QQ,它们最大的区别在于各自通讯协议的实现,所以即时通讯技术的核心在于它的传输协议 协议用来说明信息在网络上如何传输,如果有了统一的传输协议,那么应当可以实现各个IM之间的直接通讯,为了创建即时通讯的统一标准,目前已

iOS之学习资源收集--很好的IOS技术学习网站

点击图片也能打开相关的网站: The AppGuruz:http://www.theappguruz.com/category/ios 也是一个国外的网站,但是包含了IOS的学习内容 https://spin.atomicobject.com/?s=IOS : 很好的国外英文IOS学习网站 ,内容很丰富哦,耐心学习,英语阅读水平也会提高的:http://www.techotopia.com/index.php/IOS_iPhone_iPad_eBooks 下面这个https://github.c

iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角)

图像: 1.图片浏览控件MWPhotoBrowser        实现了一个照片浏览器类似 iOS 自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存.可对图片进行缩放等操作.       下载:https://github.com/mwaterfall/MWPhotoBrowser   目前比较活跃的社区仍旧是Github,除此以外也有一些不错的库散落在Google Code.SourceForge等地方.由于Github社区太过主流,这里主要介绍一下Gith

iOS ARC学习汇总

ARC ARC是什么 ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting).简单地说,就是编译阶段自动做了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了.就ARC并不是GC,不是运行时内存管理,不会做malloc/free的工作,它只是一种代码静态分析(Static Analyzer)工具. 比如,我们生成一个对象: addretain.png 编译器会给我们添加上retain/rel

JavaScript Event事件学习第一章 Event介绍_javascript技巧

没有event就没有脚本.可以看看任何有JavaScript代码的网页:几乎所有的例子都有一个事件触发了脚本.原因非常简单.JavaScript就是给你的页面添加内部活动:用户做一些事情然后页面做出回应. 因此JavaScript就需要一个方法能够检测到用户的动作然后才能知道什么时候做出反应.这还需要知道那个函数会被执行,函数会做一些你认为的给你的网页增色的动作.这些文字描述了如何去写这样的脚本.虽然不容易,但是这是一个很让人满足的工作. 当用户做了什么事情event就发生了,当然还有一些eve

iOS 逆向工程 - 学习整理

一.class-dump 简介:顾名思义,就是用来导出目标对象的class信息的工具,私有方法声明也能导出来. 原理:利用 Objective-C语言的 runtime 特性,将存 在Mach-O 文件中的头文件信息提 出来,并生成对应的 .h 文件. 使用方法: 1,下载然后将class-dump 复制到" /usr/bin"目录下. 2,执行sudo chmod 777 /usr/bin/class-dump"命令赋予其执行权限. 3,class-dump执行: clas

开源中国iOS客户端学习 (十一) AES加密

数据加密在解密在软件开发过程中举足轻重的作用,可能有的公司在加密的时候有自己公司内部一套设计的算法,而在这方面不想浪费太大精力就可以去考虑使用第三方提供的加密算法,如AES加密算法,本篇内容介绍开源中国iOS客户端使用ASE算法加密密码: AES   GitHub 下载地址  https://github.com/Gurpartap/AESCrypt-ObjC 对一个比较大的工程我们可能都不知道某个类库或者方法在哪被使用,但是智能的Xcode给我们提供了一个全局搜索的功能,我们可以在真个工程中来