1. 协议
1.1 协议的格式
- 协议的定义方式与类,结构体,枚举的定义都非常相似
protocol SomeProtocol {
// 协议方法
}
- 遵守协议的格式
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
// 实现协议中的方法
}
1.2 协议的基本使用
- 定义协议和遵守协议
// 1.定义协议
protocol SportProtocol {
func playBasketball()
func playFootball()
}
// 2.遵守协议
// 注意:默认情况下在swift中所有的协议方法都是必须实现的,如果不实现,则编译器会报错
class Person : SportProtocol {
var name : String?
var age : Int = 0
// 实现协议中的方法
func playBasketball() {
print("人在打篮球")
}
func playFootball() {
print("人在踢足球")
}
}
- 协议之间的继承
protocol CrazySportProtocol {
func jumping()
}
protocol SportProtocol : CrazySportProtocol {
func playBasketball()
func playFootball()
}
1.3 代理设计模式
- 协议继承用于代理设计模式
protocol BuyTicketProtocol {
func buyTicket()
}
class Person {
// 1.定义协议属性
var delegate : BuyTicketProtocol
// 2.自定义构造函数
init (delegate : BuyTicketProtocol) {
self.delegate = delegate
}
// 3.行为
func goToBeijing() {
delegate.buyTicket()
}
}
class HuangNiu: BuyTicketProtocol {
func buyTicket() {
print("买了一张火车票")
}
}
let p = Person(delegate: HuangNiu())
p.goToBeijing()
1.4 协议中方法的可选
// 1.定义协议
@objc
protocol SportProtocol {
func playBasketball()
optional func playFootball()
}
// 2.遵守协议
class Person : SportProtocol {
var name : String?
var age : Int = 0
// 实现协议中的方法
@objc func playBasketball() {
print("人在打篮球")
}
}
2. 闭包
2.1 闭包的介绍
- 闭包和OC中的block非常相似
- OC中的block是匿名的函数
- Swift中的闭包是一个特殊的函数
- block和闭包都经常用于回调
- 注意:闭包和block一样,第一次使用时可能不习惯它的语法,可以先使用简单的闭包,随着学习的深入,慢慢掌握其灵活的运用方法.
2.2 闭包的使用
block的用法回顾
- 定义网络请求的类
@interface HttpTool : NSObject
- (void)loadRequest:(void (^)())callBackBlock;
@end
@implementation HttpTool
- (void)loadRequest:(void (^)())callBackBlock
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"加载网络数据:%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
callBackBlock();
});
});
}
@end
- 进行网络请求,请求到数据后利用block进行回调
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.httpTool loadRequest:^{
NSLog(@"主线程中,将数据回调.%@", [NSThread currentThread]);
}];
}
- block写法总结:
- 类型:
返回值(^block的名称)(block的参数)
^(参数列表) {
// 执行的代码
};
使用闭包代替block
- 定义网络请求的类
class HttpTool: NSObject {
func loadRequest(callBack : ()->()){
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("加载数据", [NSThread.currentThread()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callBack()
})
}
}
}
- 进行网络请求,请求到数据后利用闭包进行回调
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// 网络请求
httpTool.loadRequest ({ () -> () in
print("回到主线程", NSThread.currentThread());
})
}
- 闭包写法总结:
- 技巧:初学者定义闭包类型,直接写()->().再填充参数和返回值
类型:(形参列表)->(返回值)
{
(形参) -> 返回值类型 in
// 执行代码
}
闭包的简写
- 如果闭包没有参数,没有返回值.in和in之前的内容可以省略
httpTool.loadRequest({
print("回到主线程", NSThread.currentThread());
})
- 尾随闭包写法:
- 如果闭包是函数的最后一个参数,则可以将闭包写在()后面
- 如果函数只有一个参数,并且这个参数是闭包,那么()可以不写
httpTool.loadRequest() {
print("回到主线程", NSThread.currentThread());
}
// 开发中建议该写法
httpTool.loadRequest {
print("回到主线程", NSThread.currentThread());
}
2.3 闭包的循环引用
- 如果在HttpTool中有对闭包进行强引用,则会形成循环引用
- 补充:在Swift中检测一个对象是否销毁,可以实现对象的deinit函数
// 析构函数(相当于OC中dealloc方法)
deinit {
print("ViewController----deinit")
}
- 循环引用的(实现)
- 该实现是为了产生循环引用
class HttpTool: NSObject {
// 定义属性,来强引用传入的闭包
var callBack : (()->())?
func loadRequest(callBack : ()->()){
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("加载数据", [NSThread.currentThread()])
dispatch_async(dispatch_get_main_queue(), { () -> Void in
callBack()
})
}
self.callBack = callBack
}
}
- swift中解决循环引用的方式
- 方案一:
- 使用weak,对当前控制器使用弱引用
- 但是因为self可能有值也可能没有值,因此weakSelf是一个可选类型,在真正使用时可以对其强制解包(该处强制解包没有问题,因为控制器一定存在,否则无法调用所在函数)
// 解决方案一:
weak var weakSelf = self
httpTool.loadData {
print("加载数据完成,更新界面:", NSThread.currentThread())
weakSelf!.view.backgroundColor = UIColor.redColor()
}
- 方案二:
- 和方案一类型,只是书写方式更加简单
- 可以写在闭包中,并且在闭包中用到的self都是弱引用
httpTool.loadData {[weak self] () -> () in
print("加载数据完成,更新界面:", NSThread.currentThread())
self!.view.backgroundColor = UIColor.redColor()
}
- 方案三:(常用)
- 使用关键字unowned
- 从行为上来说 unowned 更像OC中的 unsafe_unretained
- unowned 表示:即使它原来引用的对象被释放了,仍然会保持对被已经释放了的对象的一个 "无效的" 引用,它不能是 Optional 值,也不会被指向 nil
httpTool.loadData {[unowned self] () -> () in
print("加载数据完成,更新界面:", NSThread.currentThread())
self.view.backgroundColor = UIColor.redColor()
}
3. 懒加载
3.1 懒加载的介绍
- swift中也有懒加载的方式
- (苹果的设计思想:希望所有的对象在使用时才真正加载到内存中)
- 和OC不同的是swift有专门的关键字来实现懒加载
- lazy关键字可以用于定义某一个属性懒加载
3.2 懒加载的使用
- 格式
lazy var 变量: 类型 = { 创建变量代码 }()
- 懒加载的使用
// 懒加载的本质是,在第一次使用的时候执行闭包,将闭包的返回值赋值给属性
// lazy的作用是只会赋值一次
lazy var array : [String] = {
() -> [String] in
return ["why", "lmj", "lnj"]
}()
4. 非逃逸闭包与逃逸闭包理解(难点)
从Swift3开始,传递闭包到函数中的时候,系统默认为非逃逸闭包(Nonescaping Closures),有非逃逸闭包必然就有逃逸闭包(Escaping Closures),下面分析一下两者的区别。
4.1 非逃逸闭包
非逃逸闭包的生命周期比较简单:
1、把闭包作为参数传递给函数。
2、函数中运行该闭包。
3、退出函数。
非逃逸闭包被限制在函数内,当函数退出的时候,该闭包引用计数不会增加,也就是说其引用计数在进入函数和退出函数时保持不变。
而对于非逃逸型闭包,由于其生命周期确定短于相关函数,编译器可以据此做性能优化。
class ClassA {
// 接受非逃逸闭包作为参数
func someMethod(closure: () -> Void) {
// 想干什么?
}
}
class ClassB {
let classA = ClassA()
var someProperty = "Hello"
func testClosure() {
classA.someMethod {
// self 被捕获
someProperty = "闭包内..."
}
}
}
当传递闭包参数给函数someMethod时,要注意ClassB中的属性someProperty,虽然闭包会捕获self,但是由于默认闭包参数是非逃逸型,这里可以省略 self, 反正编译器已经知道这里不会有循环引用的潜在风险。
4.2 逃逸闭包
逃逸闭包恰恰与非逃逸闭包相反,其生命周期长于相关函数,当函数退出的时候,逃逸闭包的引用仍然被其他对象持有,不会在相关函数结束后释放。
逃逸闭包- 逃逸闭包的定义:一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包。
下面通过一个例子来理解逃逸闭包:
// 定义一个存放闭包的全局数组
var completionHandlers: [() -> Void] = []
// 定义一个接收闭包的函数
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void)
{
completionHandlers.append(completionHandler)
}
// 定义另一个接收闭包的函数
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
/*
定义一个类:
初始化x值为10
通过调用上面定义的两个函数,使用尾随闭包的方式将实现"对x赋值"这么一个功能的闭包传入
*/
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
// 创建类的对象
let instance = SomeClass()
/*
执行doSomething函数
PS:内部执行someFunctionWithEscapingClosure,someFunctionWithNonescapingClosure,即期望内部会利用两个尾随闭包对x进行赋值
*/
instance.doSomething()
print(instance.x)
// 打印出 "200"
completionHandlers.first?()
print(instance.x)
// 打印出 "100"
因为逃逸闭包是在函数执行之后才会执行,所以可以这么理解:
(1)我创建了一个类的对象instance
(2)对象中初始化了一个x = 10
(3)利用对象执行了函数doSomething
(4)函数内部先调用了全局函数someFunctionWithEscapingClosure该函数传入了尾随闭包{ self.x = 100 },期望修改instance对象中的x值为100(但是此时并没有执行这个包含了赋值语句的闭包)
(5)函数内部进一步调用全局函数someFunctionWisthNonescapingClosure该函数传入了尾随闭包{ x = 200 },期望修改instance对象中的x值为200(因为此时全局函数someFunctionWithNonescapingClosure内部执行这个包含了赋值语句的闭包,所以x的值由10变为了200)
(6)输出(显然结果为200)
(7)查找全局数组completionHandlers,找到里面第一个元素,显然找到的是在someFunctionWithEscapingClosure函数中添加的闭包{ self.x = 100 },此时才通过全局数组的查询找出闭包并执行,于是x此时才被赋值为100(典型的someFunctionWithEscapingClosure函数执行完毕后才执行闭包{ self.x = 100 },于是这个在函数之后最后才执行到的闭包,就是符合定义的逃逸闭包了。
- 使用逃逸闭包的2个场景:
(1)异步调用:如果需要调度队列中异步调用闭包, 这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不可预知的。
(2)存储:需要存储闭包作为属性,全局变量或其他类型做稍后使用。
网友评论