IOS開發 block(程式碼塊)基本使用
1. block基本概念:
(開篇廢話)
Block是C級別的語法和執行時特性。Block比較類似C函式,但是Block比之C函式,其靈活性體現在棧記憶體、堆記憶體的引用。
Block是蘋果推薦的型別,效率高,可以幫助我們組織獨立的程式碼段,並提高複用性和可讀性。主要是用來在執行中封裝程式碼和儲存程式碼用的。
Block可以在任何時候被執行。
和c語言的比較:
1、可以儲存程式碼。
2、有返回值。
3、有引數
4、呼叫方式一樣
(函式和函式是同級的關係,函式裡面不能定義行數,但是block可以定義在程式的任何地方,只要遵循一條原則:程式碼是從上到下執行的,先定義後使用)
最簡單地理解:block就是一個用來儲存程式碼的變數,可以在你需要的使用的時候通過block 來使用你儲存的程式碼,通常用來做併發任務、遍歷、以及回撥。
格式說明:
(返回型別)(^塊名稱)(引數型別列表) = ^(形參列表) {程式碼實現};
如果沒有引數,等號後面引數列表的()可以省略
2. block 在開發或者系統架構中的使用
從 xcode 4.0 開始,系統類庫中的函式越來越多的開始使用 block 作為引數,以下是在系統函式中使用程式碼塊的部分情況
a. 遍歷陣列和字典
b. 排序
c. 檢視動畫
d. 結束回撥
e. 錯誤處理
f. 多執行緒等
3. 必須瞭解的東西
廢話說完後來點重點: a. block(程式碼塊) 是 oc中的一種資料型別,可以被當做引數傳遞,可以有返回值,是一個能工作的程式碼單元,可以在任何需要的時候被執行,就像呼叫函式一樣呼叫。 在 ios 開發中廣泛使用。 b. ^ 是 block 的特有的標記。 c. block 熟練了解block 的定義(塊程式碼的定義),記得實現程式碼包含在 {} 之間。 d. block 是以內聯 inline 函式的方式被定義使用。 e. 本質上是輕量級的匿名函式。 c. 塊程式碼的使用注意點 i. 預設情況下,不允許在塊程式碼內部修改外部的變數的數值 ii. __block,讓外部的變數能夠在block中修改。 iii. 迴圈引用的問題 __weak (ios5.0以下的版本使用__unsafe_unretained(廢話)) iv. 預設情況下,block 外部的變數,在 block 中是隻讀的。 v. 塊程式碼與代理的區別
4、block的定義
1、block定義和指向函式的指標的對比
定義除了一個符號發生了改變基本是一樣的:
指向函式的指標和block定義的程式碼對比:
#import <Foundation/Foundation.h>
// 測試函式
void test(){
NSLog(@"%s",__func__);
}
/*
對於指標不理解的:函式名就是指向函式的指標
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 這個是定義了一個函式的指標並賦值
void (*Mytest)() = test;
// 呼叫函式
Mytest();
// 定義一個沒有返回值的block
void(^Myblock)() = ^{
NSLog(@"這個是Myblock");
};
// 呼叫block
Myblock();
/*
void (*Mytest)();
void(^Myblock)();
1、指向函式的指標和block的定義的對比,基本只有一個符號的區別。
2、使用函式的指標和block的使用基本是一樣的。需要呼叫才會執行。
指向函式的指標是通過函式名的指向的地址呼叫函式。
block是直接執行一段儲存的程式碼。
*/
}
return 0;
}
如果要糾結啥子有返回值沒有返回值有引數沒有引數的block和指向函式的指標的對比那就自己去玩去。
列印結果:
2015-04-22 23:03:18.965 block[4274:1360943] test
2015-04-22 23:03:18.966 block[4274:1360943] 這個是Myblock
2、block定義—— 沒有引數沒有返回值的block的定義
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定義block ————> 沒有引數沒有返回值的
/*
定義的時候,將block當成資料型別
特點:
1、型別比函式多了一個 ^
2、設定數值,有一個^ ,內容是{}括起來的一段程式碼
最簡單的方式:不帶返回值不帶引數。
void (^Myblock)() = ^ {
// 要儲存的程式碼實現;
};
*/
// 定義一個block 並儲存一段程式碼
void (^Myblock)() = ^{
NSLog(@"Myblock"); // 這個是儲存的程式碼
}; // 這個; 號是不能少的
// 呼叫block
Myblock(); // 像呼叫函式一樣呼叫block
}
return 0;
}
列印結果:
2015-04-22 23:22:04.593 block[4306:1412796] Myblock
3、block定義—— 有引數沒有返回值的block的定義
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定義block ————> 有引數沒有返回值的
void (^Myblock)(int) = ^(int num){ // 當有引數的時候 ^(int num){ 中間的()是不能少的
NSLog(@"Myblock,傳入的引數是:%d",num );
};
// 呼叫block
Myblock(10);
}
return 0;
}
列印的結果:
2015-04-22 23:28:48.488 block[4317:1439967] Myblock,傳入的引數是:10
4、block定義—— 有引數有返回值的block的定義
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定義block ————> 有引數有返回值的
int (^Myblock)(int) = ^(int num){
NSLog(@"Myblock,傳入的引數是:%d",num );
return num; //當定義的block 是有返回值的時候一定要返回要不就會報錯
};
// 呼叫block
Myblock(10); // block有返回值並不一定要接收
NSLog(@"%d", Myblock(20));
}
return 0;
}
列印的結果:
2015-04-22 23:34:34.298 block[4354:1462221] Myblock,傳入的引數是:10
2015-04-22 23:34:34.301 block[4354:1462221] Myblock,傳入的引數是:20
2015-04-22 23:34:34.301 block[4354:1462221] 20
5、block定義—— 有引數有返回值的block的定義可能看見的還有一種寫法
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 定義block ————> 有引數有返回值的
int (^Myblock)(int,int) = ^int(int num1 ,int num2){
NSLog(@"Myblock,傳入的引數num1是:%d num2是%d",num1,num2);
return num1 + num2; //當定義的block 是有返回值的時候一定要返回要不就會報錯
};
// 呼叫block
Myblock(10,20); // block有返回值並不一定要接收
NSLog(@"%d", Myblock(20,30));
}
return 0;
}
^int( 這個中間的int時返回值型別可以省略不寫
列印的結果:
2015-04-22 23:43:48.958 block[4378:1501255] Myblock,傳入的引數num1是:10 num2是20
2015-04-22 23:43:48.959 block[4378:1501255] Myblock,傳入的引數num1是:20 num2是30
2015-04-22 23:43:48.959 block[4378:1501255] 50
其他的什麼資料型別引數和返回值的排列組合就不一一列之舉,想玩的自己去試試。
6、block的祕籍——block的書寫是我們最蛋疼的事,一招解決所有
使用inlineblock這個速記符號可以快速的敲出block的基本結構
注意:block的書寫一定要熟記。
inlineblock這個速記符號只能用來輔助記憶。
(如果你不知道填空大話,我也是醉了)
5、關於block幾個疑惑和容易出錯的問題
1、block在記憶體中的位置
棧裡面的東西是不需要我們程式設計師管理的,記憶體中唯一一個需要程式設計師管理的是堆,我們在寫程式碼的時候new出來的東西都在堆中。
block預設情況下是在棧中的,是不需要我們程式設計師管理的。如果多block進行了一次copy操作,就會將block轉移到堆中。
注意點:
如果block 在棧裡,那麼block中用到了外界的物件,不用我麼管理。
但是如果block在堆中,那麼block中如果用到外界物件,會對物件進行一次retain操作,也就是會進行強引用。
如果想讓堆中的block不對使用到的外界物件進行retain,那麼就只需要在外界物件的前面加__block.
2、關於block引用外部變數操作的問題
block 使用,如果引用了外部變數,就會對外部變數做一個copy的操作。記錄住定義block時候的值。如果後續再修改外部變數的值,不會影響block內部的數值的變化!
外部變數本來是在棧區中,block引用的那一刻,就將外部變數copy 到堆中了,block裡面使用的時copy 後堆中的變數的值。
所有在block 呼叫之前修改外部變數的值,不會影響block裡面值的原因。
如果要驗證:可以通過列印地址值的方式來驗證,棧區是高位地址值,相對於棧,堆在低位地址。
通過列印地址發現,block裡面的變數的地址值比block外面的地址值要小很多。
示例程式:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int x = 10;
void (^Myblock)() = ^{
NSLog(@"%d",x); // 引用外部變數 (已經copy,記錄了外部變數的值)
};
x = 20; // 修改外部變數的值
Myblock();
}
return 0;
}
列印結果:
2015-04-23 00:37:15.045 block[4470:1683462] 10
3、關於在block內部修改外部變數的值的問題
在預設的情況下,是不允許在block內部修改外部變數的值。
原因是:會破壞程式碼的可讀性,不易於維護。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int x = 10;
void (^Myblock)() = ^{
x = 80; // 這樣是不能修改的,這樣寫直接報錯
};
Myblock();
}
return 0;
}
如果我們一定要再block的內部修改外部變數的值,必須在外部變數的前面新增 _ _block ,這樣才會允許修改。
使用__block,說明不在關係外部變數數值的具體變化。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int x = 10; // 必須在前面加 __block 才可以在block 中修改外部變數的值
void (^Myblock)() = ^{
x = 80;
};
Myblock();
}
return 0;
}
為什麼使用__block 會達到這個效果(可以通過跟蹤地址值發現問題)?
在定義block時,如果引用了外部變數使用了__block的變數。block定義之後,外部變數同樣會被copy到堆中,不同的是棧中的那一份沒有了,只保留了堆中的那一份。在block 中修改的那一份和 保留的那一份是同一份。所以可以修改。
3、關於在block內部修改外部變數的值 —— 一個蛋疼的問題
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 指標記錄的是地址
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
NSLog(@"定義前 %p %p", strM, &strM);
void (^myBlock)() = ^ {
// 修改strM指標指向的內容
[strM setString:@"lisi"];
NSLog(@"inblock %p %p", strM, &strM);
// 這句程式碼是修改strM指標指向的地址
// strM = [NSMutableString stringWithString:@"wangwu"];
};
NSLog(@"定義後 %p %p", strM, &strM);
myBlock();
NSLog(@"%@", strM);
}
return 0;
}
列印的結果:
2015-04-23 01:30:30.900 block[4566:1798733] 定義前 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.901 block[4566:1798733] 定義後 0x100406b30 0x7fff5fbff7d8
2015-04-23 01:30:30.902 block[4566:1798733] inblock 0x100406b30 0x1004074f0
2015-04-23 01:30:30.902 block[4566:1798733] lisi
block copy的知識指標沒有copy變數的地址。在block修改的是變數。所以結果會變。
4、關於在block在MRC 中的使用注意(重要的面試題(在ARC開發的時代基本沒有主要是為了測試功底))
(小白略過)
#import <Foundation/Foundation.h>
// 塊程式碼可以當作引數,也可以當作返回值
typedef void(^eBlock)();
/**
問
-以下程式碼在ARC中有問題嗎?=》沒有問題
-在MRC中有問題嗎?存在記憶體隱患,i和b都是區域性變數,出了作用域就會被釋放
解決問題:
-返回前使用 Block_copy
-使用後,使用 Block_release
網上錯誤答案 return [b copy];
*********
Product - Analyze (靜態分析)
從程式碼結構上分析是否存在缺陷!本身並不會執行程式!並不能夠檢測到真正的記憶體洩漏!
但是:只要是靜態分析工具發現的問題,通常都是需要提升的程式碼!
靜態分析工具,是MRC開發時的利器!提前發現記憶體隱患!
另外,在ARC開發時,如果程式要上架之前,建議使用靜態分析工具檢測一下,通常可以發現一些不注意的警告,有助於提升程式碼質量!尤其在使用到C語言框架的程式碼!
*/
eBlock myBlock() {
int i = 10;
eBlock b = ^ {
NSLog(@"hello %d", i);
};
// 利用Block_copy將block以及內部的變數拷貝到堆中
return Block_copy(b);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
eBlock bb = myBlock();
bb();
// 釋放堆中block
Block_release(bb);
}
return 0;
}
6、block迴圈引用問題的解決
迴圈引用的結果就是導致物件無法釋放。
我們測試的最好的辦法是在物件中重寫dellac方法,看這個方法是否被呼叫。沒有呼叫說明存在迴圈引用。
在我們的IOS開發當中,什麼時候會出現迴圈引用:
在我們使用block的時候,如果block中使用到了self ,這個時候就需要關心迴圈引用的問題。
解決方案:__weak typeof(self) weakSelf = self;
// 示例程式碼SDWebImage 框架使用的使用用的程式碼:
__weak typeof(self) weakSelf = self;
SDWebImageManager *manage = [SDWebImageManager sharedManager];
[manage downloadImageWithURL:url options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (finished) {
// 這裡是用 weakSelf 代替self 避免迴圈引用
[weakSelf setupImageFrame: image];
}
}];
注意點:並不是所有的block中使用self都會有迴圈引用的問題。為了避免迴圈引用的問題,遇到block中用到self 。我們都這麼寫,就可以避免迴圈引用的問題。
7、代理和block在使用的時候我們是怎麼選擇的。
委託和block是IOS上實現回撥的兩種機制。Block基本可以代替委託的功能,而且實現起來比較簡潔,比較推薦能用block的地方不要用委託。
單就程式設計過程而言,block對開發者處理邏輯,程式設計效率,程式碼閱讀都有積極影響。
代理是一種很金典的模式,我們很多人都已經習慣了這種模式,若果對block的回撥傳值的過程不是很理解的話,建議使用代理。可以達到同樣地效果。
一下是還未完善的區域,我會持續的更新的
8、 block 在 IOS 開發的實際運用
在實際的開發中block的使用基本就是傳值和回撥。這個也是難點和重點。