iOS篇-block篇-0基礎到熟練應用全面解析

寫在前面:demo 或者代碼 評論發郵箱地址,我會及時給需要的小伙伴發過去.

一 : 科普一分鐘

  • 什么是block: 個人簡單的理解為就是一個存放代碼片段的容器,作用就是保存代碼.
  • block 蘋果官方定義為 對象 可以用數組字典進行操作,寫到這同學們可能會明白了,block 說的簡單點就是 一個可以存放代碼片段的對象,可以進行內存管理,可以作為屬性,等普通對象操作.

既然分析明白了就可以做事情了,接下來進入block 的神器世界

二 : block的基本使用

  • block的聲明
    void(^block)();
    聲明一個名字為 block 的 block 對象;當然 名字可以換啊 我們可以用doit 代替,更形象
    void(^doit)();
    聲明了名字 為doit 的block 代碼塊對象

  • block 的定義
    block 的定義方式通常有三種方式

  1. 第一種 :沒有參數 沒有返回值 = 右邊相當對名字為 doit1這個block對象的代碼塊的賦值 里面存放一段代碼
void(^doit1)() = ^(){
       NSLog(@"關注我吧,會有更多精彩");
};
  • 第二種 :如果沒有參數,參數可以隱藏
void(^block2)() = ^{
  NSLog(@"關注我吧,會有更多精彩");
    };

如果有參數,定義的時候 必須要寫參數 而且必須要有參數變量名

void(^block22)(int) = ^(int a){

      NSLog(@"關注我吧,會有更多精彩");

    };
  • 第三種 block 返回值可以省略,不管有沒有返回值 都可以省略
int(^block3)() = ^int{
    
        return 3;
        
    };

也可以寫成

 int(^block3)() = ^{
     
     return 3;
     
     };
  • block 的類型
    int(^)(NSString *) 我們定義了一個返回值 為int 參數為 NSString block 代碼塊對象類型 它現在還沒有名字 我們現在給它取名為 :doit4
 int(^doit4)(NSString *) = ^(NSString *name){
    
        return 2;
        
    };
  • block 調用
    不要以為定義完成 就大功告成了 ,因為我們還沒有去調用它,我們寫block 代碼塊的 目的 就是去使用它 .
 doit1();

之前我們已經聲明了 doit1 這個block 代碼塊對象 所以運行后的結果是
NSLog(@"關注我吧,會有更多精彩");

  • block 的快捷方式
    我們手動去寫block 會很麻煩 我們可以敲 inline 然后選擇第一個 接下來 就可以方便我們使用了,會自動聯想代碼
 <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
     <#statements#>
     };
  • block作為屬性
    block 對象也可以當成屬性使用 屬性的書寫原則是 如何聲明 就如何屬性 block

例如
@property(nonatomic,strong) void(^doit)() ;
一個無返回值,無參數的block類型 名字為doit的屬性對象

當然這樣書寫 我們會不習慣 我們還可以重新定義類型 來寫成我們熟悉的形式.
例如重新聲明類型

//BlockType;類型的別名
typedef void(^BlockType)();

注意 這個BlockType 是類型名 并不是對象名
所以我們要再定義一個 類型 為BlockType 名字為doit 的對象
@property(nonatomic,strong) BlockType doit;

- blcok 內部變量傳遞分析
  • 看代碼分析結果:
- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 3;
    
    a = 5;
    
    void(^block)() = ^{
    
        NSLog(@"-- %d",a);

    };
    block();
    
    
}

結果為 : 5

  • 看代碼分析結果
- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 3;
    
   
    
    void(^block)() = ^{
    
        NSLog(@"-- %d",a);
        
    
    };
    
     a = 5;
    block();
    
    
}

結果為 : 3

  • 原因分析
    如果是局部變量 block 是值傳遞,當定義block 是 a = 3 此時,block 內部代碼塊的a = 3 ,改變a = 5,但是不影響block 代碼塊內容.

  • 看代碼分析結果

- (void)viewDidLoad {
    [super viewDidLoad];
  static  int a = 3;
    void(^block)() = ^{
    
        NSLog(@"-- %d",a);
    };
    
     a = 5;
    block();  
}
  • 結果 : 5
  • 原因分析 : 如果是靜態變量,全局變量,block 是指針傳遞

三 : block 在開發中的基本應用

1. block 保存一段代碼

實例場景:在我們寫tableView列表中 根據不同的cell 判斷不同點擊事件 我們多數人的做法是 通過很多很麻煩的判斷如下:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    if (indexPath.row == 0) {
        
    }else if (indexPath.row == 1){
   
    }else if (indexPath.row == 2){
   
    }
   
}

其實我們可以用Block 存儲要做的事情 來代替這些復雜的判斷,因為這樣增加了代碼的可讀性,而且需求發生變化的時候也非常容易的完成了需求的變化
做法:

  • 我們在cellItem 這個模型里添加 block 屬性
//保存每個cell 做的事情
@property(nonatomic,strong) void(^block)();
  • 保存每個模型對應cell 要做的事情
  cellItem *item1 = [cellItem itemWithTitle:@"打電話"];
    item1.block = ^{
        NSLog(@"睡覺");
    };
    
    cellItem *item2 = [cellItem itemWithTitle:@"發短信"];
    item2.block = ^{
        NSLog(@"吃飯");
    };
    
    cellItem *item3 = [cellItem itemWithTitle:@"發郵件"];
    item3.block = ^{
        NSLog(@"裝逼");
    };
    _items = @[item1,item2,item3];
  • 替換復雜又low 的判斷
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    cellItem *item = self.items[indexPath.row];
    if (item.block) {
        item.block();
    }
    
}
2. 逆向傳值

我們的經典逆向傳值的例子就是代理了吧,大多數的開發者也是習慣于代理的方式,但是block 的傳值方式要比代理好太多了,代理的6步驟比較復雜. 接下來我們要用block 替換代理
實例場景:

  • 首先我們定義了兩個控制器ViewController 和 TZpopViewController 點擊ViewController 的view模態到TZpopViewController 然后 點擊TZpopViewController的view 返回 并且傳至到ViewController

  • ViewController方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{


    TZpopViewController *vc  = [[TZpopViewController alloc]init];
    vc.block = ^(NSString *index) {
        
        NSLog(@"index = %@",index);
        
    };
    
    vc.view.backgroundColor = [UIColor brownColor];
    [self presentViewController:vc animated:YES completion:nil];
    

}
  • TZpopViewController 屬性
    @property(nonatomic,strong)void(^block)(NSString*index) ;

  • TZpopViewController 方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_block) {
        _block(@"tz123");
        
    }

}
  • 分析
    首先當我們在ViewController點擊觸發 touchesBegan 方法時候 創建了 TZpopViewController 并且給它的 block 屬性賦值了一段帶代碼
= ^(NSString *index) {

        NSLog(@"index = %@",index);

    };

此時這個代碼片段是不走的 ,因為沒有操作執行block

當我們觸發TZpopViewController 中的 touchesBegan方法時候 做了執行block 的方法

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_block) {
        _block(@"tz123");
        
    }

}

此時則會在ViewController 中走打印方法 打印結果為tz123

四 : block 內存管理

  • block 在MRC下的注意事項
    由于MRC沒有strong ,weak局部對象相當于基本類型,存放于棧區,代碼走過就銷毀

1.在MRC 中block只能使用copy 修飾,不能使用retain,因為使用retain 了block 存放于棧區,被銷毀了.

2.在MRC只要block引用了外部局部變量,block 放在棧里面,只要block 沒有引用外部局部變量,block 放在全局區里面.

  • block在ARC下的注意事項

1.只要block 引用外部局部變量,block放在堆里面

注意:在ARC block 使用 strong 修飾,最好不用使用 copy 節省性能.

有的小伙伴會反駁說用copy ,其實是看需求而定 ,因為我們大部分需求是不可變賦值給不可變 ,所以這里建議用strong .如果保守的話也可以用copy

  • 分析
    我們使用 copy 通常是淺拷貝 因為 是不可變賦值給不可變 ,不用考慮 重新分配內存的問題, 我們copy 修飾 內部方法會走 [block copy] 這個方法系統會分析是否從新分配內存 ,浪費資源.所以我們通常情況下使用strong

五 : block 的循環引用問題

  • 我們在使用的時候稍微疏忽會造成循環引用,雙方都不會銷毀,導致內存泄漏.
  • 模擬循環引用
    在ViewController 定義屬性
    @property(nonatomic,strong)void(^block1)();
- (void)viewDidLoad {
    [super viewDidLoad];

    _block1 = ^{

     NSLog(@"關注我有更多精彩%@",self);

         };
    
    _block1(); 
}

這種寫法就會造成循環引用.

  • 分析
    block造成循環引用:block 會對里面所有的強指針變量都強引用一次


    循環引用圖解.png
  • 解決辦法

    __weak typeof (self) weakself = self;

 _block1 = ^{

          
        NSLog(@"關注我有更多精彩%@",weakself);

    };
    
    _block1();
}
解決循環引用圖解.png
  • 思考
    假如我們想在block 代碼塊種寫一段延遲做的事情怎么辦
    通常block 代碼塊走過,則會銷毀拿不到.

  • 解決辦法

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof (self) weakself = self;
    
    _block1 = ^{

        __strong typeof (weakself) strongself = weakself;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"關注我有更多精彩%@",strongself);

        });
    
    };
    
    _block1();
    
    
}

再次強引用一次,知道 延遲代碼走完,則指針釋放 .

六 : block 在開發中的高級應用

- block 當參數在開發中的應用

1.什么情況下block 被當成參數了呢,
看參數有沒有^ 如果有^ 怎參數為block.
2.什么時候需要把block 當成參數,
做的事情由外部決定,什么時候做由內部決定.

代碼舉例:

  • 做一個計算器:要求怎么計算由外部決定,時候時候計算由內部決定

TZcaculoterManager

@property(nonatomic,assign)NSInteger result;
//計算
-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock;

-(void)cacutor:(NSInteger(^)(NSInteger result))cacultorblock{


   _result = cacultorblock(_result);
    
}

調用

- (void)viewDidLoad {
    [super viewDidLoad];
    caculoterManager *magr = [[caculoterManager alloc]init];
    
    [magr cacutor:^(NSInteger result){
        
        result += 5;
        result += 7;
        return result;
        
    }];

}
  • 分析 表面看有點難理解 其實我們不妨把它當成另一個函數
-(void)magrCautor:(NSString*)tz{

}

感覺是一個意思,后者傳遞的是字符串,前者傳遞的是block 代碼塊,區別在于前者控制 傳遞的代碼塊何時調用.AFN 封裝 等各種封裝 很多采用這種

- block 當返回值在開發中的應用
  • 主要應用場景:鏈式編程,Masonry就是最典型的鏈式編程,其內部原理用就是用block實現

  • 鏈式編程特點:把所有的語句 用.號連接起來,好處:可讀性非常號.

  • 代碼實現 我們先簡單寫一個返回值為block 的函數

-(void(^)())test{


    return ^{
    
    
    };
    
}

這個是一個返回值 為無返回值,無參數的block
現在我們來調用這個函數

self.test();
相當于 self.test 這個函數給我們返回block
然后我們去執行 block()

  • 操作:我們現在還做 一個需求 封裝一個計算器,提供一個累加方法

cacutotermanager

.h

@property(nonatomic,assign)int result;

-(cacutotermanager* (^)(int))add;

.m

-(cacutotermanager* (^)(int))add{

    return ^(int value){
    
        _result += value;
        
        return self;
        
    };
    
}

調用

  cacutotermanager *magr1 = [[cacutotermanager alloc]init];
   
    magr1.add(1).add(2).add(4);

模仿了 Masonry的鏈式編程方法.

  • 解析
    magr1.add調用方法后 返回block 執行 block(1);
    此時block 的返回類型是 cacutotermanager magr 再調用 magr(2)....

七 : 總結

通過上述大家對block 的應用應該了解的非常透徹了,希望大家活學活用. 根據需求選擇正確的方法.效率更高. ^ _ ^ 下期再見. 想聽什么可以在評論區留言. H5,Java 陸續會開始. 加油!!

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

推薦閱讀更多精彩內容