避免在Swift Struct中使用闭包

为什么我们应该避免在结构体使用闭包

我们所有人都喜欢闭包,你难道不喜欢吗? Closure能够让iOS开发者生活更轻松。如果它让我们更轻松了,那为啥我还要说不在在结构体中使用闭包了,原因就是:“内存泄露和发生不可预料的事”,你会不会有为啥Swift结构体会发生内存泄露问题的疑问?

Swift 结构体是值类型,按道理不会发生内存泄露,事实真的如此吗?我们已经有很多疑问了,接下来我们看看Swift基本内存管理吧。

基本类型

在解决主要问题之前,我要强调基本类型。
Swfit 主要有两个基本类型:引用类型和值类型,比如类就是引用类型,而结构体和枚举都是值类型。

值类型

值类型数据直接存储在内存,每个实例有唯一的复制数据,当变量赋值给存在的变量时,数据就会被复制。值类型的内存分配是在堆栈(stack)中完成。当值类型变量超出作用范围,内存分配就会发生。

struct Person {
    var name : String
}
var oldPerson = Person(name: "Rizwan")
var newPerson = oldPerson
newPerson.name = "Oh my Swift"
print(oldPerson.name)
print(newPerson.name)

-------
Output:
Rizwan
Oh my Swift
-------

我们可以看到,newPerson变量的改变不会引起oldPerson的改变。这就是值类型的特性。

引用类型

引用类型在初始化时保留数据的引用(指针)。无论这个变量被分配到哪个已存在的引用类型上,这个引用共享变量值,引用类型的内存分配是在堆(heap)中完成.由自动引用计数(ARC)管理引用类型变量的内存。

class Person {
    var name: String
    init(withName name: String){
        self.name = name
    }
}
var oldPerson = Person(withName: "Rizwan")
var newPerson = oldPerson
newPerson.name = "Oh my Swift"
print(oldPerson.name)
print(newPerson.name)


------
Output
Oh my Swift
Oh my Swift
------ 

我们可以看到, olaPerson变量受newPerson的改变而改变,这就是引用类型的特性。

通常,内存泄露发生在引用类型。大多数情况发生在循环引用,想知道循环引用,可以阅读这篇博客
如果引用类型引起循环引用,我们可以尝试使用值类型去解决这个问题。

但是,这个也不是方法,有时候枚举和结构体能作为引用类型的参考,有时候循环引用也会在枚举和结构体中发生。

闭包-是结构体中的反面教材

当你在闭包中使用结构体,就像在引用类型中使用闭包时,就会发生问题。闭包时拥有外部环境的引用,以便在执行闭包主体时修改引用外部环境的值。

有这样一个例子,我们使用weak self来解决循环引用。如果我们尝试在结构体中去这样做,我们就会得到如下编译错误:'weak self' many noly be applied to class add class-bound protocol type, not '{struct name}'(意思就是weak self只能用于类或者类协议)

struct Car {
    var speed: Float = 0.0
    var increaseSpeed: (() -> ())?
}
var myCar = Car()
myCar.increaseSpeed = {
    myCar.speed += 30 // The retain cycle occurs here. We cannot use [weak myCar] as myCar is a value type.
}
myCar.increaseSpeed?()
print("My car's speed :")
print(myCar.speed) // Prints 30

var myNewCar = myCar
myNewCar.increaseSpeed?()
myNewCar.increaseSpeed?()
print("My new car's speed :")
print(myNewCar.speed) // Prints 30 still! 

你大概期待myNewCar结果是90.0,但是数据为30(而且引起循环引用了)

为什么

原因就是 myNewCar 只是 newCar 的部分复制,然而闭包和他的外部环境不能被完成复制。speed值被复制,但是myNewCar的属性increaseSpeed持有的是myCar的increaseSpeed的环境,即myCar的speed环境。所有myCar的increaseSpeed被调用。

这就是为什么在swift中定义闭包使用是危险的。

那我们应该怎么解决

最直接的办法是避免在结构体内定义闭包使用。如果你想使用,你应该理解非常小心,可能会出现一些预料之外的问题。对于循环引用问题,唯一的方法是设置myCar和myNewCar为nil,听起来不好,但是这也是无赖之举。

当我知道闭包在值类型中使用会有如此危险时,是如此令人深思。我希望你也有这种感受。

参考

[1] https://forums.swift.org/t/avoiding-unbreakable-reference-cycle-with-value-types-and-closures/18757/6

[2] https://github.com/Wolox/ios-style-guide/blob/master/rules/avoid-struct-closure-self.md

[3] https://www.objc.io/issues/16-swift/swift-classes-vs-structs/

[4] https://marcosantadev.com/capturing-values-swift-closures/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值