Swift 内存管理(2.1 高级)
内容列表
深浅拷贝
字符串拷贝
集合类的拷贝
局部临时对象和全局对象
类型属性的声明周期
隐式强引用 - 集合类,timer,元组
闭包属性引起的循环强引用
解决闭包属性的强引用问题
详细内容
1. 深浅拷贝
深拷贝与浅拷贝这一概念在iOS开发中比较重要,这两个概念之前的区别在于:深拷贝是将新开辟一块空间将某一对象复制过来放进去,也就是说旧的和新的一模一样,但是是两个独立的个体。相互之间互不影响,其中一个对象改变其中的值。另一个对象并不会受到影响。
浅拷贝是开辟一块内存空间,但是里面放的并不是新复制的对象。而是放的指向被复制对象的地址的指针(objective-c中的说法,一下均简称OC),换句话说新开辟的空间里面并没有保存新的拷贝对象,也就是说,当改变对象其中的属性时,拷贝的内容也会跟着改变。
代码演示
<!--结构体。值类型-->
struct DeepCopy {
var copy : Int = 0
}
<!--类,引用类型-->
class ShallowCopy {
var copy : Int = 0
}
var d0 = DeepCopy(copy: 90)
var d1 = d0
d1.copy = 9
print("d0.copy : \(d0.copy)")
print("d1.copy : \(d1.copy)")
print("----分行线----")
var s0 = ShallowCopy()
s0.copy = 2
var s1 = s0
print("s0.copy: \(s0.copy)")
print("s1.copy: \(s1.copy)")
打印信息
d0.copy : 90
d1.copy : 9
----分行线----
s0.copy: 2
s1.copy: 2
从结果可以看出:值类型的赋值操作为深拷贝,引用类型的赋值操作为浅拷贝。正因为应用类型的赋值操作为浅拷贝,所以才需要内存管理
2. 字符串拷贝
首先针对字符串拷贝在OC中内容较多,涉及知识点颇广,这里不做扩展描述,只介绍Swift字符串的拷贝介绍,在Swift中有自己的String类型字符串,也有OC类型的NSString类型。针对不一样的类型字符串的拷贝方式不同。具体如何不同请看下面代码。
代码演示
var swiftStr : String = "Hello"
var swiftStr1 = swiftStr
swiftStr1 += " World"
print("swiftStr : \(swiftStr)")
print("swiftStr1 : \(swiftStr1)")
print("---分行线----")
var ocStr = NSMutableString(string: "Hello")
var ocStr1 = ocStr
ocStr1.insert(ocStr as String, at: ocStr.length)
print("ocStr : \(ocStr)")
print("ocStr1 : \(ocStr1)")
打印信息
<!--改变一个值-->
swiftStr : Hello
<!--另一个并没有变化-->
swiftStr1 : Hello World
---分行线----
<!--改变一个值-->
ocStr : HelloHello
<!--另一个跟着变化了-->
ocStr1 : HelloHello
从结果可以看出Swift的字符串String类型是深拷贝,而OC中常使用的字符NSMutabliString的类型是浅拷贝,原因究其根本的话需要点击到String和NSMutabliString里面查看一下(快捷键是 command + 鼠标左键 + 需要查看的内容)
点开后的信息
Swift中的String是结构体,值类型,根据上面的阐述,值类型都是深拷贝
使用OC中常使用的NSString类以及其子类NSMutableString,都是类,引用类型,根据上面的阐述,引用类型都是浅拷贝
3. 集合的拷贝
对于此处结合我只介绍最简单,最粗浅的数组与字典,至于数组里面放置的内容是值类型或者是引用类型,此处先不做过深探讨,后续会有介绍,同样的此处的介绍也分为Swift与Foundation框架内的类区分介绍。
代码演示
1. 数组的拷贝
<!--Swift数组-->
var swiftArray : Array <Int> = [1,2,3]
var swiftArray1 = swiftArray
swiftArray1 += [4]
print( "swiftArray1 : \(swiftArray1)")
print("swiftArray : \(swiftArray)")
<!--OC常用数组-->
var ocArray = NSMutableArray(array: [1, 2, 3 , 4])
var ocArray1 = ocArray
ocArray1.add(5)
print("ocArray:\(ocArray)")
print("ocArray1:\(ocArray1)")
打印信息
<!--Swift的原生数组为深拷贝-->
swiftArray1 : [1, 2, 3, 4]
swiftArray : [1, 2, 3]
<!--OC中常用的数组为浅拷贝-->
ocArray:(
1,
2,
3,
4,
5
)
ocArray1:(
1,
2,
3,
4,
5
)
2. 字典拷贝
<!--Swift原生的字典-->
var swiftDict : Dictionary = [1 : "1" , 2 : "2"]
var swiftDict1 = swiftDict
swiftDict[3] = "3"
print("swiftDict1 :\(swiftDict1)")
print("swiftDict :\(swiftDict)")
<!--OC常使用的字典-->
var ocDict = NSMutableDictionary(dictionary: [1 : "1", 2 : "2", 3 : "3"])
var ocDict1 = ocDict
ocDict1.addEntries(from: [4 : "4"])
print("ocDict1 :\(ocDict1)")
print("ocDict : \(ocDict)")
打印信息
<!--Swift原生的字典-->
swiftDict1 :[2: "2", 1: "1"]
swiftDict :[2: "2", 3: "3", 1: "1"]
<!--OC常用的字典-->
ocDict1 :{
3 = 3;
2 = 2;
1 = 1;
4 = 4;
}
ocDict : {
3 = 3;
2 = 2;
1 = 1;
4 = 4;
}
查看类型实现细节
1. Swift数组
1.1 OC数组
2. Swift字典
2.1 OC字典
从上可以看出Foundation类库类面的类对象在Swift中全都被重写成引用类型的,而Swift原生的数组以及字典都是值类型
4. 深入分析集合类的拷贝
这里深入介绍一下数组的拷贝情况,之前介绍的数组拷贝都是很简单的拷贝,没有讨论数组里面的元素是深拷贝对象,还是浅拷贝对象。这里需要演示的就是这类情况。
代码演示
引用之前的代码片段
//结构体。值类型
struct DeepCopy {
var copy : Int = 0
}
//类,引用类型
class ShallowCopy {
var copy : Int = 0
}
数组拷贝演示
<!--深拷贝对象-->
var de0 = DeepCopy()
var de1 = DeepCopy()
<!--数组元素是深拷贝对象-->
var deArray = [de0, de1]
<!--浅拷贝对象-->
var sh0 = ShallowCopy()
var sh1 = ShallowCopy()
<!--数组元素是浅拷贝对象-->
var shArray = [sh0, sh1]
<!--拷贝操作-->
var deArray1 = deArray
var shArray1 = shArray
//修改数组里面的元素的值
deArray1[0] = DeepCopy(copy: 3)
print("deArray[0].copy : \(deArray[0].copy)")
print("deArray1[0].copy : \(deArray1[0].copy)")
<!--修改数组里面的元素的值-->
shArray[0].copy = 2
print("shArray[0].copy : \(shArray[0].copy)")
print("shArray1[0].copy : \(shArray1[0].copy)")
<!--删除深拷贝元素的数组元素-->
deArray1.remove(at: 1)
print("deArray1.count : \(deArray1.count)")
print("deArray.count : \(deArray.count)")
<!--删除浅拷贝元素的数组元素-->
shArray1.remove(at: 0)
print("shArray1.count : \(shArray1.count)")
print("shArray.count : \(shArray.count)")
打印信息
<!--第一部分-->
//修改数组里面的元素的值
deArray[0].copy : 0
deArray1[0].copy : 3
<!--修改数组里面的元素的值-->
shArray[0].copy : 2
shArray1[0].copy : 2
<!--第二部分-->
<!--删除深拷贝元素的数组元素-->
deArray1.count : 1
deArray.count : 2
<!--删除浅拷贝元素的数组元素-->
shArray1.count : 1
shArray.count : 2
分析上面的打印信息,首先是修改数组里面的元素的值,deArray与deArray1里面放的都是深拷贝对象,在修改数组元素里面的值的属性值的时候,通过打印信息可以发现:深拷贝元素并不会根据修改的元素的属性的值得改变而受到影响(deArray[0]元素的属性copy属性值原本为0,deArray1[0]元素的属性copy属性修改为3,打印后发现只有修改的deArray1[0]的属性值修改了)。而浅拷贝元素却会根据修改的元素的属性的值得更改变而改变(shArray[0]的元素的属性值修改之后,通过打印信息发现,shArray1[0]的元素的属性值也修改了)。
那么产生了一个问题,数组明明使用的是Swift原生的结构体(值类型),他的拷贝应该是深拷贝,可是之前的结果却显示,shArray1数组内容明明跟着改变了。这是为什么?我们接着看第二部分操作的代码。
deArray1深拷贝数组在删除了元素之后,元素个数变成1个,原来个数为两个。deArray深拷贝数组并不受影响。再看shArray1浅拷贝数组,原有2个元素,在移除一个元素之后元素个数变成一个,而shArray浅拷贝数组的元素个数并不受影响,依旧是两个。
那么根据以上打印信息,与总结可以发现这样一个原则:数组在进行赋值操作的时候,都是深拷贝,这一点与数组内元素是引用类型(浅拷贝对象)或者值类型(深拷贝对象)无关,数组在赋值拷贝的时候,首先系统会根据被拷贝数组的大小开辟一样大小的内存空间。然后将原数组中的每一元素依次拷贝到新的数组对象中。
但是,在修改数组元素的时候又在内容是浅拷贝对象的时候,修改数组内部元素赋值的数组会跟着改变,因此可以得出结论:当数组内部的元素为引用类型的对象时,修改这个元素的属性值,数组的拷贝内部的元素为浅拷贝,即如果修改被拷贝数组的内部元素时,拷贝的数组里面的元素也会跟着变化。反之,内部元素如果是值类型的对象时,数组的拷贝内部元素为深拷贝。
最后的结论:当仅仅改变了数组的大小(长度),不会影响另外一个数组。
本篇文章先将内存管理介绍到这里。会有后续文章继续介绍内存管理。
扫描二维码
关注更多精彩
点击“阅读原文”