语言
weak, _ _weak 和 _ _block 修饰符
Objective-C中, weak
用于修饰属性, __weak
和 __block
用于修饰局部变量.
weak | __weak | __block | |
---|---|---|---|
修饰对象 | 属性 | 局部变量 | 局部变量 |
意义 | 弱引用 | 弱引用 | block以外弱引用, block以内保留引用, 需要在block内手动释放. |
Swift中只有weak
修饰符, 同时用于声明一个变量或者属性为弱引用, 并没有__block
意义上的声明.
便利构造函数 (designated, convenience)
便利构造函数(designated initializer
)是为了保证类型在初始化后, 其所有属性的值都是存在的.
在Swift中, 又多了一个关键字convenience
去加强这个特性.convenience initializer
的作用在于, 可以规定一个类型的属性的初始值.
designated | convenience | |
---|---|---|
存在性 | Objective-C, Swift | Swift |
- 在Objective-C中
- 声明一个便利构造函数的做法, 就是在声明函数的最后面加上宏
NS_DESIGNATED_INITIALIZER
- 当一个类型声明了
designated initializer
之后, 我们希望创建这个类型的实例, 就必须调用它的designated initializer
. - 如果不调用designated initializer`, 编译器会发出警告.
- 声明一个便利构造函数的做法, 就是在声明函数的最后面加上宏
- 在Swift中
- 每个类型都会有
designated initializer
, 一个类型的init
函数在没有修饰的情况下, 它就是这个类型的designated initializer
. convenience initializer
的调用优先级在designated initializer
之上: 创建这个类型的实例, 如果这个类型有定义convenience initializer
, 则必须调用.- 声明
convenience initializer
, 其内部必须调用这个类型自己的designated initializer
. - 如果不按照上一条去做, 编译器会直接报错.
- 每个类型都会有
struct 和 class
很大路的知识点struct
是值类型, 而class
是引用类型.
关键在于,一个 struct
实例的属性里面存在一个引用类型class
的时候, 使用这个struct
去赋值到一个新的变量的时候, struct
属性里面的class
会被怎么处理?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Teacher: NSObject {
var name: String
init(name: String) {
self.name = name
}
}
struct Room {
var memberNum: Int
var teacher: Teacher
}
let room = Room(memberNum: 1, teacher: Teacher(name: "oreo"))
var room2 = room
room2.memberNum = 2
room2.teacher.name = "loli v"
print(room.memberNum,room.teacher.name)
print(room2.memberNum, room2.teacher.name)
print(room2.teacher == room.teacher)
room2.teacher = Teacher(name: "oreo")
print("modify room2's teacher")
print(room.memberNum,room.teacher.name)
print(room2.memberNum, room2.teacher.name)
print(room2.teacher == room.teacher)
/*
结果:
1 loli v
2 loli v
true
modify room2's teacher
1 loli v
2 oreo
false
*/
可以看到, room
赋值之后room2
, room2
的值类型属性被拷贝了一份, 修改room2
的memberNum
不会影响room
.
但是room2
的引用类型属性, 其实是拷贝了一份room
对应属性的地址, 所以修改room2
中teacher
的属性, 会影响room
,而重新给room2
赋值一个新的Teacher
实例之后, 两个Room
实例里面的引用型属性就不是同一个了.
运算符
在Objective-C中运算符是针对值类型使用的.
在Swift中, 用户可以自定义运算符, 运算符可以用在应用型对象身上. 并且用户可以自己定义运算符的功能.
协议 protocol
- Objective-C:
- 协议主要是MVC设计模式中的数据回调手段.
- 因为用于回调, 协议本身可以被声明为弱引用, 因此协议都继承
NSObject
.
-
Swift:
拥有Objective-C中已经有的全部特性, 并且被功能更强大:
- 可以使用extension特性默认实现的协议函数或属性.
- 协议中默认实现的函数可以在实现类型的代码中”重写”.
- 在使用extension特性声明的时候可以添加约束条件, 这样默认实现的函数只会在满足条件的时候才会生效, 如果不满足则等于没有实现.
- 因为使用范围不止是用于回调, Swift中的协议不必继承
NSObjectProtocol
. struct
和class
都可以实现的协议, 因此在使用协议的时候不能直接当做引用型来使用, 除非协议本身就是继承自引用型协议.
extension
Foundation
double 和 NSDecimalNumber
项目中我们使用double
表示和存储小数, 而在需要对小数进行计算的时候, 比如电商项目的购物车功能要进行金额结算, 我们使用double
就可能出现金额精度不正确的问题.
针对小数运算, Apple给我们提供了一个专门的类: NSDecimalNumber
, NSDecimalNumber
进行算数运算的方式是通过调用函数, 在Swift中可以用链式的方法计算, NSDecimalNumber
运算结果仍然是一个NSDecimalNumber
, 我们可以使用它的对应属性获得我们想要的值类型.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Item {
let unitPrice: Double
}
struct Coupon {
let value: Double
}
// 20件商品, 75折
let item = Item(unitPrice: 9.99)
let coupon = Coupon(value: 0.75)
let count: Int = 20
let dPrice = NSDecimalNumber(value: item.unitPrice)
let dCount = NSDecimalNumber(value: count)
let dCouponValue = NSDecimalNumber(value: coupon.value)
let result = dPrice.multiplying(by: dCouponValue).multiplying(by: dCount)
let doubleResult = result.doubleValue
let stringResult = result.stringValue
GCD
GCD会自动管理线程的生命周期(创建线程, 调度任务, 销毁线程). 我们调用GCD的时侯不会面对线程, 而是面对队列. 就是说:
即使N次调用了异步执行并发队列, 并不代表我们创建了N个的子线程.
-
死锁
GCD的死锁, 关键是阻塞. 有很多面试题里面提到在主线程里面同步调用主队列会引起死锁. 而之前在公司面试, 问起死锁很多人也是这样回答, 回答者没有真的明白GCD的死锁. 事实上, 即使不是在主队列做这种操作, 用一个任意的串行队列如此操作, 都会进入死锁.
1 2 3 4 5 6 7 8
let serialQueue = DispatchQueue(label: "serial") serialQueue.async { serialQueue.sync { print("sync in async???") } print("may I run?") }
以上这段代码同样是编译不通过的, 因为
sync
函数做的第一件事情就是阻塞当前队列的任务(也就是print("may i run?")
这一行代码), 而串行队列同时间只能执行1个任务,sync
调用之后, 而闭包里面的代码也要在serialQueue
中执行, 而这个任务会被排在了serialQueue
队列的最后方, 而串行队列后方的任务必须等待前方的任务执行完才能执行, 因此陷入一个无限等待的循环.而主队列其实就是一个串行的队列!!!
再看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
let concurrentQueue = DispatchQueue(label: "concurrent", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) concurrentQueue.async { print(Thread.current) concurrentQueue.sync { print("sync in async???", "Thread:", Thread.current) } print("yes you can!", Thread.current) } /* 结果: <NSThread: 0x600001a5c100>{number = 3, name = (null)} sync in async??? Thread: <NSThread: 0x600001a5c100>{number = 3, name = (null)} yes you can! <NSThread: 0x600002b32d00>{number = 3, name = (null)} */
没错, 在并发队列里面, 这几串代码是正常运行的. 因为
sync
阻塞了print("yes you can!")
, 但是concurrentQueue
它可以同时执行多个任务, 闭包里面的代码不需要排到print("yes you can!")
的后面, 而是可以使用concurrentQueue
分配的其他任务资源去执行. -
线程和队列
回头继续看上面的代码, 可以发现, 由始至终
concurrentQueue
都一直在用同一个线程!!!这就是GCD做了它应该做的事情: 管理线程的生命周期.
因为上面的代码, 事实上
concurrentQueue
同一时间永远只有1个任务需要执行, 所以即使它本身可以同时执行多个任务, 它也没必要创建多个线程来给任务开始.(请脑补一下收银排队的情景, 一模一样.)
也就如开头所说的, 并发队列异步执行命令, 并不等于就是开辟了新的线程.
UIKit
UIView 和 CALayer 的关系
当我们需要在屏幕上显示一些元素的时候, 一般会使用到UIView
的各种子类. 而实际上如果我们只需要实现单纯的显示功能, 也可以直接使用CALayer
去实现.
**相比起UIVIew
, CALayer
并不具备交互的功能, 就是不能响应任何点击拖拽行为. **查看UIView
的类不难发现, 它自身也有一个layer
的属性, 其对应的其实就是用于显示在屏幕上的CALayer
对象.
待完成
Responder
待完成