iOS基础-弱引用

实现弱引用

本文将整理

  • 弱引用、强引用的定义
  • 为什么会出现“弱引用”
  • weak 实现原理
  • 实现弱引用的N种方法
  • 如何在 Block 内自由的使用self

什么是弱引用

  • 强引用:当前对象被其他对象引用时,会执行retain操作,引用计数器+1。当retainCount=0时,该对象才会被销毁。因为我们要进行对象的内存管理,所以这是默认的引用方式。(默认是强引用)

  • 弱引用:当前对象的生命周期不由其他对象引用限制,它本该什么时候销毁就什么时候被销毁。即使它的引用没断,但是当它的生存周期到了时就会被销毁。

在定义属性时,若声明为retain类型的,则就是强引用;若声明为assign类型的,则就是弱引用。后来内存管理都由ARC来完成后,若是强引用,则就声明为strong;若是弱引用,则就声明为weak。

strong / weak

多个对象共用一块地址时,当内存销毁的时候,指向这片内存地址的几个指针需要重新定义才可以使用,否则出现野指针现象。
Weak修饰的内存销毁的时候,指向该内存的指针全部销毁

weak / assign

  • weak:对象销毁之后会自动置为nil,防止野指针。
    • Delegate基本总是使用weak,以防止循环引用。特殊情况是,希望在dealloc中调用delegate的某些方法进行释放,此时如果使用weak将引起异常,因为此时已经是nil了,那么采用assign更为合适,这时需要自己手动在dealloc中给Delegate置为nil。
  • assign:基础数据类型(NSInteger,CGFloat)和C数据类型(int, float, double, char等)使用
  • __unsafe_unretained

当一个对象的引用计数为0时,所有指向该对象的weak属性指针会被自动设置为nil,而assign属性不会,如果对象被释放了,此时再进行访问,程序崩溃
weak assign 修饰的属性,超出赋值函数之外,就无地址。而weak会将指针置nil,而assign不会,所以如果在使用assign修饰的属性,会 bad access

为什么会出现“弱引用”

  1. 在强引用中,有时会出现循环引用的情况(不解释)这时就需要弱引用来帮忙(__weak)。
    Because:
    强引用持有对象,弱引用不持有对象。
    强引用可以释放对象,但弱引用不可以,因为弱引用不持有对象,当弱引用指向一个强引用所持有的对象时,当强引用将对象释放掉后,弱引用会自动的被赋值为nil,即弱引用会自动的指向nil。

  2. 应用场景
    2.1 delegate
    2.2 block

weak 实现原理

weak 变量在引用计数为0时,会被自动设置成 nil,这个特性是如何实现的?

在 Friday QA 上,有一期专门介绍 weak 的实现原理。https://mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html

《Objective-C高级编程》一书中也介绍了相关的内容。

简单来说,系统有一个全局的 CFMutableDictionary 实例,来保存每个对象的 weak 指针列表,因为每个对象可能有多个 weak 指针,所以这个实例的值是 CFMutableSet 类型。

剩下我们要做的,就是在引用计数变成 0 的时候,去这个全局的字典里面,找到所有的 weak 指针,将其值设置成 nil。如何做到这一点呢?Friday QA 上介绍了一种类似 KVO 实现的方式。当对象存在 weak 指针时,我们可以将这个实例指向一个新创建的子类,然后修改这个子类的 release 方法,在 release 方法中,去从全局的 CFMutableDictionary 字典中找到所有的 weak 对象,并且设置成 nil。我摘抄了 Friday QA 上的实现的核心代码,如下:

Class subclass = objc_allocateClassPair(class, newNameC, 0);
Method release = class_getInstanceMethod(class, @selector(release));
Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
objc_registerClassPair(subclass);

实现弱引用

1. 宏
__weak __typeof(self) weakSelf = self;
__strong __typeof(weakSelf) strongSelf = weakSelf; 
// __weak的实现上也能知道使用 __weak 是有开销的
@weakify(self);
@strongify(self);
2. 弱引用数组

https://blog.csdn.net/shaohua_lv/article/details/70257053
http://www.saitjr.com/ios/nspointerarray-nsmaptable-nshashtable.html

传统的集合类型都有哪些短板:

  • 放到集合中的对象,只能强引用
  • 如果想要弱引用,要先用 NSValue 打包
  • 不能放入 nil

2.1 使用场景
如果你想添加一个对象集合,但不想集合创建一个强引用。
如果有需求在数组保持对象的弱引用,对象移除时,数组中也随之移除。

2.2 How

2.2.1 使用 NSValue 包装
NSValue *value = [NSValue valueWithNonretainedObject:obj];

2.2.2 NSPointerArray

对应着 NSArray,先来看看 API 中介绍的特点:

  • 和传统 Array 一样,用于有序的插入或移除;
  • 与传统 Array 不同的是,可以存储 NULL,并且 NULL 还参与 count 的计算;
  • 与传统 Array 不同的是,count 可以 set,如果直接 set count,那么会使用 NULL 占位;
  • 可以使用 weak 来修饰成员;
  • 成员可以是所有指针类型;
  • 遵循 NSFastEnumeration,可以通过 for...in 来进行遍历

// TODO: 后续补充

3. 弱引用字典

3.1 使用NSValue包装value

获取value:value.nonretainedObjectValue;

3.2 NSMapTable

比起传统字典,它还有一些优势:

  • key 可以不用遵循 NSCopying 协议;
  • key 和 value 的内存管理方式可以分开,如:key 是强引用,value 是弱引用;

如何在 Block 内自由的使用self

只有当block直接或间接的被一个object持有时,在block内使用就需要weak object来避免循环引用。其中obejct当然可以是self

  1. 不持有block:Masonry / AFN
  2. block 置 nil:YTKNetwork

在 YTKNetwork 库中,我们的每一个网络请求 API 会持有回调的 block,回调的 block 会持有 self,而如果 self 也持有网络请求 API 的话,我们就构造了一个循环引用。虽然我们构造出了循环引用,但是因为在网络请求结束时,网络请求 API 会主动释放对 block 的持有,因此,整个循环链条被解开,循环引用就被打破了,所以不会有内存泄漏问题。代码其实很简单,如下所示:

- (void)clearCompletionBlock {
    // nil out to break the retain cycle.
    self.successCompletionBlock = nil;
    self.failureCompletionBlock = nil;
}

__weak 疑问

提问:为什么 masonry 的 block 里引用 self 不需要 weak?

这个就和网络请求里面使用self道理是一样的。因为UIView未强持有block,所以这个block只是个栈block,而且构不成循环引用的条件。栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,所以可以直接放心的使用self。

提问:“数组” 和 “字典” 的 enumeratXXXUsingBlock: 是否要使用 weakSelf 和 strongSelf 呢?

提问:block 里 strong self 后,block 不是也会持有 self 吗?而 self 又持有 block ,那不是又循环引用了

你可能感兴趣的:(iOS基础-弱引用)