1、閉包簡介
閉包和OC中的Block非常相似
OC中的Block類似于匿名函數
閉包是用來定義函數
作用: Block是用于保存一段代碼, 在需要的時候執行
閉包也是用于保存一段代碼, 在需要的時候執行
做一個耗時操作
/*
OC: block類似于匿名函數, 用于封裝代碼塊, 在特點的時候執行
執行一些耗時操作
類型: 返回值類型(^block名稱)(形參列表)
值:
^(形參列表){
需要執行的代碼
}
Swift: 閉包是用于定義函數(Swift中函數就是閉包, 閉包就是一個特殊的函數)
執行一些耗時操作
類型: (形參列表)->返回值類型
值:
{
(形參列表)->返回值類型
in
需要執行的代碼
} // in 的含義是用于區分形參返回值和執行代碼
*/
- 在講解閉包之前,我們先講解一下OC中的block
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self loadData:^{
NSLog(@"刷新UI");
}];
}
- (void)loadData:(void(^)())finished
{
// __weak : 如果對象釋放, 會自動設置為nil
// __unsafe_unretained: 如果對象釋放, 不會自動設置為nil
// 1.在子線程中加載數據
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"加載數據");
// 2.在主線程中回調, 刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
finished();
});
});
}
2、 閉包基本使用
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/*
閉包的幾種格式
1. 完整寫法
loadData ({ () -> () in
print("更新UI")
})
2.如果閉包沒有形參, 那么in和in之前的代碼都可以省略
loadData ({
print("更新UI")
})
3.如果閉包是函數的最后一個參數, 那么閉包可以寫在函數()的后面
loadData (){
print("更新UI")
}
4.如果函數只有一個閉包參數, 那么函數的()可以省略
loadData {
print("更新UI")
}
*/
// in是用于區分代碼和描述
loadData { () -> () in
print("更新UI")
}
}
func loadData(finished: ()->())
{
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print(NSThread.currentThread())
print("加載數據")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print(NSThread.currentThread())
// 回調通知調用者
finished()
})
}
}
3、閉包的參數和返回值
-
實例:
- 需求: 在控制器的View上添加一個UIScrollview, 然后在UIScrollview上添加15個按鈕, 讓按鈕平鋪
1、不使用閉包簡單實現
// 1.創建UIScrollview
let sc = UIScrollView(frame: CGRect(x: 0, y: 200, width: UIScreen.mainScreen().bounds.width, height: 50))
sc.backgroundColor = UIColor.redColor()
// 2.通過循環創建15個按鈕
let count = 15
let width:CGFloat = 80
let height = sc.bounds.height
for i in 0..<count
{
let btn = UIButton()
btn.setTitle("標題\(i)", forState: UIControlState.Normal)
btn.frame = CGRect(x: CGFloat(i) * width, y: 0, width: width, height: height)
btn.backgroundColor = UIColor.greenColor()
// 3.將按鈕添加到UIScrollview上
sc.addSubview(btn)
}
sc.contentSize = CGSize(width: CGFloat(count) * width, height: height)
// 4.將UIScrollview添加到控制器view上
view.addSubview(sc)
- 2、使用閉包封裝方法:快速創建
// 2.1 自定義方法:創建子控件并返回UIScrollView
// 技巧: 在編寫閉包代碼時, 不管三七二十一先寫上 ()->(), 然后再修改
/*
形參1 getNumber: ()->Int 指定添加子控件的個數閉包 返回Int類型;
形參2 createView: (index: Int)->UIView) 用來根據index索引來創建子控件返回UIView類型閉包;
函數的返回值:UIScrollView類型
*/
func createScrollview(getNumber: ()->Int, createView: (index: Int)->UIView) -> UIScrollView
{
// 1.創建UIScrollview
let sc = UIScrollView(frame: CGRect(x: 0, y: 200, width: UIScreen.mainScreen().bounds.width, height: 50))
sc.backgroundColor = UIColor.redColor()
// 2.通過循環創建15個按鈕
let count = getNumber()
let width:CGFloat = 80
let height = sc.bounds.height
for i in 0..<count
{
/*
let btn = UIButton()
btn.setTitle("標題\(i)", forState: UIControlState.Normal)
btn.frame = CGRect(x: CGFloat(i) * width, y: 0, width: width, height: height)
btn.backgroundColor = UIColor.greenColor()
*/
let subView = createView(index: i)
subView.frame = CGRect(x: CGFloat(i) * width, y: 0, width: width, height: height)
// 3.將按鈕添加到UIScrollview上
sc.addSubview(subView)
}
sc.contentSize = CGSize(width: CGFloat(count) * width, height: height)
return sc
}
// 2.1 使用自定義方法創建UIScrollView返回并添加到view視圖中
override func viewDidLoad() {
super.viewDidLoad()
// 調用自定義方法:createScrollview(getNumber: ()->Int, createView: (index: Int)->UIView) -> UIScrollView
let sc = createScrollview({ () -> Int in
return 20
}) { (index) -> UIView in
// let btn = UIButton()
// btn.setTitle("標題\(index)", forState: UIControlState.Normal)
// btn.backgroundColor = UIColor.greenColor()
let label = UILabel()
label.text = "標題\(index)!!!"
label.backgroundColor = (index % 2 == 0) ? UIColor.greenColor() : UIColor.purpleColor()
return label
}
view.addSubview(sc)
}
3、閉包循環引用
/*
閉包中使用了外部屬性self,就對對其進行強引用,同OC中block一樣會出現循環引用的問題,如何解決
OC中如何解決: __weak typeof(self) weakSelf = self;
Swift中如何解決: weak var weakSelf = self
對應關系: __weak == weak __unsafe_unretained == unowned
注意:
__weak : 如果對象釋放, 會自動設置為nil
__unsafe_unretained: 如果對象釋放, 不會自動設置為nil
*/
import UIKit
class ViewController: UIViewController {
// 在Swift中, 如果在某個類中定義一個屬性, 那么這個屬性必須要初始化, 否則就會報錯
// 如果占時不想初始化, 那么可以在后面寫上一個?號
// 注意: 在設置閉包屬性是可選類型時一定更要用一個()括住閉包的所有的類型, 否則只是指定了閉包的返回值是可選的
// 錯誤寫法: var callback: ()->()?
var callback: (()->())? // 定義一個閉包屬性
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// OC中如何解決: __weak typeof(self) weakSelf = self;
// Swift中如何解決: weak var weakSelf = self
// 對應關系: __weak == weak __unsafe_unretained == unowned
// 1、bug:閉包與self會循環引用的問題出現,兩者都無法釋放掉。
// 因為:閉包中使用了外部屬性self,就對對其進行強引用,同OC中block一樣會出現循環引用的問題
loadData {() -> () in
print("被回調了")
// 在Swift開發中, 能不寫self就不寫slef
// 一般情況下只有需要區分參數, 或者在閉包中使用
self.view.backgroundColor = UIColor.greenColor()
}
// 2、解決方式一:weak解決
// 2.1
weak var weakSelf = self // 注意:weak修飾的weakSelf時可選的,如果我們使用可選類型數據,必須要強制解包 weakSelf!
loadData {() -> () in
print("被回調了")
weakSelf!.view.backgroundColor = UIColor.greenColor() // 可選數據weakSelf 強制解包 weakSelf!
}
// 2.2
// [weak self] ,這樣說明閉包里面使用的self不會被強引用了。但是是可選類型,所以我們使用self的時候就需要自己手動強制解包 “!” => self!.view.backgroundColor
loadData {[weak self] () -> () in
print("被回調了")
self!.view.backgroundColor = UIColor.greenColor() // 可選數據weakSelf 強制解包 weakSelf!
}
// 3、解決方式二:unowned解決
// 好處:相對weak, [unowned self] 修飾的self不是可選類型,這樣就不用像上面weak修飾的self那樣自己手動利用"!"強制解包了。
/*
loadData { [unowned self] () -> () in
print("被回調了")
// 注意:unowned修飾self不是可選類型,一定有值,所以不用手動強制解包了
self.view.backgroundColor = UIColor.greenColor()
}
*/
}
func loadData(finished: ()->())
{
callback = finished
// 1.加載數據
print("加載數據")
// 2.執行回調
finished()
}
// deinit 相當于OC中的dealloc方法
// 只要一個對象釋放就會調用deinit方法
deinit
{
print("88")
}
}