概述 在iOS 4.0之後,block橫空出世,它本身封裝了一段代碼並將這段代碼當做變數,通過block()的方式進行回調。這不免讓我們想到在C函數中,我們可以定義一個指向函數的指針並且調用。 。 Block基本使用 Block的類型 block也是一種數據類型,Block的類型是什麼呢。 就是Bl ...
概述
在iOS 4.0之後,block橫空出世,它本身封裝了一段代碼並將這段代碼當做變數,通過block()的方式進行回調。這不免讓我們想到在C函數中,我們可以定義一個指向函數的指針並且調用。
#import <Foundation/Foundation.h>
void function(){
NSLog(@"function執行了");
}
int main(int argc, const char * argv[]) {
void(*funcP)(void) = function;
// 函數指針調用函數
funcP();
return 0;
}
Block的本質就是函數指針。只要通過函數指針可以在任何時候執行函數
。
Block基本使用
Block的類型
block也是一種數據類型,Block的類型是什麼呢。
返回值類型(^)(參數類型列表)
就是Block的類型。開發中可以利用typedef定義同一種類型的Block。
// 定義一個沒有返回值沒有形參的Block類型MyBlock
typedef void(^MyBlock)(void);
Block的聲明定義
聲明一個block類型的變數
返回值類型(^blockName)(參數類型列表)
定義一個block
^返回值類型(參數列表) {
};
Block的定義不管有沒有返回值,在定義時返回值類型可以省略。當一個block沒有參數時Block定義^後面的括弧也可以省略。
// 標準的聲明與定義一個block
void (^block)() = ^void(){
};
// 定義省略返回值
void (^block)() = ^(){
};
// block沒有返回值省略^後面的括弧
void (^block)() = ^{
};
Block的使用場景
監聽逆向傳值
開發中我們通常使用代理來做監聽並且逆向傳值,其實使用Block也可以做到。給被監聽著添加一個Block屬性,在外界給被監聽著賦值block。當被監聽著內部發生了事件想通知給外界可以執行屬性block。通過Block的參數將值傳遞出來。
其實本質就是Block就是一個函數指針,block定義其實就是一個定義函數的實現。當執行block,就通過block指針地址找到方法實現執行函數。這個Block定義在監聽者內部,當被監聽者內部執行了block就等於執行了監聽者內部定義的函數。
Block的記憶體管理
MRC下Block的記憶體管理
在MRC中Block在記憶體中的位置是有多種情況,總體分為三種.
* NSGlobalBlock
* NSStackBlock
* NSMallocBlock
在MRC定義一個Block,對Block進行控制台輸出發現Block預設是在全局區
的。
void (^block)(void) = ^void(){
NSLog(@"----------------");
};
NSLog(@"%@", block);
當Block內部訪問了局部變數,Block是在棧區
。如果訪問外部的變數靜態變數或者全局變數,Block還是保存在全區區。
int a = 12;
void (^block)(void) = ^void(){
NSLog(@"----------------%d", a);
};
當一個棧區
的Block通過copy後會生成新的Block,此時的Block存儲在堆區。全局區的Block被copy後沒有生成新的Block。
int a = 10;
void (^block)(void) = ^void(){
// 訪問了外界的局部變數a block就保存在棧區
NSLog(@"----------------%d", a);
};
NSLog(@"%@", block);
NSLog(@"%@", [block copy]);
這就是為什麼在MRC下,Block屬性Propery會使用copy。如果使用的是retain那麼block在棧區過了作用域就會釋放,當調用者屬性block時發生壞訪問。
如果是MRC聲明瞭一個copy修飾的屬性,建議在對象的dealloc的方法對所擁有的Block進行release。
- (void)dealloc
{
[self.block release];
[super dealloc];
}
ARC下Block的記憶體管理
在ARC下預設定義Block同樣存儲在全局區
,不同的是在ARC下,Block內部引用了局部變數是存儲在堆區的。
static int a = 10;
void (^block)(void) = ^void(){
NSLog(@"----------------%d", a); // <__NSMallocBlock__: 0x604000241860>
};
在ARC下如果聲明一個Block屬性property修飾符建議使用strong。如果使用的copy跟strong的作用一樣用一個強指針引用著Block。但是copy內部需要做一些邏輯處理,為了性能建議使用strong。
Block迴圈引用
在FMModelVCViewController控制器中聲明一個block屬性。在viewDidLoad中創建一個block為屬性賦值。block內部輸出self
。
@interface FMModelVCViewController ()
/** block屬性 */
@property (nonatomic, strong) void(^block)(void);
@end
@implementation FMModelVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
void(^block)(void) = ^{
NSLog(@"----------------%@", self);
};
self.block = block;
}
Block會對裡面的所有外部強指針變數進行強引用。
上面的代碼就造成了迴圈引用,在記憶體中控制器不會銷毀。
解決方案
在Block內部訪問控制器__weak
修飾指針的。
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
NSLog(@"----------------%@", weakSelf);
};
self.block = block;
}
這樣Block對控制器強產生的是弱引用。
Block中延時任務問題
當Block中有延時操作,延時操作block中想訪問外界的對象,但是通常Block為了防止迴圈引用使用是_weak修飾的對象指針。當Block內部的延時Block訪問的weak修飾的對象也是弱引用。有可能造成當執行延時的Block時,其內部引用的外部對象已經銷毀。
#import "FMModelVCViewController.h"
@interface FMModelVCViewController ()
/** block屬性 */
@property (nonatomic, strong) void(^block)(void);
@end
@implementation FMModelVCViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
// afterBlock由系統管理
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----------------%@", weakSelf);
});
};
self.block = block;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 執行block
self.block();
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
當點擊屏幕時控制器執行了dismissViewControllerAnimated控制器方法控制器銷毀,2s過後執行延時Block是輸出null;
此時的記憶體如下:
解決方案
對Block內部代碼調整如下
這樣2s之後依然可以訪問到控制器,當延時Block執行完畢。控制器才銷毀。至於為什麼通過記憶體圖你就明白了。