Swift 閉包

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")
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容