ios 添加block 类别_ios之Block的详细使用和具体说明
image.png
iOS代碼塊Block
一:概述
閉包 = 一個函數「或指向函數的指針」+ 該函數執行的外部的上下文變量「也就是自由變量」;Block 是 Objective-C 對于閉包的實現。
代碼塊Block是蘋果在iOS4開始引入的對C語言的擴展,用來實現匿名函數的特性,Block是一種特殊的數據類型,其可以正常定義變量、作為參數、作為返回值,特殊的Block還可以保存一段代碼,在需要的時候調用,目前Block已經廣泛應用于iOS開發中,常用于GCD、動畫、排序及各類回調。
其中,Block:
1:可以嵌套定義,定義 Block 方法和定義函數方法相似。
2:Block 可以定義在方法內部或外部。
3:只有調用 Block 時候,才會執行其{}體內的代碼。
4:本質是對象,使代碼高聚合。
使用 clang 將 OC 代碼轉換為 C++ 文件查看 block 的方法:
1:在命令行輸入代碼 clang -rewrite-objc 需要編譯的OC文件.m
2:這時查看當前的文件夾里 多了一個相同的名稱的 .cpp 文件,在命令行輸入
open main.cpp 查看文件
二:Block定義
1.無參數無返回值
//無參無返回值
void (^myBlock) (void) = ^{
NSLog(@"無參無返回Block");
};
myBlock(); // block的調用
2.有參數無返回值
void (^myBlock3) (int a) = ^(int a){
NSLog(@"a == %d, 我就是有參數無返回值的block", a);
};
myBlock3(100); // //調用block
//控制臺打印結果:a == 100, 我就是有參數無返回值的block
3.有參有返回值
//有參有返回值的block
int (^myBlock4)(int , int) = ^(int a, int b){
NSLog(@"-- %d, 我就是有參數有返回值的block", a +b);
return (a +b);
};
myBlock4(30 , 20); // //調用block
// 控制臺打印結果:50, 我就是有參數無返回值的block
4.無參數有返回值的block(實際中很少用到)
//無參有返回值的block
int (^myBlock5)(void) = ^{
NSLog(@"我是一個無參數, 有返回值的block");
return 100;
};
myBlock5();
5.實際開發中常用typedef 定義Block。
例如:
image.png
//typedef 定義有參數有返回值的block
typedef int (^MyBlock3)(int c, int d);
這時,MyBlock3就成為了一種Block類型。
在定義類的屬性時可以這樣:
//block 修飾 作為屬性
@property (nonatomic, copy) MyBlock3 threeBlock;
使用該block時:
//使用block
self.threeBlock = ^int(int c, int d) {
NSLog(@"block的返回值 == %d",c*d);
return c*d;
};
self.threeBlock(10, 20);
三:Block與變量
1.block 中可以訪問局部變量
int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"a的值為:%d", a);
};
myBlock();
//控制臺輸出結果:a的值為10
2.在聲明Block之后、調用Block之前對局部變量進行修改,在調用Block時局部變量值是修改之前的舊值
int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"a的值為:%d", a);
};
a = 20;
// 調用后控制臺輸出"a的值為:10"
myBlock();
3.在Block中不可以直接修改局部變量
int a = 10;
void (^myBlock)(void) = ^{
// a++; //這一句報錯
NSLog(@"a的值為:%d", a);
};
myBlock();
4.Block內訪問__block修飾的局部變量
在局部變量前使用__block修飾,在聲明Block之后、調用Block之前對局部變量進行修改,在調用Block時局部變量值是修改之后的新值
__block int a = 10;
void(^myBlock)(void) = ^{
NSLog(@"a的值==%d",a);
};
a = 11;
// 調用后控制臺輸出"a的值== 11"
myBlock();
5.在局部變量前使用下劃線__block修飾,在Block中可以直接修改局部變量
//__block 修飾的變量可以在block里面進行改變,從棧copy到堆
// 會發現一個局部變量加上block修飾符后竟然跟block一樣變成了一個Block_byref_val_0結構體類型的自動變量實例!!!!
__block int a = 10;
void(^myBlock)(void) = ^{
a++;
NSLog(@"a的值==%d",a);
};
// 調用后控制臺輸出"a的值==11"
myBlock();
6.在Block中可以訪問全局變量
//定義全局變量
@interface ViewController (){
int aa;
}
定義block并訪問全局變量
void(^myBlock)(void) = ^{
NSLog(@"aa的值 == %d", self->aa);
};
// 調用后控制臺輸出"aa的值 = 100"
myBlock();
7.在聲明Block之后、調用Block之前對全局變量進行修改,在調用Block時全局變量值是修改之后的新值
void(^myBlock)(void) = ^{
NSLog(@"aa的值 == %d", self->aa);
};
aa = 200;
// 調用后控制臺輸出"aa的值 == 100"
myBlock();
8.在Block中可以直接修改全局變量
void(^myBlock)(void) = ^{
self->aa++;
NSLog(@"aa的值 == %d", self->aa);
};
// 調用后控制臺輸出"aa的值 == 101"
myBlock();
9.在聲明Block之后、調用Block之前對靜態變量進行修改,在調用Block時靜態變量值是修改之后的新值
static int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"a的值==%d",a);
};
a = 20;
myBlock();
//控制臺輸出結果:a的值==20
10.在Block中可以直接修改靜態變量
static int a = 10;
void (^myBlock)(void) = ^{
a++;
NSLog(@"a的值==%d",a);
};
myBlock();
//控制臺輸出結果:a的值==11
四:Block的使用示例
1:Block作為變量(Xcode快捷鍵:inlineBlock)
int(^sumBlock)(int, int);
sumBlock = ^(int s, int d){
return s + d;
};
int f = sumBlock(10, 20);
NSLog(@"f的值為=%d", f);
//如下代碼等同于上
int(^sum)(int, int) = ^(int d, int j) {
int dd = d + j;
NSLog(@"dd的值為: %d", dd);
return dd;
};
sum(30, 50);
2.Block作為屬性(Xcode 快捷鍵:typedefBlock)
//typedef 定義有參數有返回值的block
typedef int (^MyBlock3)(int c, int d);
//block 修飾 作為屬性
@property (nonatomic, copy) MyBlock3 threeBlock;
@property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef
//使用block
self.threeBlock = ^int(int c, int d) {
NSLog(@"block的返回值 == %d",c*d);
return c*d;
};
self.threeBlock(10, 20);
//以下是不使用typedef 定義的block 屬性
self.sum = ^int(int b, int c) {
return (b + c);
};
self.sum(3, 49);
3.Block作為 OC 中的方法參數
// ---- 無參數傳遞的 Block ---------------------------//
- (CGFloat)testTimeConsume:(void(^)(void))middleBlock{
//執行前記錄當前時間
CFTimeInterval startTime = CACurrentMediaTime();
middleBlock();
//執行后記錄當前時間
CFTimeInterval endTime = CACurrentMediaTime();
NSLog(@"時間差為:%f", endTime - startTime);
return endTime - startTime;
}
調用方法
//作為方法調用block
[self testTimeConsume:^{
//具體實現
}];
// ---- 有參數傳遞的 Block ---------------------------//
- (NSString *)testBlockWith:(void(^)(NSString *name))testBlock{
NSString *name = @"我是block傳遞的參數";
testBlock(name);
return name;
}
調用方法
[self testBlockWith:^(NSString *name) {
// 放入 block 中的代碼,可以使用參數 name
// 參數 name 是實現代碼中傳入的,在調用時只能使用,不能修改
NSLog(@"block傳遞過來的參數為:%@", name);
}];
4.Block回調使用示例
Block回調是關于Block最常用的內容,比如網絡下載,我們可以用Block實現下載成功與失敗的反饋。開發者在block沒發布前,實現回調基本都是通過代理的方式進行的,比如負責網絡請求的原生類NSURLConnection類,通過多個協議方法實現請求中的事件處理。而在最新的環境下,使用的NSURLSession已經采用block的方式處理任務請求了。各種第三方網絡請求框架也都在使用block進行回調處理。這種轉變很大一部分原因在于block使用簡單,邏輯清晰,靈活等原因。
下面用代碼實現一個下載圖片的回調,示例如下
#import
NS_ASSUME_NONNULL_BEGIN
//定義block
typedef void(^DownloadManagerBlock) (NSData *receiveData, NSError *error);
@interface DownloadManager : NSObject
//@property (nonatomic, copy) DownloadManagerBlock downLoadBlock;
//定義方法
- (void)downloadWithUrl:(NSString *)Url parameters:(NSDictionary *)parameters hander:(DownloadManagerBlock )hander;
@end
.m文件如下:
#import "DownloadManager.h"
@interface DownloadManager ()
@end
@implementation DownloadManager
//下面通過封裝NSURLSession的請求,傳入一個處理請求結果的block對象,就會自動將請求任務放到工作線程中執行實現,我們在網絡請求邏輯的代碼中調用如下:
- (void)downloadWithUrl:(NSString *)Url parameters:(NSDictionary *)parameters hander:(DownloadManagerBlock )hander{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:Url]];
NSURLSession *session = [NSURLSession sharedSession];
//執行請求任務
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (hander) {
dispatch_async(dispatch_get_main_queue(), ^{
hander(data, error);
});
}
}];
[task resume];
}
- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
}
@end
上面通過封裝NSURLSession的請求,傳入一個處理請求結果的block對象,就會自動將請求任務放到工作線程中執行實現,我們在網絡請求邏輯的代碼中調用如下:
//點擊下載圖片
- (void)downloadImageAction:(id)sender{
__weak typeof (self) weakSelf = self;
NSString *urlStr = @"https://img95.699pic.com/photo/40011/0709.jpg_wh860.jpg";
DownloadManager *downloadManager = [[DownloadManager alloc] init];
[downloadManager downloadWithUrl:urlStr parameters:@{} hander:^(NSData * _Nonnull receiveData, NSError * _Nonnull error) {
if (error) {
NSLog(@"下載失敗:%@", error);
weakSelf.downloadImageBtn.enabled = YES;
}else{
NSLog(@"下載成功:%@",receiveData);
weakSelf.myImgView.image = [UIImage imageWithData:[NSData dataWithData:receiveData]];
weakSelf.downloadImageBtn.enabled = NO;
}
}];
}
5.Block反向傳值
再舉一示例,A,B兩個界面,A界面中有一個label,一個buttonA。點擊buttonA進入B界面,B界面中有一個UITextfield和一個buttonB,點擊buttonB退出B界面并將B界面中UITextfield的值傳到A界面中的label。
A界面中,也就是ViewController類中:
//關鍵代碼
- (void)button1Action:(id)sender{
//block反向傳值實例
ThirdViewController *thirdVc = [[ThirdViewController alloc] init];
[self.navigationController pushViewController:thirdVc animated:YES];
__weak typeof (self) weakSelf = self;
//用屬性定義的注意:這里屬性是不會自動補全的,方法就會自動補全
[thirdVc setMyBlock:^(NSString * string) {
weakSelf.label1.text = string;
}];
}
B界面中 .m文件
- (void)button1Action:(id)sender{
[self.navigationController popViewControllerAnimated:YES];
//調用block并傳參數
self.myBlock(self.textF.text);
}
.h文件
#import
NS_ASSUME_NONNULL_BEGIN
typedef void (^Myblock)(NSString *string); //定義帶參的block
@interface ThirdViewController : UIViewController
@property (nonatomic, copy) Myblock myBlock;
@end
五:Block類型
block有三種類型:
全局塊(_NSConcreteGlobalBlock)
棧塊(_NSConcreteStackBlock)
堆塊(_NSConcreteMallocBlock)
這三種block各自的存儲域如下圖:
image.png
1)全局塊存在于全局內存中, 相當于單例.
2)棧塊存在于棧內存中, 超出其作用域則馬上被銷毀
3)堆塊存在于堆內存中, 是一個帶引用計數的對象, 需要自行管理其內存
簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。
遇到一個Block,我們怎么判斷Block的存儲位置呢?
(1)Block不訪問外界變量(包括棧中和堆中的變量),Block 既不在棧又不在堆中,在代碼段中,ARC和MRC下都是如此。換種說法是:“沒有引用局部變量 or 全局變量 or 靜態變量”,此時為全局塊。
(2)Block訪問外界變量
MRC 環境下:訪問外界變量的 Block 默認存儲棧中。(引用局部變量,不賦值給強引用)
ARC 環境下:訪問外界變量的 Block 默認存儲在堆中(實際是放在棧區,然后ARC情況下自動又拷貝到堆區),自動釋放。(引用局部變量,并且賦值給強引用, copy 或者 strong)
ARC下,訪問外界變量的 Block為什么要自動從棧區拷貝到堆區呢?
棧上的Block,如果其所屬的變量作用域結束,該Block就被廢棄,如同一般的自動變量。當然,Block中的__block變量也同時被廢棄。如下圖:
image.png
為了解決棧塊在其變量作用域結束之后被廢棄(釋放)的問題,我們需要把Block復制到堆中,延長其生命周期。開啟ARC時,大多數情況下編譯器會恰當地進行判斷是否有需要將Block從棧復制到堆,如果有,自動生成將Block從棧上復制到堆上的代碼。Block的復制操作執行的是copy實例方法。Block只要調用了copy方法,棧塊就會變成堆塊。
如下圖:
image.png
例如下面一個返回值為Block類型的函數:
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count) { return rate * count; };
}
分析可知:上面的函數返回的Block是配置在棧上的,所以返回函數調用方時,Block變量作用域就結束了,Block會被廢棄。但在ARC有效,這種情況編譯器會自動完成復制。
在非ARC情況下則需要開發者調用copy方法手動復制,由于開發中幾乎都是ARC模式,所以手動復制內容不再過多研究。
將Block從棧上復制到堆上相當消耗CPU,所以當Block設置在棧上也能夠使用時,就不要復制了,因為此時的復制只是在浪費CPU資源。
Block的復制操作執行的是copy實例方法。不同類型的Block使用copy方法的效果如下表:
image.png
根據表得知,Block在堆中copy會造成引用計數增加,這與其他Objective-C對象是一樣的。雖然Block在棧中也是以對象的身份存在,但是棧塊沒有引用計數,因為不需要,我們都知道棧區的內存由編譯器自動分配釋放。
不管Block存儲域在何處,用copy方法復制都不會引起任何問題。在不確定時調用copy方法即可。
在ARC有效時,多次調用copy方法完全沒有問題:
blk = [[[[blk copy] copy] copy] copy];
// 經過多次復制,變量blk仍然持有Block的強引用,該Block不會被廢棄。
2、block變量與forwarding
在copy操作之后,既然block變量也被copy到堆上去了, 那么訪問該變量是訪問棧上的還是堆上的呢?****forwarding 終于要閃亮登場了,如下圖:
image.png
通過forwarding, 無論是在block中還是 block外訪問block變量, 也不管該變量在棧上或堆上, 都能順利地訪問同一個__block變量。
六:Block循環引用
Block 循環引用的情況:
某個類將 block 作為自己的屬性變量,然后該類在 block 的方法體里面又使用了該類本身,如下:
self.someBlock = ^(Type var){
[self dosomething];
};
解決循環引用的辦法:
(1)ARC 下:使用 __weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
(2) MRC 下:使用 __block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};
值得注意的是,在ARC下,使用 __block 也有可能帶來的循環引用,如下:
// 循環引用 self -> _attributBlock -> tmp -> self
typedef void (^Block)();
@interface TestObj : NSObject
{
Block _attributBlock;
}
@end
@implementation TestObj
- (id)init {
self = [super init];
__block id tmp = self;
self.attributBlock = ^{
NSLog(@"Self = %@",tmp);
tmp = nil;
};
}
- (void)execBlock {
self.attributBlock();
}
@end
// 使用類
id obj = [[TestObj alloc] init];
[obj execBlock]; // 如果不調用此方法,tmp 永遠不會置 nil,內存泄露會一直在
注:部分內容參考了一些好的博客。
總結
以上是生活随笔為你收集整理的ios 添加block 类别_ios之Block的详细使用和具体说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shell 取中间行的第一列_shell
- 下一篇: outlook邮箱邮件大小限制_配置邮箱