ios 添加block 类别_iOS 关于Block代码块的详解
概述
block
上圖就是一個block簡單使用,它包括了block的聲明、賦值實現、調用 三個部分,其中,實現部分可以看作是一種匿名函數;跟函數一樣,block也是需要調用才能執(zhí)行內部代碼的;賦值的行為又讓block看起來跟數據類型類似
代碼塊Block是在iOS4開始引入的,是對C語言的擴展,用來實現匿名函數的特性
Block是一種特殊的數據類型,可以像基本數據類型一樣定義成變量、作為參數、返回值來使用
Block還可以保存一段代碼,在需要的時候調用
在iOS開發(fā)中,Block被系統應在很多地方,例如:GCD、UIView動畫、排序等,我們開發(fā)者也可以應用在各類回調、傳值、傳消息等
Block的聲明、賦值實現、調用
Block的聲明樣式。
返回類型 (^Block名稱)(參數列表)
void(^myBlock)(NSString *, NSString *)
Block的返回類型分為有返回類型和無返回類型(void),參數列表也可有也可以沒有,具體看需求
// 無返回類型無參數列表
void(^block)();
// 無返回類型有參數列表
void(^block)(int);
// 有返回類型無參數列表
int(^block)();
// 有返回列表有參數列表
int(^block)(int);
Block的簡單使用:聲明、賦值、調用
Block變量的賦值格式為:
Block變量 = ^(參數列表){函數體}; // 這里的參數列表一定要和聲明時的參數列表一致
// 聲明
void(^block)();
// 賦值
block = ^(){
NSLog(@"Hello World");
};
// 調用
block();
也可以在聲明時完成賦值
// 聲明、賦值
void(^block)() = ^(){
NSLog(@"Hello World");
};
// 調用
block();
定義Block類型
前面提到過,Block是一種特殊的數據類型,我們可以使用 typedef 來定義 Block 類型,這樣我們就可以使用該類型來聲明很多相同的Block變量了。
typedef 返回類型(^Block名稱)(參數列表);
示例:
// 聲明一個Block類型
typedef void(^Block)();
// 使用定義兩個block變量
Block myBlock,myNewBlock;
// 賦值實現
myBlock = ^(){
NSLog(@"Hello World");
};
myNewBlock = ^(){
NSLog(@"Hello World, I am lolita0164.");
};
// 調用
myBlock();
myNewBlock();
ARC模式下簡單應用
作為對象屬性實現消息傳遞
前面說到,Block保存一段代碼,在需要的時候調用。我們可以將使用Block的三個步驟拆開,實現消息傳遞、傳值功能。
// 定義Block類型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 聲明Block變量
@property (nonatomic, copy) Block myBlock;
-(void)sayHello;
@end
@implementation Person
-(void)sayHello{
// Block調用
self.myBlock(@"Hello, I am lolita0164");
}
@end
在定義聲明、調用之后,還缺少實現的部分,這一步通常由外部實現。
Person *p = [Person new];
// Block賦值實現
p.myBlock = ^(NSString *string) {
NSLog(@"%@",string);
};
[p sayHello];
這樣,我們就可以在Block的賦值實現部分里拿到 p類里的數據了。
作為函數參數實現數據回調
我們將之前的例子稍加改動
// 定義Block類型
typedef void(^Block)(NSString *);
@interface Person : NSObject
// 將Block作為參數
-(void)sayHelloUseBlock:(Block)myBlock;
@end
@implementation Person
-(void)sayHelloUseBlock:(Block)myBlock{
// Block調用
myBlock(@"Hello, I am lolita0164");
}
@end
在外部進行實現。
Person *p = [Person new];
// Block實現
[p sayHelloUseBlock:^(NSString *string) {
NSLog(@"%@",string);
}];
作為參數和作為屬性傳遞消息,在應用場景稍稍有些不同。
作為參數時,通常和當前的方法有著緊密的聯系,函數體內部需要與調用的外部進行交互。例如在請求方法中,經常會使用到block進行回調,而這個block和當前的方法關系緊密,通常是該方法的結果回調。又或者是方法執(zhí)行期間需要外部提供一定的信息,從而通過block獲取外部提供的數據。
作為屬性時,通常是和當前類相關,作為類與類之間的交互代表。
作為返回值實現鏈式語法
將block作為返回值的經典例子就是約束庫 masonry,這個庫在做完每次約束設置之后通過 block 將實例再次回調,就形成了鏈式語法。
[view makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(view1.mas_right).offset(10);
make.top.equalTo(view1).offset(0);
make.right.equalTo(-10);
make.size.equalTo(viewWidth);
}];
下面通過創(chuàng)建顏色類來演示 block 作為參數的使用。
// block 作為返回值
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
// block 的聲明和實現
UIColor* (^rgbBlock)(CGFloat, CGFloat, CGFloat) = ^id(CGFloat r, CGFloat g, CGFloat b) {
return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
};
return rgbBlock;
}
解析
返回值是一個有返回值參數有三個的block:UIColor * (^)(CGFloat, CGFloat, CGFloat)。我們在該方法的內部進行了block的聲明和具體實現,并且將其作為返回值返回了出去,那么外部在調用該方法之后接收到的是一個block,可以使用該值。
那么外部使用情況如下。
// 接收 block 類型
UIColor* (^colorBlock)(CGFloat, CGFloat, CGFloat) = [UIColor rgb];
// 使用 block 獲取到顏色
UIColor* color = colorBlock(10,33,65);
self.view.backgroundColor = color;
在丟棄不需要的部分后,代碼如下。
+(UIColor* (^)(CGFloat, CGFloat, CGFloat))rgb{
return ^id(CGFloat r, CGFloat g, CGFloat b) {
return [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1];
};
}
// 使用block作為參數的方法
self.view.backgroundColor = UIColor.rgb(10, 33, 65);
Block 和 變量
Block訪問局部變量問題
block 內部可以訪問局部變量。
int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
Block(); // 輸出 "global = 100"
但是 block 會把變量 復制 為自己私有的const變量,也就是說block會捕獲棧上的變量(或指針),將其復制為自己私有的const變量,當變量被修改時,不會影響到block自己私有的const變量。
int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
global = 101;
Block(); // 輸出 "global = 100"
在Block中不可以直接修改局部變量。
int global = 100;
void (^Block)() = ^(){
global ++; // 這句報錯
NSLog(@"global = %i", global);
};
Block();
但是可以通過 __block 修飾符修改局部變量。
__block int global = 100;
void (^Block)() = ^(){
NSLog(@"global = %i", global);
};
global = 101;
Block(); //輸出 "global = 101"
__block int global = 100;
void (^Block)() = ^(){
global ++; // 這句正確
NSLog(@"global = %i", global);
};
Block(); //輸出 "global = 101"
原因:在局部變量前使用 __block修飾 ,在Block定義時便是將局部變量的指針傳給Block變量所指向的結構體,因此在調用Block之前對局部變量進行修改會影響B(tài)lock內部的值,同時內部的值也是可以修改的。
Block訪問全局變量、靜態(tài)變量問題
可以訪問和修改。
全局變量所占用的內存只有一份,供所有函數共同調用,在Block定義時并未將全局變量的值或者指針傳給Block變量所指向的結構體,因此在調用Block之前對全局變量進行修改會影響B(tài)lock內部的值,同時內部的值也是可以修改的。
在Block定義時便是將靜態(tài)變量的指針傳給Block變量所指向的結構體,因此在調用Block之前對靜態(tài)變量進行修改會影響B(tài)lock內部的值,同時內部的值也是可以修改的。
ARC下的內存管理
在ARC默認情況下,Block的內存存儲在堆中,ARC會自動進行內存管理,我們只需要避免循環(huán)引用即可。
// 當Block變量出了作用域,Block的內存會被自動釋放
void(^myBlock)() = ^{
NSLog(@"------");
};
myBlock();
如果在Block中引用了外面的對象,會對所引用的對象進行強引用,但是在Block被釋放時會自動去掉對該對象的強引用,因此比并不會造成內存泄漏問題。
Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock();
// Person對象在這里可以正常被釋放
// 注:這里的Block只是單方面的強引用,所以不會產生循環(huán)引用,也不會內存泄漏
如果對象內部引用一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環(huán)引用,導致內存泄漏。
self.block = ^{
NSLog(@"------%@", self);
};
解決辦法:使用一個弱引用的指針指向該對象,然后在Block內部使用該弱引用指針來進行操作,這樣就避免了Block對對象進行強引用。
__weak typeof(self) weakSelf = self;
weakSelf.block = ^{
NSLog(@"------%@", weakSelf);
};
提示:如果只是Block單方面地對外部變量進行強引用,并不會造成內存泄漏。
補充
1、聲明block屬性的時候為什么用copy呢?
在說明為什么要用copy前,先思考下block是存儲在棧區(qū)還是堆區(qū)呢?其實block有3種類型:
全局塊(_NSConcreteGlobalBlock)
棧塊(_NSConcreteStackBlock)
堆塊(_NSConcreteMallocBlock)
全局塊存儲在靜態(tài)區(qū)(也叫全局區(qū)),相當于OC中的單例;棧塊存儲在棧區(qū),超出作用域則馬上被銷毀。堆塊存儲在堆區(qū)中,是一個帶引用計數的對象,需要自行管理其內存。
關于內存分配,請看這篇:C語言內存分配。
怎么判斷一個block所在的存儲位置呢?
block不訪問外界變量(包括棧中和堆中的變量)
block既不在棧中也不在堆中,此時就為全局塊,ARC和MRC下都是如此。
block訪問外面變量
MRC環(huán)境下:默認存儲在棧區(qū)
ARC環(huán)境下:默認存儲在堆中,實際上是先放在棧區(qū),在ARC情況下自動又拷貝到堆區(qū),自動釋放
因此,使用 copy 修飾符的作用就是將block從棧區(qū)拷貝到堆區(qū)
為什么要這么做呢?官方給出的答案是:
復制到堆區(qū)的主要目的就是 保存 block 的狀態(tài),延長其聲明周期。因為block如果在棧上的話,其所屬的變量作用域結束,該block就被釋放掉了,block中的 __block 變量也同時被釋放掉了,為了解決超出作用域就被釋放的問題,我們就需要把block復制到堆中。
總結
OC 中的 block 是對 C 語言的匿名函數的一種特性是實現。block 具有函數特性,同時也可以作為變量使用。block 可以作為屬性、參數、返回值使用。想要在 Block 內部修改外部變量時,需要使用 __Block 將變量指針傳遞給 block。在使用 Block 時需要特別注意內存泄漏的問題。
總結
以上是生活随笔為你收集整理的ios 添加block 类别_iOS 关于Block代码块的详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么果汁对人好呢?
- 下一篇: 哈尔滨红肠1913和1903有什么区别?