Block注意點:
- 區分聲明和定義
- 聲明沒有等號
- 定義有等號
何時用block,何時用delegate
- 異步和回調用block
- 比如AFNetwork,MJRefesh
- 接口很多,用delegate進行解耦。其實用block也行,不過這時候有點麻煩,因為接口很多嘛。
- 比如UITableViewDelegate
- 能用代理一定能用block,能用通知一定能用block,所以block完全可以替換代理和通知,即block是萬能的。block雖然簡潔,但不好理解,只要用的多了就好理解了。block其實就是個函數
block和delegate相同點
- 具體實現都是在代理類中、初始化Block的類中處理的。具體算法都是在算法類中實現的。
- 比如我想讓A控制器顯示block的代碼塊中的內容,那么我必須把初始化block的代碼塊寫在A控制器中。具體如何執行block中的代碼,由我自己決定,我把block()這句代碼C方法中,那么程序只要執行C方法,就會立刻執行block,A控制就會顯示block代碼塊中的內容
Block核心理解
- 1.block中的代碼何時執行?只有理解這個,才理解了block
- 只有執行了類似block();這段代碼,才會真正調用block,才會執行block中保存的代碼。
- 2.block中保存的代碼在哪里?
- 只要見到{代碼塊;};或者(參數){代碼塊;}; 這兩行種形式。保存的代碼塊就存在{}里面
- 1+2的結合你就完全理解了何時執行block,執行哪里的block。
block中的實參和形參
- 實參: 調用block方法時,方法中的參數就是實參,例如block(10,20)。
- 形參: 執行block中的代碼塊時,^后面括號中的內容就是形參,例如 ^(int a,int b){代碼塊}
- 和C語言的形參、實參差不多。
block的應用場景
- 動畫
- 多線程
- 網絡請求回調
- 集合遍歷
- 第三方框架多數都使用的是block
block是地址傳遞的理解
- 通過block();的形式調用block,本質是調用block中保存的代碼。類似c語言函數,例如 eat(),就會調用eat函數;
- block是指向結構體的指針,也就是說block存的是地址,block是一種特殊的數據類型。注意數據類型的理解。
- block是地址傳遞不是值傳遞,所以block中{}的內容,內容中的變量如果是 全局變量/用__block修飾/static修飾,滿足三種情況之一,那么外界修改變量的值,就會修改block中對應的變量,因為這三種情況是地址傳值。內容中的變量如果是用普通的局部變量修飾(例如在方法內容僅僅用int修飾),那么外界修改變量對block中的內容無影響,因為是值傳遞。
- 總結:地址傳遞可以修改以block中{}的內容,值傳遞不可以修改block中{}的內容
情況1:num是全局變量
void test1();
int main(int argc, const char * argv[])
{
test1();
return 0;
}
int num = 10;// 全局變量(在方法外部定義的num)
void test1()
{
void (^block)() = ^{// 定義并初始化block
// block內部能夠一直引用全局變量
NSLog(@"----num=%d", num); // 20
};
num = 20;
block();// 調用Block
}
情況2:用__block修飾age
void test2()
{
__block int age = 10;
void (^block)() = ^{// 定義并初始化block
// block內部能夠一直引用被__block修飾的變量
NSLog(@"----age=%d", age);// 20
};
age = 20;
block();// 調用Block
}
情況3:用static修飾的局部變量height
void test3()
{
static int height = 10;
void (^block)() = ^{ // 定義并初始化block
// block內部能夠一直引用被static修飾的變量
NSLog(@"----height=%d", age);// 20
};
height = 20;
block();// 調用Block
}
情況4:用int修飾普通的局部變量weight
void test2()
{
int weight = 10;
void (^block)() = ^{// 定義并初始化block
// 普通的局部變量,block內部只會引用它初始的值(block定義那一刻),不能跟蹤它的改變
NSLog(@"----weight=%d", weight);// 10
};
age = 20;
block();// 調用Block
}
block相關的內存管理( Block_copy(block變量名) 、 __block )
- 1.block是存儲在堆中還是棧中?
- 默認情況下block存儲在棧中,block中訪問了外界的對象p, 不會對對象進行retain操作
- 如果對block進行一個copy操作,即Block_copy(block變量名), block會轉移到堆中
- block存儲在堆中, block中訪問了外界的對象p, 那么會對外界的對象進行一次retain(計數器加1)
- 例:對block進行一個copy操作,block會轉移到堆中,此時,block中訪問了外界的對象p,會retain
Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定義并初始化block
NSLog(@"block retainCount = %lu", [p retainCount]);// 計數器會+1
};
Block_copy(myBlock);// 對block進行一次copy操作,那么block就會轉移到堆中
myBlock();// 調用block
[p release]; // 2-release一次= 1,所以會造成內存泄露
- 2.如果在block中訪問了外界的對象p, 一定要給對象加上__block, 只要加上了__block, 哪怕block在堆中, 也不會對外界的對象進行retain。例如:
__block Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定義并初始化block
NSLog(@"block retainCount = %lu", [p retainCount]);// p讓__block修飾了,所以計數器不會+1
};
Block_copy(myBlock);// 對block進行一次copy操作,那么block就會轉移到堆中
myBlock();// 調用block
[p release]; // 1-release一次= 0,所以不會造成內存泄露
oc代碼如何轉成c/c++的運行時代碼
步驟1:創建一個命令行項目
步驟2:將代碼寫在main.m文件中
步驟3:打開終端
步驟4:cd 到main.m的上一級目錄
步驟5:clang -rewrite-objc main.m
步驟6:open main.cpp
block聲明
// 聲明:無返回值(^block變量名)(形參)
void(^block)();
void:block中保存的代碼沒有返回值
(^block):block是一個變量,這個變量里保存著一段代碼
():block保存的代碼沒有形參
block定義的同時并初始化block
- 等號左邊是block的定義。等號右邊是初始化block,等號左邊+等號右邊一起出現的形式就是block定義的同事并初始化block。當然也可以分開寫,即先定義block,再初始化block
- 方式一:無返回值((^block變量名)(形參)= ^(形參)
// 定義并初始化block
void(^block1)() = ^(){ //不會執行代碼塊中的內容,只有執行block1才會調用block1保存的代碼塊
// 代碼塊
NSLog(@"調用block1");
};
// 調用Block1,這個Block1就是block變量名,然后去執行下Block1保存代碼,就是等號右側的代碼^(){代碼塊}
block1();
- 方式二:無返回值+block2的定義中沒有參數(形參),那么等號右側的()可以省略
//類型:void(^)()
// 定義并初始化block
void(^block2)() = ^{//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
// 代碼塊
};
// 調用Block2,這個Block2就是block變量名,然后去執行下Block2保存代碼,就是等號右側的代碼^{代碼塊}
block2();
- 方式三:有返回值+block3的定義中沒有參數(形參)
// 類型:int(^)()
// 定義并初始化block
int(^block3)() = ^{//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
// 代碼塊
return 1;
};
// 調用Block3,這個Block3就是block變量名,然后去執行下Block3保存代碼,就是等號右側的代碼^int{代碼塊}
block3();
- 方式四:有返回值+block4的定義中有兩個參數(形參)
// 類型:int(^)(int,double)
// 定義并初始化block
int(^block4)(int,double) = ^(int age ,double height){//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
// 代碼塊
return 2;
};
// 調用Block4,這個Block4就是block變量名,然后去執行下Block4保存代碼,就是等號右側的代碼^(int age ,double height){// 代碼塊 };
block4();
block作用:
- 作用:保存一段代碼
- 類型:int(^)()
Block和typedef具體用法
- Block方式一
// 定義block+初始化block
int main(int argc, const char * argv[]) {
int (^sumBlock)(int , int );// 定義block
sumBlock = ^(int value1, int value2){// 初始化block
return value1 + value2;// 代碼塊
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 調用block
int (^minusBlock)(int , int); // 定義block
minusBlock = ^(int value1, int value2){ // 初始化block
return value1 - value2;// 代碼塊
};
NSLog(@"minus = %i", minusBlock(20, 10));//10 調用block
return 0;
}
- Block方式二
// 定義block的同時并初始化block
類比 int a; // 定義a
a = 10;// 把a初始化為10
int a = 10;// 定義a的同時,并把a初始化為10
int main(int argc, const char * argv[]) {
int (^sumBlock)(int , int ) = ^(int value1, int value2){// 定義block的同時并初始化block
return value1 + value2;// 代碼塊
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 調用block
int (^minusBlock)(int , int) = ^(int value1, int value2){// 定義block的同時并初始化block
return value1 - value2;// 代碼塊
};
NSLog(@"minus = %i", minusBlock(20, 10));// 10 調用block
return 0;
}
- block+typedef的方式
- 注意: 利用typedef給block起別名,block變量的名稱就是別名,也就是類型的名稱,以后可以用calculteBlock這個類型去定義并初始化一個block
typedef int (^calculteBlock)(int , int);
int main(int argc, const char * argv[]) {
// 由于用了typedef,所以此句代碼的含義:用calculteBlock類型定義sumBlock,并初始化sumBlock
calculteBlock sumBlock = ^(int value1, int value2){
return value1 + value2;// 代碼塊
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 調用block
// 由于用了typedef,所以此句代碼的含義:用calculteBlock類型定義sumBlock,并初始化sumBlock
calculteBlock minusBlock = ^(int value1, int value2){
return value1 - value2;// 代碼塊
};
NSLog(@"minus = %i", minusBlock(20, 10));// 10 調用block
return 0;
}
block實戰演練
#import <Foundation/Foundation.h>
// 如果外界沒有調用goToWork方法,那么goToWork的參數僅僅是定義一個block
// 如果外界調用了goToWork方法,那么goToWork的參數的含義是:定義并初始化block。
// 因為外界調用goToWork方法,必定傳進來一個"初始化block"的代碼
// 我們只需在goToWork方法中調用block();就可以執行block中的代碼,從而輸出block中的內容
void goToWork(void (^workBlock)())
{
// 參數為void (^workBlock)(),那么外界將初始化的代碼塊作為形參賦值給這個參數時,本質就會變成如下代碼
/*
void (^workBlock)() = ^{// 定義并初始化block。 所以下面的workBlock();就會執行block中的內容
NSLog(@"寫代碼嘍");
});
*/
// 總結:定義block 作為goToWork方法的參數時,外界把代碼塊傳遞給這個參數時,那么就變成了定義并初始化block,
NSLog(@"起床");
NSLog(@"駕車去上班");
// 不一樣
workBlock(); // 執行block,就是執行block中的代碼
NSLog(@"駕車回家");
NSLog(@"吃晚飯");
NSLog(@"睡覺");
}
int main(int argc, const char * argv[]) {
goToWork(^{
NSLog(@"認識新同事");
});
return 0;
}
創建block的快捷方式:
// 快捷方式:inline
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
- 若parameterTypes有類型,那么parameters必須是參數類型+值的形式,否則提示錯誤
//例如:
void(^block4)(int) = ^(int a) {//正確
};
void(^block4)(int) = ^( a ) {//錯誤 a前面必須加上返回值類型
};
void(^block4)() = ^( ) {//正確 沒有參數類型,那么參數也不需要參數
};
循環引用的問題[詳細看JS_OC_JavaScriptCore這個demo]
-
對象未變為弱指針,控制臺沒有打印-[XXX dealloc],所以控制器沒有被釋放,造成了循環引用
對象未變為弱指針.gif -
對象變為了弱指針,控制臺打印-[XXX dealloc],所以控制器被釋放,解決了循環引用
對象變為了弱指針.gif