iOS SDK 4.0開始,Apple引入了block這一特性,而自從block特性誕生之日起,彷佛它就受到了Apple特殊的照顧和青睞。字面上說,block就 是一個代碼塊,可是它的神奇之處在於在內聯(inline)執行的時候(這和C++很像)還能夠傳遞參數。同時block自己也能夠被做爲參數在方法和函 數間傳遞,這就給予了block無限的可能。objective-c
在平常的coding裏絕大時間裏開發者會是各類block的使用者,可是當你須要構建一些比較基礎的,提供給別人用的類的時候,使用block會 給別人的使用帶來不少便利。固然若是你已經厭煩了一直使用delegate模式來編程的話,偶爾轉轉寫一些block,不只能夠鍛鍊思惟,也能讓你寫的代 碼看起來高端洋氣一些,並且由於代碼跳轉變少,因此可讀性也會增長。算法
先來看一個簡單的block吧:編程
// Defining a block variable BOOL (^isInputEven)(int) = ^(int input) { if (input % 2 == 0) { return YES; } else { return NO; } };
以上定義了一個block變量,block自己就是一個程序段,所以有返回值有輸入參數,這裏這個block返回的類型爲BOOL。天賦異秉的OC 用了一樣不走尋常路的"{% raw %} ^{% endraw %}"符號來表示block定義的開始(就像用減號和加號來定義方法同樣),block的名稱緊跟在{% raw %} ^{% endraw %}符號以後,這裏是isInputEven(也即之後使用inline方式調用該block時所須要的名稱)。這段block接受一個int型的參數, 而在等號後面的int input是對這個傳入int參數的說明:在該block內,將使用input這個名字來指代傳入的int參數。一開始看block的定義和寫法時可能會 比較痛苦,可是請謹記它只是把咱們常見的方法實現換了一種寫法而已,請以習慣OC中括號發送消息的速度和決心,儘快習慣block的寫法吧!xcode
調用這個block的方法就很是簡單和直觀了,相似調用c函數的方式便可:多線程
// Call similar to a C function call int x = -101; NSLog(@"%d %@ number", x, isInputEven(x) ? @"is an even" : @"is not an even");
不出意外的話輸出爲-101 is not an even number函數
以上的用法沒有什麼特別之處,只不過是相似內聯函數罷了。可是block的神奇之處在於block外的變量能夠無縫地直接在block內部使用,好比這樣:post
float price = 1.99; float (^finalPrice)(int) = ^(int quantity) { // Notice local variable price is // accessible in the block return quantity * price; }; int orderQuantity = 10; NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
輸出爲Ordering 10 units, final price is: $19.90動畫
至關開心啊,block外的price
成功地在block內部也能使用了,這意味着內聯函數可使用處於同一scope裏的局部變量。可是須要注意的是,你不能在block內部改變本地變量的值,好比在{% raw %} ^{% endraw %}{}裏寫price = 0.99
這樣的語句的話,你親愛的compiler必定是會叫的。而更須要注意的是price
這樣的局部變量的變化是不會體如今block裏的!好比接着上面的代碼,繼續寫:ui
price = .99; NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
輸出仍是Ordering 10 units, final price is: $19.90,這就比較憂傷了,能夠理解爲在block內的price
是readonly的,只在定義block時可以被賦值(補充說明,其實是由於price
是value type,block內的price
是在申明block時複製了一份到block內,block外面的price
不管怎麼變化都和block內的price
無關了。若是是reference type的話,外部的變化其實是會影響block內的)。atom
可是若是確實須要傳遞給block變量值的話,能夠考慮下面兩種方法:
一、將局部變量聲明爲__block
,表示外部變化將會在block內進行一樣操做,好比:
// Use the __block storage modifier to allow changes to 'price' __block float price = 1.99; float (^finalPrice)(int) = ^(int quantity) { return quantity * price; }; int orderQuantity = 10; price = .99; NSLog(@"With block storage modifier - Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
此時輸出爲With block storage modifier – Ordering 10 units, final price is: $9.90
二、使用實例變量——這個比較沒什麼好說的,實例內的變量橫行於整個實例內..可謂霸道無敵...=_=
block外的對象和基本數據同樣,也能夠做爲block的參數。而讓人開心的是,block將自動retain傳遞進來的參數,而不需擔憂在 block執行以前局部對象變量已經被釋放的問題。這裏就不深究這個問題了,只要嚴格遵循Apple的thread safe來寫,block的內存管理並不存在問題。(更新,ARC的引入再次簡化了這個問題,徹底不用擔憂內存管理的問題了)
因爲block的靈活的機制,致使iOS SDK 4.0開始,Apple大力提倡在各類地方應用block機制。最典型的當屬UIView的動畫了:在4.0前寫一個UIView的Animation大概是這樣的:
[UIView beginAnimations:@"ToggleSiblings"context:nil]; [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES]; [UIViewsetAnimationDuration:1.0f]; // Make your changes [UIView commitAnimations];
在一個不知名的小角落裏的begin/commit兩行代碼間寫下須要進行的動做,而後靜待發生。而4.0後這樣的方法直接被discouraged了(雖然還沒Deprecated),取而代之的正是block:
[UIView animateWithDuration:5.0f animations:^{ view.opacity = 0.5f; }];
簡單明瞭,一切就這麼發生了..
可能有人會以爲block的語法很奇怪,不像是OOP的風格,誠然直接使用的block看起來破壞了OOP的結構,也讓實例的內存管理出現了某些「看上去奇怪」的現象。可是經過typedef
的方法,能夠將block進行簡單包裝,讓它的行爲更靠近對象一些:
typedef double (^unary_operation_t)(double op);
定義了一個接受一個double型做爲變量,類型爲unaryoperationt的block,以後在使用前用相似C的語法聲明一個unaryoperationt類型的"實例",而且定義內容後即可以直接使用這個block了~
unary_operation_t square;
square = ^(double operand) { return operand * operand; }
囉嗦一句的仍是內存管理的問題,block始終不是對象,而block的內存管理天然也是和普通對象不同。系統會爲block在堆上分 配內存,而當把block當作對象進行處理時(好比將其壓入一個NSMutableArray),咱們須要獲取它的一份copy(好比[square copy]),而且在Array retain了這個block後將其釋放([square autorelease]是不錯的選擇)。而對於block自己和調用該block的實例,則能夠放心:SDK會將調用block的實例自動 retain,直至block執行完畢後再對實例release,所以不會出現block執行到一半,實例就被dealloc這樣的尷尬的局面。 在ARC的時代,這些都是廢話了。打開ARC,而後瞎用就能夠了。ARC解決了block的最讓開發者頭疼的最大的也是惟一的問題,內存管理。關於block的內存管理方面,有一個很好玩的小quiz,能夠作作玩~傳送門
iOS SDK 4.0之後,隨着block的加入不少特性也隨之添加或者發生了升級。Apple所推薦的block使用範圍包括如下幾個方面:
block使用時應注意的事項:
1. 可訪問在同一範圍內的全局變量包括靜態變量。
2. 能夠訪問傳遞給塊的參數(如同函數參數)。
3. 同一範圍的棧(非static)變量視做const變量。它們的值相似塊表達式。嵌套塊時,從最近的做用域取值。
4. 在同一範圍內聲明的變量,若是有__block修飾符修飾,則值是可變的。在該範圍內包括同一範圍內的其餘塊對該變量的改變,都將影響該做用域。具體見「__block 存儲類型」。
5. 在塊的範圍內(塊體)聲明的本地變量,相似於函數中的本地變量。塊的每次調用都會致使從新拷貝這些變量。這些變量可做爲const或參考(by-reference)變量。
block塊回調的例子
Book.h
1
2
3
4
5
6
7
8
|
#import <Foundation/Foundation.h>
typedef
void
(^PublishBlock) (NSString *name);
@interface Book : NSObject
@property (nonatomic, strong) PublishBlock block;
- (
void
) nslogAll;
- (
void
) printAll:(PublishBlock) block;
@end
|
Book.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#import "Book.h"
@implementation Book
-(
void
)nslogAll{
NSString *name = [NSString stringWithFormat:@
"helloworld!!!"
];
if
(self.block) {
self.block(name);
}
}
- (
void
)printAll:(PublishBlock)block{
NSString *name = [NSString stringWithFormat:@
"HELLOWORLD!!!"
];
if
(block) {
block(name); // 調用block傳值
}
}
@end
|
函數調用時的類名
ViewController.h
1
2
3
4
5
6
7
8
9
|
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
- (IBAction)huiDiao_Action:(id)sender;
@end
|
ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#import "ViewController.h"
#import "Book.h"
@interface ViewController ()
@end
@implementation ViewController
- (
void
)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (
void
)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)huiDiao_Action:(id)sender {
Book *book = [[Book alloc]init];
book.block = ^(NSString *name){
NSLog(@
"++++xingming:%@"
,name);
};
[book nslogAll];
[book printAll:^(NSString *name) { // block做爲參數傳遞
NSLog(@
"+++++XINGMING:%@"
,name);
}];
}
@end
|
1. 做爲屬性而存在的Block
testBlock.m文件裏
@property (copy, nonatomic) void (^aBlock)(); // MRC下,block屬性必須是顯式標註copy策略; ARC下,其實能夠不顯式標明copy, xcode會自動對block屬性採起copy策略
- (void)viewDidLoad {
[super viewDidLoad];
self.aBlock = ^{
NSLog(@"%@",self);
};
}
當block在棧內存時,Block對內部的對象只是弱引用
1.1 copy關鍵字
block默認是在存儲在棧內存, 通過copy,會將block複製到堆內存
2. 在方法內部的Block
- (void)viewDidLoad {
[super viewDidLoad];
void (^innerFunctionBlock)() = ^{
NSLog(@"方法內部的block, 有人管這叫內聯Block");
};
innerFunctionBlock();
}
3. 其餘
- (void)viewDidLoad { ...2.裏的代碼塊... }
void (^block)() = ^{
NSLog(@"相似全局變量的Block, 其實和方法差很少");
};
總結: 1. 若是沒有對block作copy操做, block就存儲於棧內存
2. 若是對block作copy操做, block就存儲於堆內存
3. 若是block存儲於棧空間, 對block內部的所用到的外部對象,是弱引用
4. 若是block存儲於堆空間, 對block內部的所用到的外部對象,是強引用
5. 解除循環引用的方法: 5.1 ARC:使用 __weak, 有時不能用__weak,那就用 __unsafe_unretained , 這種狀況極少發生
5.2 MRC:使用__block
5.3 在調用完block以後,將block = nil
6. NSInteger a = 1;
void (^testBlock)(void) = ^{
NSLog(@"a=%d",a);
};
++a;
testBlock();
打印出來確定仍是1, 由於block在編譯階段就肯定了, 編譯的時候block內部的 NSLog(@"a=%d",a) 等價於 NSLog(@"a=%d",1) ,此時a在代碼塊中是value type,此時是不能改變值的;
想要block內部的a是引用,須要用到__block聲明變量a: __block NSInteger a = 1; 這樣編譯編輯階段,block內部的 NSLog(@"a=%d",a) 等價於 NSLog(@"a=%d",*(&a))
7. 不要迷信xcode能幫你檢查出全部block的循環引用問題,xcode只能檢查出比較淺顯的,這個尤爲須要注意
仔細研讀4.0的SDK的話,會發現不少經常使用類中都加入了很多帶block做爲參數的方法,改變固有思惟習慣,適應並儘量利用block給程序上帶來的便捷,無疑是提升效率和使代碼優雅的很好的途徑~