一、值类型和引用类型
1:值类型(Value types)
每个实例都保留一份独有的数据拷贝,一般以结构体 (struct) 、 枚举(enum) 或者元组(tuple)的形式出现。如下OBAnimal
的结构体
typedef struct {
NSString *name;
}OBAnimal;
int main(int argc, char * argv[]) {
OBAnimal ani1;
ani1.name = @"ob";
NSLog(@"%p:%@",&ani1,ani1.name);
OBAnimal ani2 = ani1;
ani1.name = @"newOB";
NSLog(@"%p:%@",&ani1,ani1.name);
NSLog(@"%p:%@",&ani2,ani2.name);
return 0;
}
//打印如下
2020-08-13 14:55:12.980223+0800 ani1=0x7ffee7d2af08:ob
2020-08-13 14:55:12.980709+0800 ani1=0x7ffee7d2af08:newOB
2020-08-13 14:55:12.980785+0800 ani2=0x7ffee7d2af00:ob
当OBAnimal ani2 = ani1;
时,系统将会创建一个新的实例并将ani1
中的值拷贝一份,赋值给ani2
,所以修改ani1
中的name
,ani2
不会改变;ani2
和ani1
是两个内存的值
2:引用类型(Reference types)
每个实例共享一份数据来源,一般以类(class)的形式出现。
如果上面的
OBAnimal
是一个类,那么修改其中任意一个实例的name属性。那么另一个也会改变。
因为OBAnimal *ani2 = ani1;
操作时,堆上只有一个实例对象,ani2
和ani1
是两个指向这个实例对象的指针。
引用类型和值类型区别
- 类是引用类型, 结构体为值类型
- 结构体不可以继承
- 值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝
- 引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝
二、 mutating关键字
类是引用类型,而结构和枚举是值类型。
默认情况下,不能在其实例方法中修改值类型的属性。为了修改值类型的属性,必须在实例方法中使用mutating关键字。
使用此关键字,方法将能够更改属性的值,并在方法实现结束时将其写回到原始结构
所以要改成mutating
struct Person {
var name = "ob";
mutating func test(_ newName: String) {
self.name = newName;
print(self.name);
}
};
var tom = Person.init(); //这里就不能用let了,因为值改变了
tom.test("lsy");
//打印如下
lsy
三、strong、weak、 unowned
Swift 的内存管理机制与 Objective-C一样。
只有引用类型变量所引用的对象才需要使用引用计数器进行管理,对于枚举、结构体等,他们都是值类型的。因此不需要使用引用计数进行管理。
1:strong
strong 代表着强引用,是默认属性。不用写关键字strong
let cat = Animal.init();
2:weak
weak代表着弱引用。当对象被声明为 weak 时,该对象的引用计数不会增加1。它在对象释放后弱引用也随即消失。继续访问该对象,程序会得到 nil,不会崩溃
weak var cat = Animal.init();
因为对象释放时:需要对cat
指针进行cat = nil
操作,所以这里只能用var
可变类型
3:unowned
unowned 与弱引用本质上一样。唯一不同的是,对象在释放后,依然有一个无效的引用指向对象,它不是 Optional 也不指向 nil。如果继续访问该对象,程序就会崩溃。和oc的__unsafe_unreturned
一样
unowned var cat = Animal.init();
print(cat as Any); //发生崩溃
weak 和 unowned 的引入是为了解决由 strong 带来的循环引用问题。weak安全性高
四、闭包
1:闭包
func testBlock() {
let a = { () -> () in //无参数无返回值的闭包
print("aaa");
};
a();
let b = { () in //无参数无返回值的闭包的简写-1
print("bbb");
};
b();
let c = { //简写-2
print("ccc");
};
c();
}
2:值捕获
在OC中,Block的值捕获
int a = 10;
void(^ob_blick)(void) = ^{
printf("%d", a); // 10 如果在int前加 __block,那么打印20
};
a = 20;
ob_blick();
在swift中
var age = 10;
let c = {
print("ccc--:\(age)"); //打印 20
};
age = 20;
c();
发现打印的是20,和oc中的表现不一样啊,怎么回事?不急再来换个写法
var age = 10;
let c = { [age] in //---->这里有变化
print("ccc--:\(age)");
};
age = 20;
c();
原来在swift中,闭包也是会捕获变量的,但是分情况
- 默认情况下,只有在执行block()时,才会捕获变量
- 加上
[]
中括号后,就是立即捕获,和OC一样了
同样的struct和enum也符合这个情况,除了class
struct OBDog {
var name = "jack";
}
func testBlock() {
var dog = OBDog.init();
let block = {
[dog] in //注销就会打印tom 不注销立即捕获就会打印jack
print("ccc--:\(dog.name)");
};
dog.name = "tom";
block();
}
如果吧struct OBDog
换成class OBDog
,那么就会一直打印 tom
因为class是引用类型,捕获的是指针,防止循环引用可以使用[weak dog] in
和[unowned dog]
,具体使用哪个?根据闭包和捕获对象的生命周期而定,
unowned:性能高,可读性强,不需要解包
weak :性能相对低(释放时还需要去weak表里操作),安全性高,需要解包,
3:逃逸闭包 escaping
当闭包作为参数传递给函数时,闭包被称为转义函数,在函数返回后调用。
就是将参数闭包赋值给其他全局的或者静态的变量,就要使用
逃逸闭包
typealias Block = (Int) ->();
class Model {
var myblock: Block;
func test3(call: @escaping (Int)->()) {
self.myblock = call;
}
init() {
}
}
- @escaping和@non-escaping修饰符是用来修饰闭包的。闭包默认为@non-escaping类型
- @escaping:转义闭包,闭包的生命周期不在传入的函数范围内管理。由函数外部变量持有。当函数结束之后,闭包才去执行
- @non-escaping:闭包在函数内执行完后,函数才去执行,闭包销毁。
当使用@escaping时,要考虑self被循环引用。使用
weak
或者unowned
解决
五、inout
Swift中inout只不过是按值传递,然后再写回原变量,类似于c语言中的指针传递
func test(_ num: inout Int) {
num = num+1;
}
var num = 3;
test(&num);
print(num);
// 打印
4
六、auto变量
在计算机编程领域,自动变量(Automatic Variable)指的是局部作用域变量,具体来说即是在控制流进入变量作用域时系统自动为其分配存储空间,并在离开作用域时释放空间的一类变量。在许多程序语言中,自动变量与术语“局部变量”(Local Variable)所指的变量实际上是同一种变量,所以通常情况下“自动变量”与“局部变量”是同义的。
七、Swift值类型的写时复制
当执行如下代码时:
func testArr() {
var arr = [1,2];
let brr = arr;
printValuePoint(arr);
printValuePoint(brr);
arr.append(4);
print("----");
printValuePoint(arr);
printValuePoint(brr);
}
func printValuePoint(_ obj: UnsafeRawPointer) {
print(obj);
}
//打印如下-----------
0x0000600003f37140
0x0000600003f37140
----
0x0000600002402560
0x0000600003f37140
经过多方查证以及自身的实验,得出如下结论:
基本数据类型Int
, String
, Double
以及Struct
都是在赋值的时候进行赋值的,
而集合类型Array
,Set
,Dictionary
确实是写时复制的
八、defer
注意
defer
的作用域,if - else - while - for
大括号结束都是可以执行defer
的。
使用defer代码块来表示在函数返回前,函数中最后执行的代码。具有延时执行的特性。
func testDefer(t: Bool) {
print("start --1");
defer {
print("defer --2");
}
print("end --3");
}
//打印
start --1
end --3
defer --2
当有多个defer时,后加入的先执行,可以猜测Swift使用了stack来管理defer。
defer
不会像OC的block一样捕捉变量,
func testDefer(t: Bool) {
print("start --");
var age = 10;
defer {
print("defer --\(age)");
}
age = 20;
print("end --");
}
start --
end --
defer --20