您的位置:首页 > 移动开发 > Swift

swift(16)自动引用计数ARC

2014-07-03 16:46 387 查看
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




我觉得有一个方法,就是在释放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"




上图的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"




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)</\(self.name)>"
} 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 "<p>hello, world</p>"

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)</\(self.name)>"
} 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 "<p>hello, world</p>"

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


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: