其實swift 的閉包跟 OC的block 是同樣同樣的,學會了block,你swift裏邊的閉包就會無師自通。html
參考:http://www.jianshu.com/p/e23078c11518c++
http://www.360doc.com/content/15/0901/11/10504424_496203197.shtmlweb
先來簡單介紹一下Block
Block是什麼?蘋果推薦的類型,效率高,在運行中保存代碼。用來封裝和保存代碼,有點像函數,Block能夠在任什麼時候候執行。swift
Block和函數的類似性:(1)能夠保存代碼(2)有返回值(3)有形參(4)調用方式同樣。多線程
定義一個簡單的block閉包
咱們再給a賦值爲20,此時打印出來a 的值仍是10函數
但當咱們在第一次給a 賦值時,前面加上__block 的時候,則打印出來20。ui
那麼爲何加上__block 後 就打印出20了呢,這個原理是什麼呢?google
其實能夠用兩個詞來歸納:傳值 和傳址。 可能這樣說你們以爲有點扯,接下來 用C++ 代碼進行編譯。
打開終端作以下操做 在當前文件夾下會獲得一個.cpp 文件。atom
此時打開當前的.cpp 文件(會有差很少10萬行代碼),前面咱們都忽略,只須要滾動到最後,此時你會發現block跟OC中的變化。
接下來咱們一個個來看這個block,先來看等號左邊的。
void(*block)()
這是一個沒有參數沒有返回值的函數指針,既然是一個函數指針,那它就是一個變量,變量裏面只能保存函數地址,而後它又在等號的左邊是否是意味着右邊返回的是一個函數地址(本身推斷)。
再看等號右邊:
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
參數(自我推斷):
__main_block_impl_0 這是一個函數名,這個函數有三個參數, com+F 搜索一下,又會發現這是一個結構體,結構體以下:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a;
可能你會疑惑,剛剛說這是一個函數,而如今是一個結構體。其實在 c++ 裏面結構體至關於OC的類,c++ 裏面結構體擁有本身的屬性以及構造方法和方法。那麼爲何取一個結構體的地址呢? 其實它取得是下面這段代碼的地址:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }
那麼在上面個方法實現裏,又有四個參數。而在剛剛調用的時候只有三個參數,多了一個參數 flags= 0,這個參數其實就至關於Swift中指定了一個默認值,不傳也有值,能夠忽略。那麼後面繼續:
impl.FuncPtr = fp; 將fp賦值給了 impl 結構體的 FuncPtr 參數, 在這個參數裏面存放的是下面這段代碼的地址:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // 這裏 int a = 10; printf("%d\\\\\\\\n",a); // 打印出a }
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
接下來,又從新給 a賦值爲 20,可是Block 最終要找到 FuncPtr 裏面存放的是值來執行, 在這裏纔會最終執行打印a 的值的代碼,可是這段代碼裏 a 是 10 了。因此最終打印的仍是10。
接下來看加上__block 的實現。
修改咱們的代碼:
再次在終端裏面進行編譯,你會發現生成的結構體會變化。
等號左邊會封裝一個__Block_byref_a_0 結構體類型的變量a,下面是結構體的聲明:
truct __Block_byref_a_0 {
void *__isa; //isa 類型的指針 本身的類型 __Block_byref_a_0 *__forwarding; //與本身結構體同名,是一個本身類型的結構體的指針,存放的是本身的地址 int __flags; // 標記 int __size; // 類型的大小 int a; // a 屬性 保存變量的值 };
等號右邊:
{(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
這裏僅僅是建立,由於使用了__block 因此建立了一個block 類型的結構體,接下來會纔是調用block,你會發現其他參數和第一種實現都同樣,惟一不一樣的是再去取值的時候,拿到的是結構體的地址,只要把地址傳遞過去,就有了最高的操做權限,到時候再去取值就能夠取到內存中最新的值。
接下來(a.__forwarding->a) = 20; 這句代碼是拿到結構體裏面的地址去修改a的值爲20。
後面再去打印,打印的就是內存地址中最新的值,因此就是20
========================================================
1、關於block的循環引用:
block屬性,通常用copy修飾;
1.1.若是沒有對block進行copy操做,block就存儲於棧空間
1.2.若是對block進行copy操做,block就存儲於堆空間---強引用
1.3.若是block存儲於棧空間,不會對block內部所用到的對象產生強引用
1.4.若是block存儲於堆空間,就會對block內部所用到的對象產生強引用
注意1:因爲使用了copy修飾,若是block中調用了block屬性的對象,就會形成循環引用
爲了不循環引用,須要對對象進行若引用修飾:
1 ICKPerson *p = [[ICKPerson alloc] init]; 2 // 一、修飾方法1 3 // __unsafe_unretained typeof(p) weakP = p; 4 // 二、修飾方法2 5 __block typeof(p) weakP = p; 6 p.testBlock = ^{ 7 [weakP run]; 8 };
2、關於block中變量的值:
2.1 若是變量沒有經過__block修飾,那麼block中的變量本質是值捕獲,在建立block的同時,是將變量的值傳入到block中,不管何時調用,變量的值就是最初傳進去的值
1 int age = 10; 2 void (^block)() = ^{ // 值捕獲 3 NSLog(@"age=%d", age);// 打印是10; 4 }; 5 age = 20; 6 block();
2.2 若是變量經過__block修飾,那麼block中的變量實際傳遞的是變量的地址,在建立block的同時,是將變量的地址傳入到block中,在調用block的時候,其變量的值是當時變量的值(經過地址(指針)獲取到)。
1 __block int age = 1; 2 void (^block)() = ^{ // 值捕獲 3 NSLog(@"age=%d", age);// 打印是20; 4 }; 5 age = 20; 6 block();
3、關於block的內部實現:
建立block的時候,內部是建立了對應的函數;
在調用block的時候,是調用了以前封裝的函數。
4、關於block的應用:
4.1.如何定義block
1 一、// inline 2 // blockName:block變量名 3 // 返回值類型(^變量名)(返回值類型) 4 <#returnType#>(^blockName)(<#parameterTypes#>) = ^(<#parameters#>) { 5 <#statements#> 6 }; 7 void(^block)() = ^(){ 8 NSLog(@"block"); 9 }; 10 二、// name:Block類型別名 11 typedef void(^MyBlock)() 12 MyBlock myBlock = ^(){ 13 };
4.2、調用block
1 block(); 2 myBlock();
4.3、實戰練習:
// 4.通信錄Block使用:
// 點擊保存,通知聯繫人刷新表格,用代理
// block:小弟 代理:打電話
// block:先把刷新表格的代碼保存起來
// 等用戶點擊了保存按鈕的時候,調用Block
4.3.1、在頭文件中(向其餘文件中傳遞數據的文件)定義一個block:是否帶參數,根據需求肯定
1 @class ICKAddViewController,ICKContact; 2 typedef void(^ICKAddViewControllerBlock)(ICKContact *contact); 3 @interface ICKAddViewController : UIViewController 4 @property (nonatomic, strong) ICKAddViewControllerBlock contactBlock; 5 @end
4.3.2、在獲取數據後,跳轉頁面以前,調用block,將數據傳遞過去
1 - (IBAction)addcontact { 2 ICKContact *contact = [ICKContact contactWithName:self.nameFiled.text andPhone:self.phoneFiled.text]; 3 // 調用block 4 if (self.contactBlock) { 5 self.contactBlock(contact); 6 } 7 [self.navigationController popViewControllerAnimated:YES]; 8 }
4.3.3、在獲取(保存、利用)數據的文件中(拿到獲取數據的對象的時候)調用其block屬性,保存block代碼段(實現特定功能的代碼)
1 // 跳轉控制器時數據傳遞 2 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{ 3 ICKAddViewController *addVc = segue.destinationViewController; 4 // 聲明block 5 addVc.contactBlock = ^(ICKContact *contact){ 6 [self.contacts addObject:contact]; 7 8 // 存儲數據 9 NSString *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; 10 NSString *path = [cache stringByAppendingString:@"contacts.data"]; 11 [NSKeyedArchiver archiveRootObject:self.contacts toFile:path]; 12 [self.tableView reloadData]; 13 }; 14 }
=====================================================================
文章末尾說一下block 析構的狀況處理。。
四、使用方將self或成員變量加入block以前要先將self變爲__weak
五、在多線程環境下(block中的weakSelf有可能被析構的狀況下),須要先將self轉爲strong指針,避免在運行到某個關鍵步驟時self對象被析構。
第4、第五條合起來有個名詞叫weak–strong dance,來自於2011 WWDC Session #322 (Objective-C Advancements in Depth)
如下代碼來自AFNetworking,堪稱使用weak–strong dance的經典。
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } };