swift(16)自动引用计数ARC

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

ARC:Automatic Reference Counting

ARC自动管理内存,原理就是类对象的指针被引用时计数加,被释放时计数减,如果为0则自动释放。

类的对象的内存一旦被ARC释放,则该对象不能再被使用,否则你的程序会崩溃或跑飞了

强引用:一个类的对象被赋值给一个变量,则这个变量对这个类对象的地址有强引用。

有强引用,则就有弱引用,被weak修饰的变量对赋值的类对象有弱引用。

先看一个例子来理解ARC是如何工作的,这时候析构函数派上用场了,析构的时候打印一句话,以让你知道它的实体被释放了

class Person {
    let name: String
    init(name: String) {
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }
}
//定义了4个可选变量,当变量被赋值为nil时,表示释放
var reference1: Person?
var reference2: Person?
var reference3: Person?

//创建对象,ARC+1
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized"

//赋值,ARC+1+1
reference2 = reference1
reference3 = reference1

//释放连个ARC-1-1,释放两个后,还有一个reference3 对Person对象John Appleseed有强引用,所以不会释放
reference1 = nil
reference2 = nil
//释放reference3 后,ARC再-1,此时变为0,Person对象John Appleseed内存被释放归还操作系统
reference3 = nil
// prints "John Appleseed is being deinitialized"

上面是最简单和最正常的情况,但是在实际应用中类之间往往有复杂的关系,可能会相互包含,相互之间产生强引用。比如下面的例子:住户和房子

住户john有房子,因此住户类有一个房子类型的变量number73

房子number73也有住户,因此房子类有一个住户类型的变量john

定义住户和房子,ARC计数都是1,然后相互赋值,ARC计数都是2。

此时释放john,他的ARC减1,但是房子number73的属性对john有强引用,所以john的内存没能被自动回收。

同样的道理,释number73,他的ARC减1,但是number73的内存被john强引用,也没有被回收内存,此时产生了内存泄露。

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}
 
class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

var john: Person?
var number73: Apartment?

//看下图,两个竖着的箭头strong
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

//下图两个横着的箭头
john!.apartment = number73
number73!.tenant = john

//去掉了两个竖着的箭头,但是横着的箭头还是存在的,所以无法自动释放内存
john = nil
number73 = nil

swift(16)自动引用计数ARC_第1张图片

我觉得有一个方法,就是在释放john前,将number73的tenant属性置为nil。但是上例还是一个简单,关系很明确的例子,在实际应用中对john的引用可能不止一个类的一个对象,难道要挨个释放吗,这肯定是不可能的,所以引入了弱引用。

使用weak修饰的变量,对其赋值时,被赋值的对象ARC计数器不加。看修改后的例子:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}
 
class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    //就多了下面一个单词weak
    weak var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

var john: Person?
var number73: Apartment?

//john的ARC+1,number73的ARC+1
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

number73的ARC = 2,john的ARC不变
john!.apartment = number73
number73!.tenant = john

//john的ARC=0,被释放,同时其apartment成员被释放,因此number73的ARC-1
john = nil
// prints "John Appleseed is being deinitialized"
//number73的ARC再减1,变为0,内存也被回收
number73 = nil
// prints "Apartment #73 is being deinitialized"

swift(16)自动引用计数ARC_第2张图片

上图的weak表示弱引用,我觉得可理解为ARC不加计数

还有一种解决上述问题的方式是无主引用,用法类似weak,关键字是unowned,我觉得是不拥有的意思吧,就是类实例虽然赋值给我了,但是我不拥有你的强引用,不对你的ARC有影响。

unowned变量不能声明为可选变量,也就是它不能被赋值为nil,并且他一旦存在时就得有值。

如果调用被释放了内存的unowned的变量,将发生运行时错误。

下面的例子是用户和银行卡的关系,银行卡肯定是有用户的,如果该用户被释放,则银行卡对象也被释放。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { println("\(name) is being deinitialized") }
}
 
class CreditCard {
    let number: Int
    unowned let customer: Customer
    init(number: Int, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { println("Card #\(number) is being deinitialized") }
}

var john: Customer?

john = Customer(name: "John Appleseed")
//银行卡被创建时,就有用户,银行卡的ARC是1,银行卡的customer对john的ARC没有影响,john的ARC仍然是1
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

//用户释放时,卡的ARC减1,也被释放了
john = nil
// prints "John Appleseed is being deinitialized"
// prints "Card #1234567890123456 is being deinitialized"

swift(16)自动引用计数ARC_第3张图片

weak是两个类的属性都可以被设置为nil,unowned的是其中一个类的属性不能设置为nil,如果两个类的属性都不能设置为nil该怎么办呢,方法是一个使用无主引用,另一个使用隐式展开的可选属性(就是肯定有值,但是声明时初始值为nil,值是构造函数创建的)

class Country {
    let name: String
    //可选属性,!表示这个属性肯定是可用的,但是初始值是nil,目的是可以在构造函数中提前使用self
    let capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        //由于capitalCity 有初始值是nil,那么已经被赋值,类的所有属性都有初值,所以可以使用self
        self.capitalCity = City(name: capitalName, country: self)
    }
}
 
class City {
    let name: String
    //定义为无主的属性
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

var country = Country(name: "Canada", capitalName: "Ottawa")
println("\(country.name)'s capital city is called \(country.capitalCity.name)")
// prints "Canada's capital city is called Ottawa"

关于闭包的强引用环,如果将一个闭包赋值给一个类的属性,并且这个闭包也是用这个类的实例,则产生闭包的强引用环,如下例:

class HTMLElement {
    
    let name: String
    let text: String?
    //lazy类型的变量肯定在类实例初始化完成后才可使用
    @lazy var asHTML: () -> String = {
        //多次用到了自身实例self,所以产生了强引用,但是只有一次
        if let text = self.text {
            return "<\(self.name)>\(text)"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        println("\(name) is being deinitialized")
    }
    
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "

hello, world

" paragraph = nil //没有释放

解决这种强引用环使用占有列表

@lazy var someClosure: (Int, String) -> String = {
    [unowned self] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}
//或者省略参数
@lazy var someClosure: () -> String = {
    [unowned self] in
    // closure body goes here
}
//都是表示不占用self

上述问题的解决办法:

class HTMLElement {
    
    let name: String
    let text: String?
    
    @lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        println("\(name) is being deinitialized")
    }
    
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "

hello, world

" paragraph = nil // prints "p is being deinitialized"

swift(16)自动引用计数ARC_第4张图片







转载于:https://my.oschina.net/carlcheer/blog/286995

你可能感兴趣的:(swift(16)自动引用计数ARC)