版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.07.31 |
前言
我是swift2.0的时候开始接触的,记得那时候还不是很稳定,公司的项目也都是用oc做的,并不对swift很重视,我自己学了一段时间,到现在swift3.0+已经出来了,自己平时也不写,忘记的也差不多了,正好项目这段时间已经上线了,不是很忙,我就可以每天总结一点了,希望对自己对大家有所帮助。在总结的时候我会对比oc进行说明,有代码的我会给出相关比对代码。
1. swift简单总结(一)—— 数据简单值和类型转换
2. swift简单总结(二)—— 简单值和控制流
3. swift简单总结(三)—— 循环控制和函数
4. swift简单总结(四)—— 函数和类
5. swift简单总结(五)—— 枚举和结构体
6. swift简单总结(六)—— 协议扩展与泛型
7. swift简单总结(七)—— 数据类型
8. swift简单总结(八)—— 别名、布尔值与元组
9. swift简单总结(九)—— 可选值和断言
10. swift简单总结(十)—— 运算符
11. swift简单总结(十一)—— 字符串和字符
12. swift简单总结(十二)—— 集合类型之数组
13. swift简单总结(十三)—— 集合类型之字典
14. swift简单总结(十四)—— 控制流
15. swift简单总结(十五)—— 控制转移语句
16. swift简单总结(十六)—— 函数
17. swift简单总结(十七)—— 闭包(Closures)
18. swift简单总结(十八)—— 枚举
19. swift简单总结(十九)—— 类和结构体
20. swift简单总结(二十)—— 属性
21. swift简单总结(二十一)—— 方法
22. swift简单总结(二十二)—— 下标脚本
23. swift简单总结(二十三)—— 继承
24. swift简单总结(二十四)—— 构造过程
25. swift简单总结(二十五)—— 构造过程
26. swift简单总结(二十六)—— 析构过程
自动引用计数
和OC
一样,swift
也有引用自动引用计数,用来管理内存。引用计数仅仅应用于类的实例。结构体和枚举类型都是值类型,不是引用类型,也不是通过引用的方式存储和传递。
主要从下面几个方向讲述。
- 自动引用计数的工作机制
- 自动引用计数实践
- 类实例之间的循环强引用
- 解决实例之间循环强引用
- 闭包引起的循环强引用
- 解决闭包引起的循环强引用
自动引用计数的工作机制
和OC
一样,swift
中为了确保使用中的实例不被销毁,ARC会跟踪和计算每一个实例正在被多少属性、常量和变量引用,只要引用计数器不为1,ARC就不会销毁这个实例。无论是将实例赋值给常量、变量或者属性,都会对此实例创建强引用,将这个实例牢牢抓住
。
自动引用计数实践
先看一个代码。
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
在Person
类里面定义了构造函数和析构函数。下面定义三个类型为Person
的变量,用来按照代码片段中顺序,为新的Person
实例建立引用,如下所示:
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
这里要注意,这些变量被定义为可选类型Person?
不是Person
,它们的值会被自动初始化为nil
,目前还不会引用到Person
类的实例。
下面我们建立实例。
class JJPracticeVC: UIViewController {
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
reference1 = Person(name: "John")
reference2 = Person(name: "Rose")
reference3 = Person(name: "Nick")
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
下面我们看一下
John is being initialized
Rose is being initialized
Nick is being initialized
可见析构函数没有调用,也就是三个对象都没有销毁,想要解除强引用,只需要给相关对象赋值为nil
即可。
class JJPracticeVC: UIViewController {
var reference1 : Person?
var reference2 : Person?
var reference3 : Person?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
reference1 = Person(name: "John")
reference2 = Person(name: "Rose")
reference3 = Person(name: "Nick")
reference2 = nil
reference3 = nil
}
}
下面看输出结果
John is being initialized
Rose is being initialized
Nick is being initialized
Rose is being deinitialized
Nick is being deinitialized
可见,reference2
和reference3
被销毁了。
类实例之间的循环强引用
循环引用的概念与OC
中是一样的,如果链各个类实例之间互相保持对方的强引用,并让对方不被销毁,这就是所谓的循环强引用。在swift
中解除强引用的方法就是通过定义类之间的关系为弱引用或者无主引用,以此替代强引用,从而解决循环强引用的问题。
下面就给出一个强引用的例子。
class JJPracticeVC: UIViewController {
var john : Person?
var number : Apartment?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Person(name: "John")
number = Apartment(number: 73)
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
var apartment : Apartment?
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number
}
var tenant : Person?
deinit {
print("Apartment \(number) is being deinitialized")
}
}
下面看输出结果
John is being initialized
看一下效果图。
他们的析构函数都没有被调用,也就是说他们有了强引用,无法释放对象。
解决实例之间的循环强引用
swift
提供了两种方法用来解决你在使用类的属性时所遇到的循环强引用问题:
- 弱引用
weak reference
- 无主引用
unowned reference
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用,这样能够互相引用而不产生强引用。
对于生命周期中变为nil
的实例使用弱引用,相反的,对于初始化赋值后再也不会被赋值为nil
的实例,使用无主引用。
1. 弱引用
弱引用不会牢牢保持住引用的实例,并且不会阻止ARC
销毁被引用的实例,这种行为阻止了引用变为循环强引用,声明属性或者变量时,在前面加上weak
关键字表明这是一个弱引用。
弱引用还需要注意下面几点
- 在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以阻止循环强引用,如果引用总是有值,则可以使用无主引用。在上面
Apartment
的例子中,一个公寓的生命周期中,有时候是没有居民的,所以适合使用弱引用来解决循环强引用。 - 弱引用必须生命为变量,表明其值能在运行时被修改,弱引用不能被声明为常量。
- 因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。
下面看一下例子。
class JJPracticeVC: UIViewController {
var john : Person?
var number : Apartment?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = nil
number = nil
john = Person(name: "John")
number = Apartment(number: 73)
john!.apartment = number
number?.tenant = john
}
}
class Person {
let name : String
init(name : String) {
self.name = name
print("\(name) is being initialized")
}
var apartment : Apartment?
deinit {
print("\(name) is being deinitialized")
}
}
class Apartment {
let number : Int
init(number : Int) {
self.number = number
}
weak var tenant : Person?
deinit {
print("Apartment \(number) is being deinitialized")
}
}
由于一端用weak修饰,所以现在二者效果如下:
可见二者之间不会相互强引用了,现在将他们之间的一个对象设置为nil
,二者都会打印销毁信息,二者引用关系如下所示。
2. 无主引用
和弱引用不同的是,无主引用永远是有值的,因此无主引用总是被定义为非可选类型,你可以在声明常量或者变量时,在前面加上关键字unowned
。
还要注意:
- 如果你试图在实例被销毁以后,访问该实例的无主引用,会触发运行时错误,使用无主引用,你必须确保引用始终指向一个未销毁实例。还有,如果你试图访问实例已经被销毁的无主引用,程序会直接崩溃。
下面定义两个类Customer
和CreditCard
,但是这个和前面公寓和人的关系不一样,这个模型中,一个客户可能有也可能没有信用卡,但是一个信用卡一定关系一个客户,所以Customer
类有一个可选类型card
属性,但是CreditCard
类有一个非可选类型的customer
属性。
下面看一下代码。
class JJPracticeVC: UIViewController {
var john : Customer?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
}
}
class Customer {
let name : String
var card : CreditCard?
init(name : String) {
self.name = name
}
deinit {
print("\(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 {
print("Card \(number) is being deinitialized")
}
}
这里,Customer
实例持有对CreditCard
实例的强引用,而CreditCard
实例持有对Customer
的无主引用,具体如下所示。
由于Customer
的无主引用,当你断开john
变量持有的强引用时,再也没有指向customer
实例的强引用了,如下图所示。
class JJPracticeVC: UIViewController {
var john : Customer?
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
john = Customer(name: "John")
john!.card = CreditCard(number: 1234_5678_2333, customer: john!)
john = nil
}
}
加一句john = nil
,我们看一下输出结果
John is being deinitialized
Card 123456782333 is being deinitialized
可见二者都被释放了。
3. 无主引用以及隐式解析可选属性
上面打破循环引用给出了两种情况“
-
Person
和Apartment
展示了两个属性的值都允许为nil
,并可能产生循环引用,这种情况适合采用弱引用。 -
Customer
和CreditCard
展示了一个属性的值可以为nil,另外一个不可以为nil
的情况,并可能产生循环引用,这种情况适合采用无主引用。
还存在另外一种情况:两个属性都必须有值,并且初始化完成后不能为nil
,在这种情况下,需要一个类使用无主属性,另外一个类使用隐式解析可选类型。
下面看一个简单例子,每一个Country
国家都要有首都city
,每一个城市也必须属于一个国家。
class Country {
let name : String
var capitalCity : City!
init(name : String, capitalName : String) {
self.name = name
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
}
}
上面的意义在于你可以通过一条语句同时创建City
和Country
的实例,而不产生循环引用,并且capitalCity
的属性能被直接访问,而不需要通过感叹号来展开它的可选值。
闭包引起的循环强引用
这种情况有点类似OC
中的block
引用,循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例,这就容易引起循环引用。
swift
提供了一种优雅的方式解决这个问题,称为闭包占用列表(closure capture list)
,下面我们看一下闭包中循环引用是如何产生的。
class HTMLElement {
let name : String
let text : String?
lazy var asHTML : () -> String = {
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 {
print("\(name) is being deinitialized")
}
}
注意:这里asHTML
声明为lazy
属性,因为只有当元素确实需要处理为HTML输出时,才需要使用asHTML
,也就是说,在默认的闭包中可以使用self
,因为只有当初始化完成以及self
确实存在后,才能访问lazy
属性。
下面调用一下
var paragraph : HTMLElement? = HTMLElement(name: "p", text: "Hello,world")
下面看一下循环引用的示意图。
这里即使将paragraph = nil
设置,也不会调用析构器。
解决闭包引起的循环引用
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环引用,捕获列表定义了闭包体内捕获一个或者多个类型的规则,跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或者无主引用,具体选择哪个根据代码关系确定。
注意:swift
要求只要在闭包内使用 self
的成员,就要用self.someProperty
或者self.someMethod
。
1. 定义捕获列表
捕获列表中的每个元素都是由weak
或者unowned
关键字和实例的引用成对组成,每一对都在方括号中,通过逗号分开。
捕获列表放在闭包函数参数列表和返回类型之前。
lazy var someClosure : (Int ,Int) -> String = {
[unowned self](index : Int, stringToProcess : String) -> String in
//closure body goes here
}
如果闭包没有指定参数列表或返回类型,则可以通过上下文判断,那么可以捕获列表放在闭包开始的地方,跟着关键字in
。
lazy var someClosure : (Int ,Int) -> String = {
[unowned self] in
//closure body goes here
}
2. 弱引用和无主引用
当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用,相反,当捕获引用有时可能会是nil
时,将闭包内的捕获定义为弱引用,弱引用总是可选类型。并且当引用的实例被销毁后,弱引用会被自动设置为nil
,这使我们可以在闭包内检查它们是否存在。
注意如果捕获的引用绝对不会置为nil
,应该用无主引用,而不是弱引用。
下面看一下简单例子。
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 {
print("\(name) is being deinitialized")
}
}
这样子就解除了闭包引起的循环引用。
后记
未完,待续~~~~