有趣 IOS 开展 - block 使用具体解释
Block 它是iOS于4.0新的程序語法之后,于iOS SDK 4.0之后,block應用幾乎無處不在。
在其他語言中也有類似的概念,稱為閉包(closure),實例object C兄弟swift 中閉包(swift 閉包具體解釋)的使用跟 OC的block一樣重要。總的來說:
Block是C語言的
Block是一個數據類型
Block 是一個提前準備好的代碼。在須要的時候運行
1. block作用:
Block用來封裝一段代碼,能夠在不論什么時候運行;
- Block能夠作為函數參數或者函數的返回值,而其本身又能夠帶輸入參數或返回值。
- 蘋果官方建議盡量多用block。在多線程、異步任務 、集合遍歷、集合排序、動畫轉場用的非常多
在新的iOS API中block被大量用來代替傳統的delegate和callback,而新的API會大量使用block主要是基于以下兩個原因:
A. 能夠直接在block代碼塊中寫等會要接著運行的代碼,直接把block變成函數的參數傳入函數中,這是新API最常使用block的地方。
B. 能夠存取局部變量,在傳統的callback操作時,若想要存取局部變量得將變量封裝成結構體才干使用,而block則是能夠非常方便地直接存取局部變量。
2. Block的定義:
定義時。把block當成數據類型
特點:
1. 類型比函數定義多了一個 ^
2. 設置數值,有一個 ^,內容是 {} 括起的一段代碼
(1)基本定義方式
/* *1.最簡單的定義方式: *格式:void (^myBlock)() = ^ { // 代碼實現; } */ void (^myBlock)() = ^ {NSLog(@"hello"); };// 運行時。把block當成函數 myBlock();/* *2.定義帶參數的block: *格式:void (^block名稱)(參數列表) = ^ (參數列表) { // 代碼實現; } */ void (^sumBlock)(int, int) = ^ (int x, int y) {NSLog(@"%d", x + y); };sumBlock(10, 20);/* *3.定義帶返回值的block *格式:返回類型 (^block名稱)(參數列表) = ^ 返回類型 (參數列表) { // 代碼實現; } */ int (^sumBlock2)(int, int) = ^ int (int a, int b) {return a + b; };NSLog(@"%d", sumBlock2(4, 8));(2) block 指針
Block Pointer是這樣定義的:
回傳值 (^名字) (參數列);
//聲明一個名字為square的Block Pointer,其所指向的Block有一個int輸入和int輸出 int (^square)(int); //block 指針square的內容 square = ^(int a){ return a*a ; }; //調用方法,感覺是是不是非常像function的使用方法? int result = square(5); NSLog(@"%d", result);(3) 用typedef先聲明類型,再定義變量進行賦值
typedef int (^MySum)(int,int); MySum sum = ^(int a,int b){ return a + b; };(4) block 訪問外部變量
可是block使用有個特點。Block能夠訪問局部變量,可是不能改動:
int sum = 10;int (^MyBlock)(int) = ^(int num) { sum++;//編譯報錯 return num * sum; };假設要改動就要加關鍵字 __block (以下具體說明):
__block int sum =10;
(5) block 與函數指針
以下比較下函數指針與block異同:
定義函數指針 int (*myFn)();
調用函數指針 (*myFn)(10, 20);定義Block int (^MyBlocks)(int,int);
調用Blocks MyBlocks(10, 20);
3. block訪問外部變量
block 訪問外部變量有幾個特點必須知道:
- block內部能夠訪問外部變量。
- 默認情況下block內部不能改動外面的局部變量;
- 給局部變量加上關鍵字_block,這個局部變量就能夠在block內部改動;
block中能夠訪問外部變量。可是不能改動它。否則編譯錯誤。可是能夠改變全局變量、靜態變量(static)、全局靜態變量。
上面的特點是有原因滴:
A. 為何不讓改動變量:這個是編譯器決定的。理論上當然能夠改動變量了。僅僅只是block捕獲的是外部變量的副本,名字一樣。
為了不給開發人員迷惑,干脆不讓賦值。
道理有點像:函數參數。要用指針,不然傳遞的是副本(大家想起那個經典的兩個數調換值的問題了吧)。
B. 能夠改動靜態變量的值。
靜態變量屬于類的,不是某一個變量。所以block內部不用調用cself指針。
所以block能夠調用。
(1) __block存儲類型
通過__block存儲類型修飾符, 變量在block中可被改動。__block存儲跟register、auto和static存儲類型相似(可是之間相互排斥),用于局部變量。
__block變量存儲在堆區,因此。這個block使用的外部變量,將會在棧結束被留下來。
從優化角度考慮,block存儲在棧上,假設block被拷貝(通過Block_copy或者copy),變量被復制到堆。因此__block變量的地址就會改變。
__block變量還有兩個限制,他們不能是可變數組(NSMutableArray),不能是結構體(structure)。
__block 變量的內部實現要復雜很多,__block 變量事實上是一個結構體對象,拷貝的是指向該結構體對象的指針
(2) block訪問外部變量
上面已經說過,默認block 訪問的外部變量是僅僅讀屬性的,若要對外部變量進行讀寫,須要在定義外部變量時加一個 __block, 示比例如以下:
//演示樣例1:block訪問外部變量 void demoBlock1() {int x = 10;NSLog(@"定義前 %p", &x);// 局部變量在棧區// 在定義block的時候,假設引用了外部變量,默認是把外部變量當做是常量編碼到block當中,而且把外部變量copy到堆中,外部變量值為定義block時變量的數值// 假設興許再改動x的值,默認不會影響block內部的數值變化!// 在默認情況下。不同意block內部改動外部變量的數值!由于會破壞代碼的可讀性,不易于維護!
void(^myBlock)() = ^ { NSLog(@"%d", x); NSLog(@"in block %p", &x); // 堆中的地址 }; //輸出是10,由于block copy了一份x到堆中 NSLog(@"定義后 %p", &x); // 棧區 x = 20; myBlock(); } //演示樣例2:在block中改動外部變量 void demoBlock2() {// 使用 __block,說明不在關心x數值的具體變化__block int x = 10;NSLog(@"定義前 %p", &x); // 棧區// !定義block時,假設引用了外部使用__block的變量,在block定義之后, block外部的x和block內部的x指向了同一個值,內存地址同樣void (^myBlock)() = ^ {x = 80;NSLog(@"in block %p", &x); // 堆區};NSLog(@"定義后 %p", &x); // 堆區myBlock();NSLog(@"%d", x);//打印x的值為8,且地址在堆區中 }以下的樣例就有點難度了。讓我們看下block對指針變量的訪問
//樣例3:block對指針變量的訪問 void demoBlock3() { // !指針記錄的是地址NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];//strM是指針,其在堆中存儲的是zhangsan這個string在內存中的的地址值//&strM是指針strM在堆中的地址NSLog(@"定義前 %p %p", strM, &strM); void (^myBlock)() = ^ {/*首先調用block會對strM(指針)進行一份copy,這份copy會在堆中創建還有一個指針,這個指針存儲的值同strM。都是zhangsan的地址。即新copy的指針指向的內容沒有變*/// 注意以下的操作是改動strM指針指向的內容[strM setString:@"lisi"];NSLog(@"inblock %p %p", strM, &strM);//輸出:strM沒有變。由于存儲的都是zhangsan的地址,&strM為堆中新地址/**這句代碼是改動指針strM。由于strM copy過來后是僅僅讀的,所以同樣例2編譯會報錯,須要在定義strM時加__blockstrM = [NSMutableString stringWithString:@"wangwu"];NSLog(@"inblock %p %p", strM, &strM);*/};//大家想想使用__block輸出會是什么呢NSLog(@"定義后 %p %p", strM, &strM);myBlock();NSLog(@"%@", strM); }上面的樣例搞定了,來讓我們看下各種類型的變量與block之間的互動:
//演示樣例4:各種類型的變量和block之間的互動extern NSInteger CounterGlobal;static NSInteger CounterStatic;NSInteger localCounter = 42 ;__block char localCharacter;void (^aBlock)( void ) = ^( void ){++ CounterGlobal ; //能夠存取。 ++ CounterStatic ; //能夠存取。 CounterGlobal = localCounter; //localCounter在block 建立時就不可變了。 localCharacter = 'a' ; //設定外面定義的localCharacter 變數。 }; ++localCounter; //不會影響的block 中的值。 localCharacter = 'b' ; aBlock(); //運行block 的內容。 //運行完后,localCharachter 會變成'a'(3) block 引用成員變量
OC對象。不同于基本類型。Block會引起對象的引用計數變化。若我們在block中引用到oc的對象。則對象的引用計數器會加1, 只是在對象前 加__block修飾,則參考計數不變。
- 若直接存取實例變量(instance variable),self的參考計數將被加1。- 若透過變量存取實例變量的值,則變量的參考計數將被加1。 - 在對象前加 __block 則參考計數不會自己主動加1。
//樣例1:定義一個變量間接給block調用,成員變量引用計數不變 dispatch_async (queue, ^{// 由于直接存取實例變量instanceVariable ,所以self 的retain count 會加1doSomethingWithObject (instanceVariable); });//通過 id localVaribale = instanceVariable; dispatch_async (queue, ^{//localVariable 是存取值,所以這時僅僅有localVariable 的retain count 加1//self 的 return count 并不會添加。doSomethingWithObject (localVaribale); });
上面僅僅是簡單演示下block引用成員變量,以下我們研究下block引用成員變量時出現的一個經典問題:循環引用。
在block內部使用成員變量,例如以下:
@interface ViewController : UIViewController { NSString *_string; } @end在block創建中:
_block = ^(){ NSLog(@"string %@", self.string); };上面代碼中block是會對內部的成員變量進行一次retain, 即self會被retain一次。
對于block 使用 成員變量self.string來說。block內部是直接強引用self的。也就是block持有了self,在這里bock又作為self的一個成員被持有,就會導致循環引用和內存泄露。
改動方案非常easy:
新建一個__block scope的局部變量,并把self賦值給它,而在block內部則使用這個局部變量來進行取值,上面說過:__block標記的變量是不會被自己主動retain的。
__block ViewController *controller = self; _block = ^(){ NSLog(@"string %@", controller.string); };4. block 基本使用
當block定義完畢后,我們除了能夠像使用一般函數的方式來直接調用它以外,還能夠有其它妙用。這些靈活的應用才是block最為強大的地方。
(1) block 作為函數參數
我們能夠像使用一般函數使用參數的方式將block以函數參數的型式傳入函數中,在這樣的情況下,大多數我們使用block的方式將不會傾向定義一個block。而是直接以內嵌的方式來將block傳入,這也是眼下新版SDK中主流的做法
以下的樣例中,block本身就是函數參數的一部分
char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };qsort_b (myCharacters, 3 , sizeof ( char *), ^( const void *l, const void *r) {//須要類型強轉下char *left = *( char **)l;char *right = *( char **)r;return strncmp (left, right, 1 ); } // 這里是block 的終點 ); // 最后的結果為:{"Charles Condomine", "George", "TomJohn"}(2) Block當作方法的參數
// 全部的資料 NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ]; // 我們僅僅要這個集合內的資料 NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ]; BOOL (^test)( id obj, NSUInteger idx, BOOL *stop); test = ^ ( id obj, NSUInteger idx, BOOL *stop) {// 僅僅對前5 筆資料做檢查if (idx < 5 ) {if ([filterSet containsObject : obj]) {return YES ;}}return NO ; };NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test]; NSLog ( @"indexes: %@" , indexes); // 結果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)] // 前5筆資料中,有4筆符合條件。它們的索引值各自是0-1, 3-4(3)OC方法中block實例
A. sortedArrayUsingComparator:
//這里面block代碼塊直接內嵌作為方法的參數 NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {//左邊大于右邊。降序if ([obj1 integerValue] > [obj2 integerValue]) {return (NSComparisonResult)NSOrderedDescending;}//右邊大于左邊,升序if ([obj1 integerValue] < [obj2 integerValue]) {return (NSComparisonResult)NSOrderedAscending;}//同樣return (NSComparisonResult)NSOrderedSame; }];B. enumerateObjectsUsingBlock
通常enumerateObjectsUsingBlock: 和 (for(… in …)在效率上基本一致,有時會快些。
主要是由于它們都是基于 NSFastEnumeration實現的。
高速迭代在處理的過程中須要多一次轉換。當然也會消耗掉一些時間. 基于Block的迭代能夠達到本機存儲一樣快的遍歷集合. 對于字典同樣適用。
注意”enumerateObjectsUsingBlock” 改動局部變量時。 你須要聲明局部變量為 __block 類型.
enumerateObjectsWithOptions:usingBlock: 支持并發迭代或反向迭代,并發迭代時效率也非常高.
對于字典而言, enumerateObjectsWithOptions:usingBlock 也是唯一的方式能夠并發實現恢復Key-Value值.
演示樣例代碼:
//定義一個可變數組 NSMutableArray *test = [NSMutableArray array];//向數組中加入元素 for (int i= 0; i < 10000; i++) {[test addObject:@"i"]; }//迭代數組輸出 [test enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {NSLog(@"%@",obj); }];5. block內存管理
(1)堆(Stack)和棧(Heap)
heap和stack是內存管理的兩個重要概念。
這里指的是內存的分配區域。
stack的空間由操作系統進?行分配。
在現代操作系統中,一個線程會分配?個stack. 當一個函數被調用,一個stack frame(棧幀)就會被壓到stack里。
里包括這個函數涉及的參數,局部變量,返回地址等相關信息。當函數返回后,這個棧幀就會被銷毀。
?這一切都是自己主動的,由系統幫我們進行分配與銷毀。對于程序猿是透明的,我們不須要手動調度。
.
heap的空間須要手動分配。 heap與動態內存分配相關,內存能夠隨時在堆中分配和銷毀。
我們須要明白請求內存分配與內存銷毀。 簡單來說,就
是malloc與free.
(2)Objective-C中的Stack和Heap
首先全部的Objective-C對象都是分配在heap的。 在OC經典的內存分配與初始化:
NSObject *obj = [[NSObject alloc] init];一個對象在alloc的時候,就在Heap分配了內存空間。 stack對象通常有速度的優勢,?且不會發生內存泄露問題。那么為什么OC的對象都是分配在heap的呢? 原因在于:
.
我們知道block 在使用@property定義時,官方建議我們使?用copy修飾符
// 定義一個塊代碼的屬性。block屬性須要用 copy @property (nonatomic, copy) void (^completion)(NSString *text);盡管在ARC時代已經不須要再顯式聲明了,使用strong是沒有問題的,可是仍然建 議我們使?copy以顯示相關拷貝?為。
(3)為什么要使用copy?!
事實上Objective-C是有它的Stack object的。那就是block。
在Objective-C語?言中,?一共同擁有3種類型的block:
- _NSConcreteGlobalBlock 全局的靜態block,不會訪問不論什么外部變量。
- _NSConcreteStackBlock 保存在棧中的block,當函數返回時會被銷毀。
- _NSConcreteMallocBlock 保存在堆中的block,當引?用計數為0時會被銷毀。
這?我們主要基于內存管理的角度對它們進行分類。
NSConcreteGlobalBlock,這樣的不捕捉外界變量的block是不須要內存管理的,這樣的block不存在于Heap或是Stack?是作為代碼片段存在,相似于C函數。NSConcreteStackBlock,須要涉及到外界變量的block在創建的時候是在stack上?分配空間的,也就是?旦所在函數返回,運行彈棧,則會被摧毀。這就導致內存管理的問題,假設我們希望保存這個block或者是返回它,假設沒有做進?步的copy處理,則必定會出現故障。舉個栗子,在手動管理引?計數時,假設在exampleD_getBlock方法返回block 時沒有運行[[block copy] autorelease]的操作,則方法運行完畢后,block就會被銷毀, 返回block是無效的。
//定義了一個block typedef void (^dBlock)();dBlock exampleD_getBlock() {char d = 'D';return ^{printf("%c\n", d);}; }void exampleD() {exampleD_getBlock(); } NSConcreteMallocBlock,因此為了解決block作為Stack object的這個問題,我們終于須要把它拷?到堆上來。復制到堆后,block的?命周期就與?般的OC對象?樣了,我們通過引用計數來對其進行內存管理。
如今我們知道為么么要Copy了吧-_-
block在創建時是stack對象,假設我們須要在離開當前函數仍能夠使用我們創建的block。我們就須要把它 拷?到堆上以便進行以引用計數為基礎的內存管理。
在ARC模式下,系統幫助我們完畢了copy的?作。
在ARC下,即使你聲明的修飾符是strong,實際上效果是與聲明為copy一樣的。
因此在ARC情況下,創建的block仍然是NSConcreteStackBlock類型,僅僅只是當block被引用或返回時,ARC幫助我們完畢了copy和內存管理的工作。
總結
在ARC下,我們能夠將block看做?一個正常的OC對象,與其它對象的內存管理沒什么不同。
MRC下要使用 Block_copy()和 Block_release 來管理內存。
(4)再來一個栗子
上面講到ARC下, block在被引用或返回時類型會由NSConcreteStackBlock轉換為 NSConcreteHeapBlock,那在MRC環境下該怎么辦呢。
block在創建的時候,它的內存是分配在棧(stack)上,而不是在堆(heap)上。
我們在viewDidLoad中創建一個_block:
- (void)viewDidLoad { [superviewDidLoad]; int number = 1; _block = ^(){ NSLog(@"number %d", number); }; }而且在一個button的事件中調用了這個block:
- (IBAction)testDidClick:(id)sender { _block(); }此時假設按了button之后就會導致程序崩潰。解決問題的方法非常easy
在創建完block的時候須要調用 Block_copy函數。它會把block從棧上移動到堆上。那么就能夠在其它地方使用這個block了。
Block_copy實際上是一個宏。例如以下:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))使用后,使用 Block_release。從堆中釋放掉
改動代碼例如以下:
_block = ^(){ NSLog(@"number %d", number); }; _block = Block_copy(_block);同理。特別須要注意的地方就是在把block放到集合類當中去的時候,假設直接把生成的block放入到集合類中,是無法在其它地方使用block。必須要對block進行copy。
示比例如以下:
[array addObject:[[^{ NSLog(@"hello!"); } copy] autorelease]];Q:為什么不使用簡單的copy方法 而是 Blockcopy呢?
由于blcok是復雜的匿名函數。簡單的copy在有些時候不能實現準確的copy,具體就要看各自的C源代碼了
6. 視圖控制器反向傳值
使用Block的地方非常多,當中傳值僅僅是當中的一小部分,以下介紹Block在兩個界面之間的傳值:
先說一下思想:
首先,創建兩個視圖控制器,在第一個視圖控制器中創建一個UILabel和一個UIButton,當中UILabel是為了顯示第二個視圖控制器傳過來的字符串,UIButton是為了push到第二個界面。
第二個界面的僅僅有一個UITextField,是為了輸入文字,當輸入文字,而且返回第一個界面的時候,當第二個視圖將要消失的時候,就將第二個界面上TextFiled中的文字傳給第一個界面,而且顯示在UILabel上。
事實上核心代碼就幾行代碼:
在第二個視圖控制器的.h文件里定義聲明Block屬性
typedef void (^ReturnTextBlock)(NSString *showText);@interface TextFieldViewController : UIViewController@property (nonatomic, copy) ReturnTextBlock returnTextBlock;- (void)returnText:(ReturnTextBlock)block;@end 第一行代碼是為要聲明的Block又一次定義了一個名字ReturnTextBlock這樣,以下在使用的時候就會非常方便。第三行是定義的一個Block屬性第四行是一個在第一個界面傳進來一個Block語句塊的函數,不用也能夠,只是加上會降低代碼的書寫量實現第二個視圖控制器的方法
- (void)returnText:(ReturnTextBlock)block {self.returnTextBlock = block; } - (void)viewWillDisappear:(BOOL)animated {if (self.returnTextBlock != nil) {self.returnTextBlock(self.inputTF.text);} }當中inputTF是視圖中的UITextField。
第一個方法就是定義的那個方法,把傳進來的Block語句塊保存到本類的實例變量returnTextBlock(.h中定義的屬性)中,然后尋找一個時機調用,而這個時機就是上面說到的,當視圖將要消失的時候。須要重寫:
- (void)viewWillDisappear:(BOOL)animated; 方法。在第一個視圖中獲得第二個視圖控制器。而且用第二個視圖控制器來調用定義的屬性
例如以下:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {// Get the new view controller using [segue destinationViewController].// Pass the selected object to the new view controller.TextFieldViewController *tfVC = segue.destinationViewController;[tfVC returnText:^(NSString *showText) {self.showLabel.text = showText;}]; }能夠看到代碼中的凝視,系統告訴我們能夠用
[segue destinationViewController]來獲得新的視圖控制器,也就是我們說的第二個視圖控制器。
這時候上面(第一步中)定義的那個方法起作用了,假設你寫一個[tfVC return Text按回車 ,系統會自己主動提示出來一個:
tfVC returnText:<#^(NSString *showText)block#>的東西。我們僅僅要在焦點上回車,就能夠高速創建一個代碼塊了,大家能夠試試。這在寫代碼的時候是非常方便的。
面試題:
當須要在block 中改動外部變量時使用,當須要訪問內部成員變量時。
2.在block里面, 對數組運行加入操作, 這個數組須要聲明成 __block嗎?
當然不須要,由于數組能夠理解為指針,在block中對數組進行加入操作。僅僅是改變了指針指向的值,而沒有改動外部數組地址。具體參見block訪問成員變量演示樣例3
3.在block里面, 對NSInteger進行改動, 這個NSInteger是否須要聲明成__blcok
必須須要,NSInteger -> typedef long NSInteger; 這貨披著OC的外衣,事實上就是一個基本類型。基本類型在沒有static 等的保護下,當然須要__block
悄悄告訴你哦,block在iOS的面試中是非常重要的,假設你能把上面解說的內容理解了。那么就仰天長嘯出門去了。參考
使用block開發遇到的問題
http://blog.csdn.net/hherima/article/details/3858610
這篇block 博客系列從C源代碼的角度具體分析了blcok原理
http://mobile.51cto.com/iphone-446829.htm
巧叔的談Objective-C Block的實現
版權聲明:本文博主原創文章,博客,未經同意不得轉載。
總結
以上是生活随笔為你收集整理的有趣 IOS 开展 - block 使用具体解释的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于SVG的viewBox
- 下一篇: 给 c# 程序员的十个重要提示