objective-C Block對(duì)象

block對(duì)象簡(jiǎn)介及語(yǔ)法

什么是block?

block對(duì)象是一組指令,可以像調(diào)用函數(shù)指令那樣調(diào)用block對(duì)象。block對(duì)象結(jié)合了面向?qū)ο蠛兔嫦蜻^(guò)程編程的特點(diǎn)。

如何聲明一個(gè)block對(duì)象變量?

因?yàn)閎lock是對(duì)象,所以可以使用指針變量指向某個(gè)block變量。
block變量的類型指明了其應(yīng)指向的block對(duì)象類型,其中包含實(shí)參和返回值。代碼示例如下:

int (^adder)(int a, int b);

這行代碼聲明了一個(gè)名為adder的block變量,其指向的block對(duì)象應(yīng)該有兩個(gè)int實(shí)參,返回值也應(yīng)該是int類型。^符號(hào)代表adder是block變量,而不是普通的指針變量。更多示例:

void (^foo)(void);
void (^jump)(Hurdle *);
int (^double)(int);  //聲明時(shí)可以忽略實(shí)參參數(shù)名

Block對(duì)象的聲明語(yǔ)法與C語(yǔ)言的函數(shù)聲明語(yǔ)法類似,差別是要在變量前增加一個(gè)^符號(hào),并與變量名一起用圓括號(hào)括起來(lái)。

如何定義block對(duì)象?

變量只能保存若干字節(jié)長(zhǎng)度的數(shù)值,無(wú)法保存對(duì)象本身。例如,int變量可以保存數(shù)字,NSString *變量可以保存某個(gè)NSString對(duì)象的地址。Block變量也是這樣,它只能保存某個(gè)block變量的地址。

創(chuàng)建block對(duì)象的示例:

^int(int x, int y) {
    return x + y;
};

通常情況下,應(yīng)將新創(chuàng)建的block對(duì)象立即賦給某個(gè)block變量。(也可以將其作為實(shí)參傳入某個(gè)方法。)示例:

int (^adder)(int, int) = ^int(int x, int y) {
    return x + y;
};

以上代碼創(chuàng)建了一個(gè)名為adder的block變量,并指向了一個(gè)新創(chuàng)建的block對(duì)象,該對(duì)象有兩個(gè)int類型實(shí)參,并會(huì)返回兩個(gè)參數(shù)的和。

如何執(zhí)行某個(gè)block對(duì)象?

為block變量賦值后,就可以使用了。使用方法就像調(diào)用C函數(shù)那樣調(diào)用block變量,以執(zhí)行該block變量指向的block對(duì)象。示例:

int sum = adder(2, 5);

注意:

  1. block變量只能指向其類型匹配的block對(duì)象;
  2. 如果實(shí)現(xiàn)文件需要調(diào)用某個(gè)block對(duì)象,就必須有一個(gè)指向該block對(duì)象的變量;
  3. 如果某個(gè)方法創(chuàng)建了一個(gè)block對(duì)象,而另一個(gè)方法需要執(zhí)行該block對(duì)象時(shí),就必須將該block對(duì)象的變量作為實(shí)參傳給后者;
  4. 作為實(shí)參的block對(duì)象寫法為:xxx:(int ^(int, int)) adder,參數(shù)名應(yīng)聲明實(shí)參類型的括號(hào)后面。

捕獲變量(Variable Capturing)

block對(duì)象有一個(gè)很有用的特性,就是捕獲對(duì)象。
與函數(shù)類似,block對(duì)象也能使用傳入的實(shí)參并聲明局部變量。

int (^adder)(int, int) = ^(int x, int y) {
    int sum = x +y;
    return sum;
}

這段代碼中,sum是局部變量,可以在block對(duì)象內(nèi)部使用。xy都是block對(duì)象的實(shí)參,所以也能在block內(nèi)部使用。

此外,block對(duì)象也可以使用其封閉作用域(enclosing scope)內(nèi)的所有變量。對(duì)于聲明了某個(gè)block對(duì)象的方法,該方法的作用域就是這個(gè)block對(duì)象的封閉作用域。因此,這個(gè)block對(duì)象可以訪問(wèn)該方法的所有局部變量、傳入該方法的實(shí)參以及傳入當(dāng)前對(duì)象的實(shí)例變量。

當(dāng)某個(gè)block對(duì)象訪問(wèn)了一個(gè)在該block對(duì)象之前聲明的變量時(shí),可以稱該block捕獲了這個(gè)變量。在下圖代碼中,應(yīng)用會(huì)將val的值拷貝至block對(duì)象的內(nèi)存。當(dāng)應(yīng)用執(zhí)行該block對(duì)象時(shí),無(wú)論最初val的值是否發(fā)生變化,都會(huì)使用當(dāng)時(shí)賦值時(shí)刻的值。一旦block對(duì)象捕獲了某個(gè)變量,該變量之后的變化不會(huì)對(duì)block對(duì)象中的val產(chǎn)生任何影響。(在method方法返回的那一刻,最初的局部變量val會(huì)被釋放。)

捕獲的變量會(huì)被拷貝至Block對(duì)象

block可以捕獲任意類型的變量,也包括指向?qū)ο蟮闹羔?。因此,block對(duì)象代碼通過(guò)封閉作用域中的指針,向某個(gè)對(duì)象發(fā)送消息。

每個(gè)iOS應(yīng)用都有一個(gè)主操作隊(duì)列(main operation queue)。從本質(zhì)上看,該隊(duì)列中的操作都將成為循環(huán)中的事件,每次循環(huán)彈出一個(gè)。如下圖。

NSOperationQueue

我們可以用block對(duì)象來(lái)構(gòu)成操作,所以也可以將block對(duì)象加入主操作隊(duì)列。應(yīng)用會(huì)在某個(gè)運(yùn)行循環(huán)中執(zhí)行該block對(duì)象。加入操作隊(duì)列的block對(duì)象不能有返回值,也不能有任何實(shí)參。示例:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSString *string = @"hello, world!";
    
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"%lu", (unsigned long)[string length]);
    }];
    
    NSLog(@"About to exit method...");
    
    return YES;
}

輸出結(jié)果:

2014-12-16 17:40:31.644 Blocky[10442:2458003] About to exit method...
2014-12-16 17:40:31.670 Blocky[10442:2458003] 13

我們看到,控制臺(tái)首先輸出方法返回之前的退出信息,然后再顯示字符串長(zhǎng)度的計(jì)算結(jié)果。對(duì)當(dāng)前的運(yùn)行循環(huán),可能需要先完成視圖的繪制工作并釋放相應(yīng)的對(duì)象,再執(zhí)行某個(gè)block 。

那么問(wèn)題來(lái)了... ,string是指向@"hello world!"對(duì)象的唯一指針,當(dāng)應(yīng)用執(zhí)行完application: didFinishLaunchingWithOptions:方法后,不會(huì)立刻執(zhí)行block對(duì)象,但是string變量已經(jīng)不存在了,字符串對(duì)象會(huì)被釋放。但事實(shí)上,字符串對(duì)象依然存在。這是因?yàn)椋?strong>block對(duì)象會(huì)針對(duì)其封閉作用域內(nèi)的所有對(duì)象,保留強(qiáng)引用類型的引用。block對(duì)象會(huì)保留對(duì)該字符串對(duì)象的最后一個(gè)強(qiáng)引用,當(dāng)應(yīng)用釋放block時(shí),也會(huì)失去這最后一個(gè)應(yīng)用。

block的典型應(yīng)用(Typical Block Usage)

block最常見(jiàn)的一種用途使用來(lái)實(shí)現(xiàn)回調(diào)??梢詫⑦@種block對(duì)象看成是針對(duì)某個(gè)事件的一次性回調(diào),而且該事件可以在將來(lái)的某個(gè)時(shí)刻發(fā)生。通常會(huì)將這種用途的block對(duì)象稱為completion block

相對(duì)對(duì)delegate和target-action,使用completion block會(huì)簡(jiǎn)單很多。block對(duì)象是一種不依賴對(duì)象的回調(diào)。以NSArray為例,它有一個(gè)sortedArrayUsingComparator:方法。

NSArray *array = @[@"you", @"me", @"they"];

NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    if ([obj1 length] < [obj2 length])
        return NSOrderedDescending;
    else if ([obj1 length] > [obj2 length])
        return NSOrderedAscending;
    return NSOrderedSame;
}];

NSLog(@"%@", sorted);

block對(duì)象還有很多用途,例如串聯(lián)執(zhí)行不同的代碼(一個(gè)block對(duì)象調(diào)用另一個(gè)block對(duì)象,這個(gè)block對(duì)象再調(diào)用另一個(gè)block對(duì)象)。此外,block對(duì)象還可以幫助應(yīng)用充分利用多個(gè)CPU內(nèi)核,同步執(zhí)行多段代碼。

深入學(xué)習(xí):__block、簡(jiǎn)化語(yǔ)法與內(nèi)存管理(For the more Curious: The __block Modifier, Abbreviated Syntax, and Memory)

編譯器可以根據(jù)block對(duì)象中的return語(yǔ)句,自動(dòng)判斷返回類型。所以可以忽略返回類型。如果沒(méi)有實(shí)參也可以忽略實(shí)參。例如:

void (^block)(void) = ^{
    NSLog(@"I'm a silly block.");
};

但是在聲明block變量時(shí),不能忽略返回類型和實(shí)參列表。

如果某個(gè)變量是用__block修飾的,應(yīng)用就會(huì)在一塊特殊的內(nèi)存空間中保留該變量:既不是當(dāng)前棧,也不是block自身的內(nèi)存空間。這意味著可以有多個(gè)block對(duì)象修改同一個(gè)__block變量。示例:

__block int counter = 0;
void (^block)(void) = ^{
    counter ++;
    NSLog(@"Counter is now at %d", counter);
};

block();
block();
block();

控制臺(tái)會(huì)輸出:

2014-12-16 18:26:00.672 Blocky[10555:2477295] Counter is now at 1
2014-12-16 18:26:00.673 Blocky[10555:2477295] Counter is now at 2
2014-12-16 18:26:00.673 Blocky[10555:2477295] Counter is now at 3

block內(nèi)存管理

應(yīng)用會(huì)將新創(chuàng)建block對(duì)象保存在棧中。這意味著,即使應(yīng)用針對(duì)新創(chuàng)建的block對(duì)象保留了強(qiáng)引用指針,一旦創(chuàng)建該對(duì)象的方法返回,新創(chuàng)建的block對(duì)象一樣會(huì)被釋放。
要解決這個(gè)問(wèn)題,就要向block對(duì)象發(fā)送copy消息,執(zhí)行拷貝操作。如果收到copy消息的block對(duì)象已經(jīng)位于堆中的拷貝,就只會(huì)增加一個(gè)指向該對(duì)像的強(qiáng)引用,不會(huì)重復(fù)copy。
在Objective-C支持ARC之前,忘記copy是很嚴(yán)重的問(wèn)題。但是支持ARC之后,編譯器能夠?qū)lock對(duì)象執(zhí)行更多的自動(dòng)處理:即使應(yīng)用只是將某個(gè)的block對(duì)象賦給一個(gè)強(qiáng)引用特性的指針變量,并沒(méi)有執(zhí)行copy操作,編譯器也會(huì)自動(dòng)地完成copy工作。


歡迎來(lái)我的個(gè)站逛逛: http://alexyu.me/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容