返回 导航

Swift

hangge.com

Swift - 内存泄漏原因(循环强引用)及解决办法

作者:hangge | 2015-05-22 15:39
Swift使用自动引用计数(ARC)来管理应用程序的内存使用。在大多是情况下,并不需要考虑内存的管理。当实例不再需要的时候,ARC会自动释放这些实例所使用的内存。
但ARC并不是绝对安全的。下面两种情况会发生内存泄露。

1,类实例之间的循环强引用
两个类实例都有一个强引用指向对方,这样的情况就是强引用循环,从而导致内存泄露。
class Teacher {
    var tName : String
    var student : Student?
    
    init(name:String){
        tName = name
        println("老师\(tName)实例初始化完成")
    }
    
    deinit{
        println("老师\(tName)实例反初始化完成")
    }
}

class Student {
    var sName : String
    var teacher : Teacher?
    
    init(name:String){
        sName = name
        println("学生\(sName)实例初始化完成")
    }
    
    deinit{
        println("学生\(sName)实例反初始化完成")
    }
}

//测试开始
var teacher:Teacher?
var student:Student?
teacher = Teacher(name: "李老师")
student = Student(name: "刘同学")
teacher!.student = student
student!.teacher = teacher        
teacher = nil
student = nil

//测试结果(deinit未调用,则内存泄露)
老师李老师实例初始化完成
学生刘同学实例初始化完成

解决办法:使用弱引用
只需要将上述例子Teacher类的student变量加上关键字weak,或者将Student类的teacher变量加上关键字weak。
当A类中包含有B类的弱引用的实例,同时,B类中存在A的强引用实例时,如果A释放,也不会影响B的释放。但A的内存回收要等到B的实例释放后才可以回收。
class Teacher {
    var tName : String
    weak var student : Student?
    
    init(name:String){
        tName = name
        println("老师\(tName)实例初始化完成")
    }
    
    deinit{
        println("老师\(tName)实例反初始化完成")
    }
}

class Student {
    var sName : String
    var teacher : Teacher?
    
    init(name:String){
        sName = name
        println("学生\(sName)实例初始化完成")
    }
    
    deinit{
        println("学生\(sName)实例反初始化完成")
    }
}

2,闭包引起的循环强引用 
将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例,也会发生强引用循环。
class JsonElement{
    let name:String
    let jValue:String?
    
    lazy var asJson:() -> String = {
        if let text = self.jValue {
            return "\(self.name):\(text)"
        }else{
            return "text is nil"
        }
    }
    
    init(name:String, text:String){
        self.name = name
        self.jValue = text
        println("初始化闭包")
    }
    
    deinit{
        println("闭包释放")
    }
}

//开始测试
var p:JsonElement? = JsonElement(name: "p", text: "hangge.com")
println(p!.asJson())
p = nil

//测试结果(deinit未调用,则内存泄露)
初始化闭包
p:hangge.com

解决办法:使用闭包捕获列表
当闭包和实例之间总是引用对方并且同时释放时,定义闭包捕获列表为无主引用。但捕获引用可能为nil时,定义捕获列表为弱引用。弱引用通常是可选类型,并且在实例释放后被设置为nil。
class JsonElement{
    let name:String
    let jValue:String?
    
    lazy var asJson:() -> String = {
        [unowned self] in //使用无主引用来解决强引用循环
        if let text = self.jValue {
            return "\(self.name):\(text)"
        }else{
            return "text is nil"
        }
    }
    
    init(name:String, text:String){
        self.name = name
        self.jValue = text
        println("初始化闭包")
    }
    
    deinit{
        println("闭包释放")
    }
}
评论

全部评论(0)

回到顶部