總結(jié):
block本質(zhì)是指向一個結(jié)構(gòu)體的一個指針運行時機制 比較高級的特性 純C語言,平時寫的OC代碼 轉(zhuǎn)換成C語言運行時的代碼指令:clang
-rewrite-objc main.m(可以打印驗證),默認情況下,任何block都是在棧里面的,隨時可能被回收只要對其做一次copy操作 block的內(nèi)存就會放在堆里面不會釋放,只有copy才能產(chǎn)生一個新的內(nèi)存地址所有地址會發(fā)生改變
【手繪block詳解】
*************************筆記**************************
本文翻譯自蘋果的文檔,有刪減,也有添加自己的理解部分。
如果有Block語法不懂的,可以參考fuckingblocksyntax,里面對于Block
為了方便對比,下面的代碼我假設(shè)是寫在ViewController子類中的
1、第一部分
定義和使用Block,
- (void)viewDidLoad
{
[super viewDidLoad];
(1)定義無參無返回值的Block
void (^printBlock)() = ^(){
printf("no number");
};
printBlock();
printBlock(9);
int mutiplier = 7;
(3)定義名為myBlock的代碼塊,返回值類型為int
int (^myBlock)(int)
= ^(int num){
return num*mutiplier;
}
使用定義的myBlock
int newMutiplier = myBlock(3);
printf("newMutiplier is %d",myBlock(3));
}
定義在-viewDidLoad方法外部
(2)定義一個有參數(shù),沒有返回值的Block
void (^printNumBlock)(int)
= ^(int num){
printf("int
number is %d",num);
};
定義Block變量,就相當于定義了一個函數(shù)。但是區(qū)別也很明顯,因為函數(shù)肯定是在-viewDidLoad方法外面定義,而Block變量定義在了viewDidLoad方法內(nèi)部。當然,我們也可以把Block定義在-viewDidLoad方法外部,例如上面的代碼塊printNumBlock的定義,就在-viewDidLoad外面。
再來看看上面代碼運行的順序問題,以第(3)個myBlock距離來說,在定義的地方,并不會執(zhí)行Block{}內(nèi)部的代碼,而在myBlock(3)調(diào)用之后才會執(zhí)行其中的代碼,這跟函數(shù)的理解其實差不多,就是只要在調(diào)用Block(函數(shù))的時候才會執(zhí)行Block體內(nèi)(函數(shù)體內(nèi))的代碼。所以上面的簡單代碼示例,我可以作出如下的結(jié)論,
(1)在類中,定義一個Block變量,就像定義一個函數(shù);
(2)Block可以定義在方法內(nèi)部,也可以定義在方法外部;
(3)只有調(diào)用Block時候,才會執(zhí)行其{}體內(nèi)的代碼;
(PS:關(guān)于第(2)條,定義在方法外部的Block,其實就是文件級別的全局變量)
那么在類中定義一個Block,特別是在-viewDidLoad方法體內(nèi)定義一個Block到底有什么意義呢?我表示這時候只把它當做私有函數(shù)就可以了。我之前說過,Block其實就相當于代理,那么這時候我該怎樣將其與代理類比以了解呢。這時候我可以這樣說:本類中的Block就相當于類自己服從某個協(xié)議,然后讓自己代理自己去做某個事情。很拗口吧?看看下面的代碼,
定義一個協(xié)議
@protocol ViewControllerDelegate<NSObject>
- (void)selfDelegateMethod;
@end
本類實現(xiàn)這個協(xié)議ViewControllerDelegate
@interface ViewController
()<ViewControllerDelegate>
@property (nonatomic,
assign) id<ViewControllerDelegate>
delegate;
@end
接著在-viewDidLoad中的代碼如下,
- (void)viewDidLoad
{
[super viewDidLoad];
Do any
additional setup after loading the view from its nib.
self.delegate = self;
if (self.delegate
&& [self.delegate
respondsToSelector:@selector(selfDelegateMethod)])
{
[self.delegate selfDelegateMethod];
}
}
pragma
mark - ViewControllerDelegate method
實現(xiàn)協(xié)議中的方法
- (void)selfDelegateMethod
{
NSLog(@"自己委托自己實現(xiàn)的方法");
}
看出這種寫法的奇葩地方了嗎?自己委托自己去實現(xiàn)某個方法,而不是委托別的類去實現(xiàn)某個方法。本類中定義的一個Block其實就是閑的蛋疼,委托自己去字做某件事情,實際的意義不大,所以你很少看見別人的代碼直接在類中定義Block然后使用的,Block很多的用處是跨越兩個類來使用的,比如作為property屬性或者作為方法的參數(shù),這樣就能跨越兩個類了。
2、第二部分
__block關(guān)鍵字的使用
在Block的{}體內(nèi),是不可以對外面的變量進行更改的,比如下面的語句,
- (void)viewDidLoad
{
將Block定義在方法內(nèi)部
int x = 100;
void (^sumXAndYBlock)(int)
= ^(int y){
x = x+y;
printf("new x value is %d",x);
};
sumXAndYBlock(50);
}
這段代碼有什么問題呢,Xcode會提示x變量錯誤信息:Variable is not assigning (missing __blocktype),這時候給int
x = 100;語句前面加上__block關(guān)鍵字即可,如下:
__block int x = 100;
這樣在Block的{}體內(nèi),就可以修改外部變量了。
3、第三部分:Block作為property屬性實現(xiàn)頁面之間傳值
需求:在ViewController中,點擊Button,push到下一個頁面NextViewController,在NextViewController的輸入框TextField中輸入一串字符,返回的時候,在ViewController的Label上面顯示文字內(nèi)容,
(1)第一種方法:首先看看通過“協(xié)議/代理”是怎么實現(xiàn)兩個頁面之間傳值的吧,
NextViewController是push進入的第二個頁面
NextViewController.h 文件
定義一個協(xié)議,前一個頁面ViewController要服從該協(xié)議,并且實現(xiàn)協(xié)議中的方法
@protocol NextViewControllerDelegate
<NSObject>
- (void)passTextValue:(NSString *)tfText;
@end
@interface NextViewController : UIViewController
@property (nonatomic,
assign) id<NextViewControllerDelegate>
delegate;
@end
NextViewController.m 文件
點擊Button返回前一個ViewController頁面
-
(IBAction)popBtnClicked:(id)sender
{if (self.delegate
&& [self.delegate
respondsToSelector:@selector(passTextValue:)])
{self.inputTF是該頁面中的TextField輸入框
[self.delegate passTextValue:self.inputTF.text];
}
[self.navigationController
popViewControllerAnimated:YES];
}
接下來我們在看看ViewController文件中的內(nèi)容,
ViewController.m 文件
@interface ViewController
()<NextViewControllerDelegate>
@property (strong,
nonatomic) IBOutlet
UILabel *nextVCInfoLabel;
@end
點擊Button進入下一個NextViewController頁面
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
nextVC.delegate = self;//設(shè)置代理
[self.navigationController
pushViewController:nextVC animated:YES];
}
實現(xiàn)協(xié)議NextViewControllerDelegate中的方法
pragma
mark - NextViewControllerDelegate method
- (void)passTextValue:(NSString *)tfText
{
self.nextVCInfoLabel是顯示NextViewController傳遞過來的字符串Label對象
self.nextVCInfoLabel.text = tfText;
}
這是通過“協(xié)議/代理”來實現(xiàn)的兩個頁面之間傳值的方式。
(2)第二種方法:使用Block作為property,實現(xiàn)兩個頁面之間傳值,
先看看NextViewController文件中的內(nèi)容,
NextViewController.h 文件
@interface NextViewController : UIViewController
@property (nonatomic,
copy) void
(^NextViewControllerBlock)(NSString *tfText);
@end
NextViewContorller.m 文件
-
(IBAction)popBtnClicked:(id)sender
{if (self.NextViewControllerBlock)
{
self.NextViewControllerBlock(self.inputTF.text);
}
[self.navigationController
popViewControllerAnimated:YES];
}
再來看看ViewController文件中的內(nèi)容,
- (IBAction)btnClicked:(id)sender
{
NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil];
nextVC.NextViewControllerBlock = ^(NSString *tfText){
[self resetLabel:tfText];
};
[self.navigationController
pushViewController:nextVC animated:YES];
}
pragma
mark - NextViewControllerBlock method
- (void)resetLabel:(NSString *)textStr
{
self.nextVCInfoLabel.text = textStr;
}
好了就這么多代碼,可以使用Block來實現(xiàn)兩個頁面之間傳值的目的,實際上就是取代了Delegate的功能。
另外,博客中的代碼Sample Code可以再Github下載,如果因為Github被墻了,可以在終端使用git clone + 完整鏈接,即可克隆項目到本地。
Github中的代碼,可以開啟兩種調(diào)試模式,你需要在項目的配置文件BlockSamp-Prefix.pch中注釋或者解注釋下面的代碼:
define
Debug_BlcokPassValueEnable
即可開啟兩種調(diào)試的方式,如果注釋了上面的語句就是使用Delegate進行調(diào)試;否則使用Block進行調(diào)試。