Block:帶有自動變量的匿名函數,它是C語言的拓展功能,之因此是擴展,是由於C語言不容許存在這樣的匿名函數編程
匿名函數是指不帶函數名稱的函數函數
這是由於Block擁有捕獲外部變量的功能,在Block中訪問一個外部的局部變量,Block會持有它的臨時狀態,自動捕獲變量值,外部局部變量的變化不會影響它的狀態ui
int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n", val);
};
val = 2;
blk(); // 這裏輸出的值是10,而不是2,由於block在實現時就會對它所在方法中定義的棧變量進行一次只讀拷貝
複製代碼
__block
修飾__block int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n", val);
};
val = 2;
blk(); // 這裏輸出的值是2
複製代碼
UIViewController 須要監聽TableView中Cell的某個按鈕的點擊事件,既能夠經過Delegate回調,也能夠利用Block回調 Block回調的思路: 聲明一個Block屬性,注意這裏要用copy。 利用Block屬性進行回調atom
以
[UIView animateWithDuration:animations:]
爲例,animations是一個block對象,利用block實現調用者與UIView之間的數據傳遞spa
鏈式編程思想:將block做爲方法的返回值,且返回值的類型爲調用者自己,並將該方法以setter的形式返回,從而實現連續調用設計
// CaculateMaker.h
// ChainBlockTestApp
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CaculateMaker : NSObject
@property (nonatomic, assign) CGFloat result;
/* * 返回類型 CaculateMaker * 傳入參數 CGFloat num */
- (CaculateMaker *(^)(CGFloat num))add;
@end
// CaculateMaker.m
// ChainBlockTestApp
#import "CaculateMaker.h"
@implementation CaculateMaker
- (CaculateMaker *(^)(CGFloat num))add;{
return ^CaculateMaker *(CGFloat num){
_result += num;
return self;
};
}
@end
// 使用
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
複製代碼
由於在MRC狀況下若是Block屬性不使用copy修飾,在使用中會出現崩潰,在ARC狀況下,Block屬性使用strong修飾會被默認進行copy,因此ARC狀況下,Block屬性能夠使用strong或copy修飾,否則會出現崩潰。
**
爲什麼會有這種現象出現?指針
Block在內存中的位置分爲三種類型:code
這三種類型對應如下三種狀況:對象
當Block類型是__NSStackBlock__時,一旦超出了變量做用域,棧上的Block以及__block變量就會被銷燬,從而致使調用Block回調時崩潰。所以,Block屬性須要用copy修飾來避免這種狀況。生命週期
- (void)click:(id)sender {
TestClass *test = [[TestClass alloc] init];
__block int a = 1;
// 弱引用,block類型是__NSStackBlock__ 當TestClass執行回調時必崩 EXC_BAD_ACCESS
test.weakBlock = ^() {
NSLog(@"ok");
a = 2;
};
// block類型是__NSStackBlock__ 當TestClass執行回調時必崩 EXC_BAD_ACCESS
test.assignBlock = ^() {
NSLog(@"ok");
a = 3;
};
// block類型是__MallocBlock__ 正常執行
test.copyBlock = ^() {
NSLog(@"ok");
a = 4;
};
// block類型是__MallocBlock__ 正常執行
test.strongBlock = ^() {
NSLog(@"ok");
a = 5;
};
NSLog(@"copy property: %@", test.copyBlock);
NSLog(@"assign property: %@", test.assignBlock);
NSLog(@"weak property: %@", test.weakBlock);
NSLog(@"strong property: %@", test.strongBlock);
[test start];
}
複製代碼
首先,須要弄明白一個概念,static聲明的靜態局部變量是能夠在block內進行修改的,爲什麼會有這種區別呢?
由於,靜態局部變量存在於應用程序的整個生命週期,而非靜態局部變量僅存在於一個局部上下文中,絕大多數狀況下,block都是延後執行的,這就有可能出現非靜態局部變量被回收的狀況,爲了不這個問題,蘋果在設計block時,block中非靜態局部變量是值傳遞,這也解釋了爲什麼block會持有變量的臨時狀態,後續再修改,block中的變量值也再也不改變。
加上__block修飾的局部變量,被block捕獲時,就再也不是傳遞局部變量的值了,而是變成了一個結構體實例。好比:
定義一個 __block int a = 10;
會變成 __Block_byref_a_0 *a;
__Block_byref_a_0
的結構體以下所示:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding; // forwarding指針
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
int a; // 原變量同類型變量
};
複製代碼
結構體中有一個forwarding指針,此指針指向轉換後變量自己,結構體中也有一個原變量同樣類型的變量。此後代碼中涉及到原變量的地方,都會轉換成新變量->forwarding->原變量同類型變量
若是在block中直接修改變量的值,實質的過程是新變量->__forwarding->原變量同類型變量,最終修改的實際上是結構體中原變量同類型變量,很明顯這個結構體內的變量已經不屬於block的外部變量了,因此能在block內修改。
這個新變量也是非靜態局部變量,因此若是沒有copy,block執行時,新變量有可能已經被棧回收
總結:
block修飾的變量轉換成告終構體,結構體內有一個forwarding指針和一個與原變量相同類型的成員變量,forwarding指針指向結構體內的成員變量。不管在block內外,都是經過forwarding來訪問的。
咱們先看一段block致使循環引用的代碼:
TestClass *test = [[TestClass alloc] init];
test.copyBlock = ^() {
NSLog(@"ok: %d", test.result);
};
複製代碼
當咱們寫完這段代碼後,Xcode就會提醒咱們,這段代碼存在循環引用,事實上也確實存在循環引用。接下來咱們就來分析一下爲何會產生循環引用。
test的屬性block強引用了SecondViewController中的block,SecondViewController中的block又強引用了test的屬性result,從而致使了循環引用。
並不是全部的block都存在循環引用,下面列舉一些常見的block使用的示例:
// self-->requestModel-->block-->self
[self.requestModel requestData:^(NSData *data) {
self.name = @"leafly";
}];
// 雖然存在引用環,可是經過主動釋放requestModel打破了循環
[self.requestModel requestData:^(NSData *data) {
self.name = @"leafly";
self.requestModel = nil;
}];
// t-->block-->self 不存在循環引用
Test *t = [[Test alloc] init];
[t requestData:^(NSData *data) {
self.name = @"leafly";
}];
// AFNetworking-->block-->self 不存在循環引用
[AFNetworking requestData:^(NSData *data) {
self.name = @"lealfy";
}];
複製代碼