美文网首页
Swift 循环引用

Swift 循环引用

作者: just东东 | 来源:发表于2021-02-07 17:10 被阅读0次

Swift 循环引用

[TOC]

前言

本本主要研究的是Swift中闭包捕获外部变量时产生的循环引用。全部示例是在macOS 命令行工程中。

1. 循环引用

1.1 闭包捕获外部变量

举个简单的例子

var age = 10

let clourse = {
    age += 1
}

print(age)
clourse()
print(age)

打印结果:

image

此处即使闭包捕获外部变量的最简单的例子,闭包内部对变量的修改将会改变变量的原始的值,这个与OCblock是一致的,但是OCblock中修改外部变量的值需要使用__block修饰,Swift则不需要。

1.2 循环引用

1.2.1 deinit

定义一个类,代码如下:

class Person {
    var age = 18 
    //反初始化器
    deinit {
        print("Person deinit")
    }
}

类中的deinit方法是Swift中的反初始化器,当前实例对象即将回收的时候会调用该方法,所以说我们可以通过该方法是否被调用来判断当前实例对象是否被正常释放。如果没被释放就可能存在循环引用的情况。

声明一个方法,并调用它。

func test() {
    var p = Person()
}

test()

运行结果:

image

我们可以看到在以上的代码中,正常打印了Person deinit也就是说执行了deinit方法,所以说此时p这个实例对象正常释放了,不存在循环引用。

1.2.2 在闭包中修改属性值

还是上面的类,此时我们在闭包中修改实例对象的属性值,代码如下:

let clourse = {
    p.age += 1
}
    
clourse()

运行,打印结果:

image

此时依旧打印了Person deinit,说明并没有造成循环引用。

1.2.3 循环引用

在上面的两个例子中都没有造成循环引用,下面在看个例子,我们在类中增加一个无参无返回值的闭包属性。并且在这个闭包修修改age属性的值。代码如下:

class Person {
    var age = 18
    var myClourse: (()->())?
    //反初始化器
    deinit {
        print("Person deinit")
    }
}

func test() {
    var p = Person()
    p.myClourse = {
        p.age += 1
    }
}

test()

运行,打印结果:

image

此时我们并没有看到Person deinit的打印,说明此时就造成了循环引用,此时我们的myClourse持有着p对象,而myClourse作为p对象的属性,也被p对象持有着,p对象需要等待闭包释放后才能释放,而闭包也在等待内部的p对象释放,所以就造成了循环引用。

此处引入循环引用的过程虽长,但也一步一步的解释了出现循环引用的条件,在开发过程中我们最常见的循环引用就是self强持有闭包,而闭包中又要持有self,以此来实现对self中某些属性的修改,或者方法的调用。

2. 解决循环引用的方法

循环引用会使实例对象不能够正常释放,此时就会出现一些不可预估的问题,也会造成内存的浪费,所以我们需要解决循环引用,在Swift中有两种解决循环引用的方法。分别是weakunowned。关于这两个方法的底层实现及相关知识点可以参考我的另一篇文章Swift 内存管理,本篇注重应用,对原理的介绍偏弱。

2.1 weak

func test() {
    var p = Person()
    p.myClourse = { [weak p] in
        p?.age += 1
    }
}

weak也是我们OC中的一种解决循环引用的方式,在Swift中使用weak修饰的实例变量默认为可选类型,所以在使用weakp对象会成为可选类型,在使用的时候需要使用?或者!

2.2 unowned

func test() {
    var p = Person()
    p.myClourse = { [unowned p] in
        p.age += 1
    }
}

unowned也是Swift中独有的一种解决循环引用的方式,相较于weak,使用unowned修饰后的对象不是可选类型,也不可置为nilunowned的意思是假定当前对象有值,所以就可能存在野指针的情况,所以在使用的过程中要注意使用的位置。而weak修饰的对象一旦为nil的时候,由于swift特性,可选值为nil就不会执行后续的代码,相较于unowned比较安全。

3. 捕获列表

在上文中我们提到的[weak p]和[unowned p]在swift中被称为捕获列表

捕获列表:

  • 定义在闭包的参数列表之前
  • 使用[]括起来
  • 即使是省略参数和返回值的时候也要使用in关键字
  • [weak p][unowned p]是分别使用weakunowned关键字修饰p
  • 如果不需要修饰,我们可以写成[p]

下面我们来看个例子,以下代码的执行结果是什么?

func test() {
    var age = 0
    var height = 0.0

    let clourse = { [age] in
        print(age)
        print(height)
    }
    
    age = 10
    height = 1.85
    clourse()
}

test()

打印结果:

image

根据以上的打印结果我们可以知道,对于闭包中捕获列表中的常量,闭包会捕获外部相同名称的变量或常量来初始化捕获列表中定义的常量,其有如下特点:

  1. 捕获列表中的是常量,不可修改
  2. 该常量是值拷贝,相当于复制了一份外部变量或常量
  3. 既然是常量就是不可修改的

如果我们修改会报编译错误:

image

关于此处的原理将会在闭包相关文章中进行分析,也可以先看看OCBlock底层原理

相关文章

  • Swift--内存管理

    Swift内存管理概述 强引用循环 打破强引用循环 闭包中的强引用循环 Swift内存管理概述 Swift中的AR...

  • Day2

    1 单方向的引用不会产生循环引用。循环引用:闭包引用了self,self引用了闭包。Swift推荐使用解决循环引用...

  • Swift与OC真正去理解Block解决循环引用的技巧

    Swift与OC真正去理解Block解决循环引用的技巧 Swift与OC真正去理解Block解决循环引用的技巧

  • 解决swift Block内的循环引用

    解决swift Block内的循环引用

  • Swift 循环引用

    Swift 循环引用 [TOC] 前言 本本主要研究的是Swift中闭包捕获外部变量时产生的循环引用。全部示例是在...

  • 2019-12-04

    swift 闭包循环引用产生以及处理

  • swift循环引用

    ARC仅仅能对类的实例做内存管理,也就是只能针对引用类型.结构体和枚举都是值类型,不能通过引用的方式来传递和存储,...

  • Swift 循环引用

    简介 Swift 使用 Automatic Reference Counting (ARC) 管理应用内存的使用,...

  • Swift 循环引用

    在闭包里面嵌套闭包的时候 [weak self] 必须处于第一个闭包 否则会造成循环引用 例: weak self...

  • Swift 循环引用

    unownedunowned要求被捕获的变量不能为nil,所以在closure中使用[unowned self] ...

网友评论

      本文标题:Swift 循环引用

      本文链接:https://www.haomeiwen.com/subject/wziztltx.html