<<iOS 與OS X多線程和內(nèi)存管理>>筆記:Blocks

注:本文為筆記形式,所以很多都是摘抄的.<<iOS 與OS X多線程和內(nèi)存管理>>書中寫的很棒,簡(jiǎn)單易懂,建議各位看官自己去看看.


前言


前面兩篇,我們主要說(shuō)到的是關(guān)于與內(nèi)存管理相關(guān)的技術(shù),接下來(lái),我們深入了解關(guān)于隱形函數(shù)Blocks的相關(guān)技術(shù).其實(shí)在華山論劍之淺談iOS的 target - action設(shè)計(jì)模式 和 代理模式 以及Bolck中也說(shuō)過(guò)Block相關(guān)的使用方式,不過(guò)都略顯粗糙,所以這次結(jié)合著<<iOS 與OS X多線程和內(nèi)存管理>>這本書,來(lái)深入了解一下關(guān)于Blocks的相關(guān)技術(shù).


Blocks簡(jiǎn)介


在書中說(shuō)到int,float是C語(yǔ)言的擴(kuò)充功能,它是一個(gè)帶有自動(dòng)變量的匿名函數(shù).在日常開(kāi)發(fā)過(guò)程中,我覺(jué)得與其說(shuō)Blocks是一個(gè)匿名函數(shù),不如說(shuō)是它是一種數(shù)據(jù)類型更好理解一點(diǎn),像int,float一樣存儲(chǔ)數(shù)據(jù),不過(guò)int,float存儲(chǔ)的是變量,而B(niǎo)locks存儲(chǔ)的卻是一個(gè)函數(shù).

比如我們可以把一個(gè)Blocks聲明成一個(gè)類的屬性.首先,在ViewController中,我們先給Block變量改一下名稱,我們就可以聲明成ViewController的屬性了.如下所示.

typedef int(^Block)(void);

@interface ViewController : UIViewController

@property(nonatomic,copy)Block block;
@end

然后我們就可以給屬性賦值了.比如我們?nèi)缦逻M(jìn)行一個(gè)簡(jiǎn)單的賦值.

    self.block = ^int{ return 1; };

調(diào)用也是相當(dāng)?shù)暮?jiǎn)單,如下所示.

    self.block();

那么在實(shí)際開(kāi)發(fā)過(guò)程中,Blocks主要是用于什么樣的場(chǎng)景呢?或者說(shuō)Blocks的主要作用是什么呢?在我們實(shí)際開(kāi)發(fā)過(guò)程中,Blocks主要用于傳值和回調(diào),這一點(diǎn)類似于OC中代理協(xié)議模式.現(xiàn)在我們舉個(gè)例子說(shuō)明一下它的主要作用.

假設(shè)有兩個(gè)控制器,一個(gè)是ViewController對(duì)象,另外一個(gè)是NewViewController對(duì)象;其中,NewViewController對(duì)象是通過(guò)ViewController對(duì)象pushViewController推出來(lái)的,我們的目的是通過(guò)在NewViewController進(jìn)行操作,然后給ViewController進(jìn)行傳值操作.示意圖如下所示.

接下來(lái)我們一步一步的進(jìn)行實(shí)現(xiàn),首先,我們需要在NewViewController聲明Block屬性,Block的類型是有參數(shù),無(wú)返回值類型的.如下所示.

#import <UIKit/UIKit.h>
typedef void(^Block)(int);
@interface NewViewController : UIViewController

@property(nonatomic,copy)Block block;
@end

接下來(lái),我們?cè)赩iewController中創(chuàng)建NewViewController對(duì)象,然后對(duì)Block進(jìn)行賦值.

    NewViewController *newVC = [[NewViewController alloc]init];
    
    newVC.block = ^(int number){
    
       printf("value = %d",number);
    };

緊接著就是通過(guò)pushViewController這方法推出NewViewController控制器頁(yè)面.

    [self.navigationController pushViewController:newVC animated:YES];

然后,我們?cè)贜ewViewController控制器中進(jìn)行Block的調(diào)用.

    self.block(666);

通過(guò)我們調(diào)用,我們就可以在控制臺(tái)打印出如下的信息了.

具體可對(duì)照查看BlockDemo


Blocks語(yǔ)法


對(duì)于Blocks語(yǔ)法,騷棟只想用一句話來(lái)表述,那就是能省則省,我們首先看一個(gè)最完整的Blocks的寫法,如下圖所示.

我們?cè)谶@舉一個(gè)例子來(lái)說(shuō)明一下.

    ^int(int number){
        return number;
    }

接下來(lái),我們則說(shuō)一下什么叫能省則省.凡是沒(méi)有的部分我們都可以省去,比如我們沒(méi)有返回值,我們表示的完整方式如下所示.

    ^ void (int number){
    
        printf(@"value = %d",number);
    }

上面的這個(gè)block等同于下面的block表達(dá)式.

    ^ (int number){
        printf(@"value = %d",number);
    }

再比如,如果我們參數(shù)也沒(méi)有,返回值也沒(méi)有的block,用完整的形式如下表示可以如下表示.

   ^ void ( void ){
        printf(@"value = %d",number);
    }

等同于

   ^ {
        printf(@"value = %d",number);
    }

</br>

Blocks類型變量


Blocks簡(jiǎn)介中,我們就對(duì)Blocks變量作為對(duì)象屬性進(jìn)行了簡(jiǎn)單的舉例.其實(shí),我們?cè)趯?shí)際開(kāi)發(fā)過(guò)程中也是這樣用的,我們一般要先給block改名,這是為什么呢,因?yàn)閎lock很長(zhǎng),所以我們給block改名字后減少了我們代碼量,而且形式上更加容易理解.為了比對(duì)改名前后的變化,我們舉例來(lái)說(shuō)明一下.

首先我們定義一個(gè)沒(méi)有改名的block,然后我們對(duì)其進(jìn)行賦值.

    int (^blk)(void);

    blk= ^(void){
    
        return 1;  
    };

然后,我們先給block進(jìn)行改名操作之后,再進(jìn)行進(jìn)行賦值操作.

typedef int(^Block)(void);

然后聲明變量,緊接著進(jìn)行賦值.

    Block block;
    
    block = ^(void){
        
        return 1;
    };


截獲自動(dòng)變量值和截獲自動(dòng)變量


block具有截獲自動(dòng)變量值的功能,那么什么叫截獲自動(dòng)變量值呢?我們看下面的示例.

    int i = 10;
    
    void (^blk)(void) = ^{
    
        printf("Value = %d",i);
    };
  
    i = 5;
    blk();

    printf("value = %d",i);

我們看上面的代碼,首先i的初始值為10,然后我們創(chuàng)建了一個(gè)隱形函數(shù)block,并在其中打印了i的值,但是這時(shí)候,我們并沒(méi)有調(diào)用這個(gè)隱性函數(shù),當(dāng)我們把i重新賦值為5之后,緊接著我們調(diào)用上面的隱性函數(shù).那么我們控制器將會(huì)打印出處如下所示的結(jié)果.

從而,我們知道,當(dāng)隱形函數(shù)block進(jìn)行聲明實(shí)現(xiàn)的時(shí)候就截獲了i的值為10,所以調(diào)用的時(shí)候并不是5,而是10.這就是隱形函數(shù)block的截獲自動(dòng)變量值.

那么,截獲自動(dòng)變量又是什么意思呢?主要是指截獲OC中的對(duì)象,比如我們截獲一個(gè)數(shù)組并給他添加元素.

    NSMutableArray *array = [[NSMutableArray alloc]init];
    
    void (^blk)(void) = ^{
        
        id obj = [[NSObject alloc]init];
        
        [array addObject:obj];
        
    };

問(wèn)題來(lái)了,我們可不可以直接通過(guò)block給變量賦值呢?如下所示.

    int i = 10;
    
    void (^blk)(void) = ^{i = 1;};

或者是這樣對(duì)對(duì)象進(jìn)行重新賦值操作.

    NSMutableArray *array ;
    
    void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
    };

那么如果你這樣寫了,直接會(huì)報(bào)編譯錯(cuò)誤.如下圖所示.

那么這又是怎么回事呢?我們上面不是明明能打印嗎?為什么這樣使用block就會(huì)出現(xiàn)問(wèn)題呢?我們又該如何解決呢?我們接著看下一個(gè)模塊.

</br>

__block說(shuō)明符


上面的問(wèn)題是怎么出現(xiàn)的呢?在書中是這樣說(shuō)的,如果用C語(yǔ)言來(lái)描述,即是截獲NSMutableArray類的對(duì)象用的結(jié)構(gòu)體實(shí)例指針,雖然賦值給截獲的自動(dòng)變量array的操作會(huì)產(chǎn)生編譯錯(cuò)誤,但使用截獲的值卻不會(huì)有任何的問(wèn)題.我們可以總結(jié)為以下的幾句話.

不管是基本數(shù)據(jù)類型的變量還是對(duì)象變量.在使用block對(duì)其進(jìn)行操作的時(shí)候,我們?cè)试S使用變量的值,但是不允許直接給變量進(jìn)行賦值操作.

那么我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題呢?我們只需要在block需要賦值的變量 前面加上一個(gè)__block說(shuō)明符即可.如下所示.

    __block int i = 10;
    void (^blk)(void) = ^{i = 1;};
    __block NSMutableArray *array ;
    
    void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
    };

這樣,就不會(huì)再報(bào)編譯錯(cuò)誤了.我們也就完美解決了上述的問(wèn)題了.

</br>

Blocks循環(huán)引用


Blocks循環(huán)引用是什么原因造成的呢?Blocks循環(huán)引用跟我們前一篇博客中說(shuō)的自己引用自己的情況差不多.如下圖所示.

具體的代碼示例,我們可以在一個(gè)控制器進(jìn)行演示,整體代碼如下所示.

#import "NewViewController.h"

typedef void(^Block)(void);

@interface NewViewController ()

@property(nonatomic,copy)Block block;
@property(nonatomic,strong)NSString *objString;

@end

@implementation NewViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    self.objString = @"你好";
    
    self.block = ^(){
    
        NSLog(@"%@",self.objString);
    
    };
}

@end

我們首先進(jìn)行在控制器的延展部分聲明了block屬性對(duì)象和一個(gè)測(cè)試用的字符串屬性對(duì)象.

@property(nonatomic,copy)Block block;
@property(nonatomic,strong)NSString *objString;

然后我們?cè)赩iewDidLoad方面方法對(duì)字符串進(jìn)行賦值.

    self.objString = @"你好";

緊接著,我們就在block的實(shí)現(xiàn)中使用字符串.

    self.block = ^(){
    
        NSLog(@"%@",self.objString);
    };

這時(shí)候Xocde會(huì)直接拋出一個(gè)編譯警告,意思就是存在內(nèi)存泄漏.

那么解決方案也是跟前一篇博客說(shuō)的那樣,直接使用__weak修飾符.如下所示.

    __weak typeof(self)tmpe =self;
    
    self.block = ^(){
    
        NSLog(@"%@",tmpe.objString);
    };


尾聲


第三個(gè)模塊是關(guān)于多線程GCD的API相關(guān)因?yàn)橐郧岸甲鲞^(guò)了,所以接下來(lái),將看另外一本書,這本<<iOS 與OS X多線程和內(nèi)存管理>>就到這了.歡迎大家一起來(lái)討論<<iOS 與OS X多線程和內(nèi)存管理>>相關(guān)問(wèn)題,如果有任何問(wèn)題,歡迎聯(lián)系騷棟,謝謝.最后還是<<iOS 與OS X多線程和內(nèi)存管理>>的pdf版的下載傳送門,各位看官可以自行去參考查看.

<<iOS 與OS X多線程和內(nèi)存管理>>的pdf版?zhèn)魉烷T??


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容