前言

从大二的开始接触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
原文链接

点此

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

  1. iOS开发——总结篇&关键字介绍

    关键字介绍 在做iOS开发中,系统的各个关键字处处都是,但是未必每一个关键字都是那么的明白,所以罗列出一些关键字供大家互相学习,有不完善之处请指正,谢谢 atomic atomic是Objc使用的一种 ...

  2. 开源中国iOS客户端学习

    开源中国iOS客户端学习 续写前言 <开源中国iOS客户端学习>续写前系列博客    http://blog.csdn.net/column/details/xfzl-kykhd.html ...

  3. iOS动画学习-视觉效果

    CALayer不仅仅是iOS动画学习-CALayer中介绍的那些内容,他还有一些其他属性,比如shadowColor,borderWidth,borderColor等等,这些属性我们只需要简单点设置就 ...

  4. 李洪强iOS经典面试题36-简单介绍 ARC 以及 ARC 实现的原理

    李洪强iOS经典面试题36-简单介绍 ARC 以及 ARC 实现的原理   问题 简单介绍 ARC 以及 ARC 实现的原理. 考查点 ARC 是苹果在 WWDC 2011 提出来的技术,因此很多新入 ...

  5. 李洪强iOS经典面试题32-简单介绍 ARC 以及 ARC 实现的原理

    李洪强iOS经典面试题32-简单介绍 ARC 以及 ARC 实现的原理 问题 简单介绍 ARC 以及 ARC 实现的原理. 考查点 ARC 是苹果在 WWDC 2011 提出来的技术,因此很多新入行的 ...

  6. iOS开发UI篇—Modal简单介绍

    iOS开发UI篇—Modal简单介绍 一.简单介绍 除了push之外,还有另外一种控制器的切换方式,那就是Modal 任何控制器都能通过Modal的形式展⽰出来 Modal的默认效果:新控制器从屏幕的 ...

  7. ios网络学习------6 json格式数据的请求处理

    ios网络学习------6 json格式数据的请求处理 分类: IOS2014-06-30 20:33 471人阅读 评论(3) 收藏 举报 #import "MainViewContro ...

  8. ios开发之OC基础-ios开发学习路线图

    本系列的文章主要来自于个人在学习前锋教育-欧阳坚老师的iOS开发教程之OC语言教学视频所做的笔记,边看视频,边记录课程知识点.建议大家先过一遍视频,在看视频的过程中记录知识点关键字,把把握重点,然后再 ...

  9. 原 iOS深入学习(Block全面分析)http://my.oschina.net/leejan97/blog/268536

    原 iOS深入学习(Block全面分析) 发表于1年前(2014-05-24 16:45)   阅读(26949) | 评论(14) 39人收藏此文章, 我要收藏 赞21 12月12日北京OSC源创会 ...

随机推荐

  1. php 访问对象的属性(当属性的键名称为数字时)的方法 {}

    这篇文章下面写了一个 将 数组转化成对象的方法, 这个数组的键为数字(即数组为索引数组), 访问对象的时候, $obj->1 显然错位,那到底该怎么样访问对象的数字属性呢? $obj->{ ...

  2. 前端教程&开发模块化/规范化/工程化/优化&工具/调试&值得关注的博客/Git&面试-资源汇总

    内容精简 资源这么多,多看看多学习再总结肯定是好的.多读读就算看重了不算浪费时间,毕竟一千个读者就有一千个林黛玉,还有温故而知新,说不定多读一些内容,就发现惊喜了呢.不过,在此也精简一些内容,就1~2 ...

  3. Web 网站 故障常用分析命令

    系统连接状态篇: 1.查看TCP连接状态 netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn netstat -n | awk '/^tcp/ { ...

  4. 模块的_name_

    模块的__name__每个模块都有一个名称,在模块中可以通过语句来找出模块的名称.这在一个场合特别有用——就如前面所提到的,当一个模块被第一次输入的时候,这个模块的主块将被运行.假如我们只想在程序本身 ...

  5. LAMP优化

    LAMP系统优化是非常必要的,一个好的优化能使系统运作的越快,从而提高工作效率,下面我将从几方面给大家详细介绍下LAMP系统优化的内容. 一.硬件优化 1.升级硬件的一般规则:对于 PHP 脚本而言, ...

  6. Ubuntu apt-get 详解

    一.常用的APT命令参数: apt-cache search package 搜索软件包 apt-cache show package  获取包的相关信息,如说明.大小.版本等 sudo apt-ge ...

  7. Spring ---annotation (重点)--AutoWired 不常用

    1. 默认按类型 by type, 如果想用byname, 使用@Qualifier 2. 如果写在set上, @qualifier需要写在参数上 bean.xml: 默认bytype去找set方法, ...

  8. 四、Hbase

    一.什么情况下使用Hbase 例子: 这里Order By无时不刻的处理,我们要看到刚才的足迹,不能使用缓存技巧. 根据时间戳来查询,显然很快,应为Hbase就是以时间戳来存的. 将最近的数据放在内存 ...

  9. hadoop如何查看文件系统

    1.查看当前的文件系统 [root@hadoopmaster bin]# . itemsdrwxr 00 00 /user 当然也可以以浏览器中这样查看localhost:50070   这就是had ...

  10. lpc1768的rit使用

    LPC1768在系统滴答定时器和通用定时器之外还引入了一个定时器,叫做重复定时器RIT,该定时器只能用于定时操作,带有一个中断,我个人的感觉,这似乎是为了延时函数设计的一个定时器 那么使用该定时器时遵 ...