對Block的一些理解

1、Block的使用

在OC編程中,咱們常常會使用到block。它能夠做爲成員變量、函數參數、函數返回值等等。接下來,咱們先來看下block的一些經常使用方式。html

  • 普通使用
- (void)blockTest1 {
    //無參數無返回值
    void(^printBlock)(void) = ^ {
        NSLog(@"print block");
    };
    printBlock();
    
    //有參數無返回值
    void(^combineBlock)(NSString *str, NSNumber *num) = ^(NSString *str, NSNumber *num) {
        NSString *combineStr = [NSString stringWithFormat:@"%@-%@", str, num];
        NSLog(@"combine block: %@", combineStr);
    };
    combineBlock(@"str", @1);
    
    //有參數有返回值
    int(^caculateBlock)(int value1, int value2) = ^(int value1, int value2) {
        return value1 + value2;
    };
    int sum = caculateBlock(1000, 24);
    NSLog(@"caculate block: %d", sum);
    
    //定義Block
    typedef NSString*(^ConvertBlock)(int value);
    ConvertBlock block = ^(int value) {
        return [NSString stringWithFormat:@"convert-%d", value];
    };
    NSString *convertStr = block(1);
    NSLog(@"convert block: %@", convertStr);
}
複製代碼

根據上面的例子,能夠看出Block的定義爲:返回類型 (^Block名稱) (參數類型)c++

  • 做爲函數參數
- (void)blockTest2 {
    [self doSomethingWithCompletion:^(BOOL success) {
        NSLog(@"do something finished with %@", (success ? @"YES" : @"NO"));
    }];
}

- (void)doSomethingWithCompletion:(void(^)(BOOL success))completion {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //模擬耗時
        if (completion) {
            completion(YES);
        }
    });
}
複製代碼

一般在進行一些異步操做的時候,咱們都會使用block做爲函數參數來回調結果。git

  • 做爲返回值
- (void)blockTest3 {
    NSString *(^convertBlock)(void) = [self createBlockWithValue:1];
    NSString *convertStr = convertBlock();
    NSLog(@"convert block: %@", convertStr);
}

- (NSString *(^)(void))createBlockWithValue:(int)value {
    return ^{
        return [NSString stringWithFormat:@"str-%d", value];
    };
}
複製代碼

一般將block做爲函數返回值處理的場景會比較少,不事後面講到的鏈式調用就會經過該形式實現。github

  • 做爲成員變量
@interface BlockViewController : UIViewController
@property (nonatomic, strong) void(^blockTest)(NSString *result);
@end
 
@implementation BlockViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    if (self.blockTest) {
        self.blockTest(@"block result");
    }
}
@end

BlockViewController *viewController = [[BlockViewController alloc] init];
viewController.blockTest = ^(NSString * _Nonnull result) {
  NSLog(@"block result: %@", result); //通知外層
};
[self.navigationController pushViewController:viewController animated:YES];
複製代碼

能夠經過設置成員變量爲block來通知外部調用者,從而達成二者數據的傳遞。objective-c

  • __block修飾符
- (void)blockTest4 {
    
    int value1 = 1;
    void (^onlyreadBlock)(void) = ^ {
        NSLog(@"only read block: %d", value1);
    };
    
    onlyreadBlock();
    
    __block int value2 = 1;
    void(^processBlock)(void) = ^ {
        value2 = 2;
        NSLog(@"process block: %d", value2);
    };
    
    processBlock();
}
複製代碼

當須要在block內修改局部變量時,須要經過__block修飾符定義,不然只能讀取,不能修改。編程

  • __weak__strong修飾符
- (void)blockTest5 {
    __weak typeof(self) weakSelf = self;
    [self doSomethingWithCompletion:^(BOOL success) {
        [weakSelf doSecondThing];
    }];
    
    [self doSomethingWithCompletion:^(BOOL success) {
        __strong typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            [strongSelf doSecondThing];
            [strongSelf doThirdThing];
        }
    }];
}

- (void)doSecondThing {
    NSLog(@"do second thing");
}

- (void)doThirdThing {
    NSLog(@"do third thing");
}
複製代碼

爲了不循環引用,一般在Block內會將self轉換爲weakSelf,但爲何有時候還須要使用strongSelf呢?好比第一個block中只使用weakSelf定義,而第二個block卻額外使用了__strongSelf數組

其實,這裏主要是沒法肯定block內weakSelf什麼時候會被釋放掉,對第一個block,若weakSelf被釋放了,則不會調用doSecondThing方法,這樣一般並不會致使什麼錯誤發生。但對於第二個block,若是仍是繼續沿用weakSelf,假設weakSelf在執行完doSecondThing後被釋放了,那麼就會致使doThirdThing方法不會被調用,意味着只執行了一個方法!這樣勢必是很容易引起出一些不可預見的狀況。promise

所以,爲了保證代碼執行的"完整性",block內使用__strong修飾符,這樣在weakSelf未被釋放的狀況下進入block後,block在被執行完前都不會被釋放。具體分析能夠看這篇文章bash

2、Block的內存類型

在咱們通常的開發工做中,每每不多會涉及到Block的內存類型相關。由於在ARC下,編譯器幫咱們自動處理了關於block內存相關的操做。不過總有些狀況,須要咱們對Block的內存概念有所瞭解,否則會致使一些錯誤的發生,好比下面的這個例子。app

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *blocks = [self blockArray];
    typedef void(^blk)(void);
    for (NSInteger index = 0; index < blocks.count; index ++) {
        blk block = blocks[index];
        if (block) {
            block();
        }
        NSLog(@"blk-%ld: %@", index, block);
    }
}

- (NSArray *)blockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil]; 
}
複製代碼

上面的例子,其實就是經過blockArray方法返回一個包含3個block的數組,而後遍歷該數組,分別調用block對象。

運行程序,會發現程序crash了,並提示:

block

能夠看到當訪問第二個block時,致使程序crash,顯示EXC_BAD_ACCESS。一般該錯誤是訪問了野指針,但這裏獲取到的blocks爲什麼會出現野指針,並且訪問第一個block的時候是正常的?

能夠先從blocks的debug信息下手:

debug信息顯示第一個block_NSMallocBlock_類型,再探究以前,這裏須要瞭解清楚Block的內存類型了。咱們再看另外一個例子:

- (void)blockTest {
    
    void(^globalBlock)(void) = ^ {
        NSLog(@"global block");
    };
    
    int value = 1;
    void(^stackBlock)(void) = ^ {
        NSLog(@"stack block: %d", value);
    };
    
    void(^mallocBlock)(void) = [stackBlock copy];
    
    NSArray *blocks = [[NSArray alloc] initWithObjects:globalBlock, stackBlock, mallocBlock, nil];
    
    for (id blk in blocks) {
        NSLog(@"blk: %@", blk);
    }
}
複製代碼

在運行前,須要在Build Prases -> Compile Sources中找到對應的文件,而後添加-fno-objc-arc,即對該文件禁用ARC功能。由於ARC會自動對Block進行一些額外內存處理操做。運行後,能夠看到結果以下:

blk: <__NSGlobalBlock__: 0x10ac45758>
blk: <__NSStackBlock__: 0x7ffee4fe4ea8>
blk: <__NSMallocBlock__: 0x600001d3daa0>
複製代碼

可見,上面的三種block分別對應Block的三種不一樣內存類型。

__NSGlobalBlock__: 存儲在數據段(通常用來存儲全局變量、靜態變量等,直到程序結束時纔會回收)中,block內未訪問了局部變量

__NSStackBlock__: 存儲在棧(通常用來存儲局部變量,自動分配內存,當局部變量做用域執行完後會被當即回收)中,block內訪問了局部變量

__NSMallocBlock__: 存儲在堆(alloc出來的對象,由開發者進行管理)中,當__NSStackBlock__進行copy操做時

清楚Block的內存類型後,再將文件中-fno-objc-arc去掉,看看ARC下,結果又是如何?

ARC下結果:
blk: <__NSGlobalBlock__: 0x1087bc758>
blk: <__NSMallocBlock__: 0x600001505a10>
blk: <__NSMallocBlock__: 0x600001505a10>
複製代碼

根據結果,得知第二個stackBlock也是__NSMallocBlock__類型,這是由於ARC下,當對棧block進行賦值操做時,編譯器會自動將其拷貝到堆中。

再回到第一個例子,就能解釋爲何會出現野指針問題了:由於blockArray中建立的block訪問了局部變量,爲__NSStackBlock__類型,而這裏並無對block進行賦值或者copy,因此ARC下也不會將其拷貝到堆中。所以,當blockArray做用域結束時,__NSStackBlock__類型的block也會被自動釋放,而致使後面訪問的block爲野指針。

- (NSArray *)blockArray {
    int val = 10;
    id block0 = ^(){NSLog(@"blk0: %d", val);};
    id block1 = ^(){NSLog(@"blk1: %d", val);};
    id block2 = ^(){NSLog(@"blk2: %d", val);};

    return [[NSArray alloc] initWithObjects:[^(){NSLog(@"blk0: %d", val);} copy], [^(){NSLog(@"blk1: %d", val);} copy], [^(){NSLog(@"blk2: %d", val);} copy], nil]; //方式二
    //return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil]; //方式二
}
複製代碼

如上,使用賦值或copy操做均可以使得block修改成__NSMallocBlock__類型,從而能正常訪問。

這裏還有一個問題就是,爲何第一個例子中第一個Block__NSMallocBlock__?筆者這裏猜想應該是initWithObjects:方法內部會保留第一個對象,即會致使第一個Block會被賦值,因此會被拷貝到堆中。

3、Block的本質

咱們知道在OC中,大部分狀況下都是跟NSObject對象打交道的,而Block彷佛是與NSObject不太同樣的一種存在形式。那麼Block的本質究竟是怎麼樣呢,接下來將經過一些例子來進行探究。

a. 先在main.m文件中定義一個block,並調用它

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        
        void(^block)(void) = ^ {
            NSLog(@"hello world");
        };
        
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
複製代碼

b. 而後使用clang插件對main.m改寫爲cpp文件

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
複製代碼

生成對應的main.cpp

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_main_25fc50_mi_0);
}

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));

        //1. block建立
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 

        //2. 調用block
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); 
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
複製代碼

這裏能夠分爲Block的建立和調用兩步來看,代碼這樣看起來可能會比較難理解,這裏對這兩個步驟整理爲以下:

// 1-1:建立__main_block_impl_0對象
__main_block_impl_0 block_impl = __main_block_impl_0((void *) __main_block_func_0, &__main_block_desc_0_DATA);
// 1-2: 將__main_block_impl_0強制轉換爲函數指針
void(*block)(void) = (void(*)()) &block_impl;

// 2-1:定義另外一個函數指針,並將__main_block_impl_0中的FuncPtr轉換爲定義的函數指針
void(*block_call)(__block_impl *) = (void(*)(__block_impl *)) ((__block_impl *)block)->FuncPtr;
// 2-2: 調用函數指針
block_call((__block_impl *)block);
複製代碼

如上所示,block的定義實際上就是建立了一個__main_block_impl_0的對象,而block的調用,即調用對象的函數指針FuncPtr

接下來,咱們來看下__main_block_impl_0相關信息,這裏對比着Block_layout來看:

//main.cpp
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//Block_private.h
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};
typedef void(*BlockInvokeFunction)(void *, ...);
複製代碼

能夠看到生成的__main_block_desc_0Block_layout的結構是一致的。

  • isa指針:瞭解過NSObject的內部結構的話,應該清楚,它通常是指向其父類
  • flags:用於判斷Block的一些信息,後面會講解到
  • reserved:保留信息
  • invoke:block調用對應的函數指針
  • descriptor: 用於存儲block的一些描述信息
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
複製代碼

仔細查看Block_private.h文件時,會發現有三種Block_descriptor_x結構體,而Block_layout只包含了Block_descriptor_1,是否其餘兩種就沒用到呢?咱們經過例子來驗證下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        __block int value = 1; 
        void (^block)(void) = ^{
            value = 2; //修改__block變量
            NSLog(@"update value: %d", value);
        };
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}


static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  // 新增
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_value_0 *value; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

如上所示,block修改了__block修飾符的變量,重寫爲c++代碼後,對比以前的__main_block_desc_0,新增了copydispose兩個函數指針。而這兩個函數指針恰好與Block_descriptor_2的變量對應。由此,能夠看出Block_layout會根據具體狀況決定是否添加Block_descriptor_2Block_descriptor_3

總結:經過上面的探究,得知:Block的本質爲一個Block_layout的結構體,也包含了isa指針,所以,也能夠稱爲OC對象。

4、__Block修飾符原理

咱們知道一般要Block內修改局部變量,必須得使用__block修飾局部變量。而對於成員變量和全局變量則直接修改便可。接下來咱們將經過例子來講明__block是如何實現支持局部變量的修改,以及爲什麼成員變量和全局變量不須要使用__block修飾。

- (void)_blockTest {
    NSInteger readonlyValue = 1;
    void(^readonlyBlock)(void) = ^{
        NSLog(@"readonly variable : %ld", readonlyValue);
    };
    readonlyBlock();
    
    void(^memberVariableBlock)(void) = ^{
        self.value = 1;
        NSLog(@"member variable :%ld", self.value);
    };
    memberVariableBlock();
    
    static NSInteger globalValue = 0;
    void(^globalVariableBlock)(void) = ^{
        globalValue = 1;
        NSLog(@"global variable :%ld", globalValue);
    };
    globalVariableBlock();
    
    __block NSInteger localValue = 0;
    void(^localVariableBlock)(void) = ^{
        localValue = 1;
        NSLog(@"local variable :%ld", localValue);
    };
    localVariableBlock();
}
複製代碼

一樣,咱們先經過clang插件將其重寫爲cpp文件:

static void _I_BlockTypeViewController__blockTest(BlockTypeViewController * self, SEL _cmd) {
    //1.僅訪問局部變量
    NSInteger readonlyValue = 1;
    void(*readonlyBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_0((void *)__BlockTypeViewController___blockTest_block_func_0, &__BlockTypeViewController___blockTest_block_desc_0_DATA, readonlyValue));
  
    ((void (*)(__block_impl *))((__block_impl *)readonlyBlock)->FuncPtr)((__block_impl *)readonlyBlock);
    
    //2.修改爲員變量
    void(*memberVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_1((void *)__BlockTypeViewController___blockTest_block_func_1, &__BlockTypeViewController___blockTest_block_desc_1_DATA, self, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)memberVariableBlock)->FuncPtr)((__block_impl *)memberVariableBlock);

    //3.修改全局變量
    static NSInteger globalValue = 0;
    void(*globalVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_2((void *)__BlockTypeViewController___blockTest_block_func_2, &__BlockTypeViewController___blockTest_block_desc_2_DATA, &globalValue));

    ((void (*)(__block_impl *))((__block_impl *)globalVariableBlock)->FuncPtr)((__block_impl *)globalVariableBlock);

    //4.修改局部變量
    __attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};
    void(*localVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_3((void *)__BlockTypeViewController___blockTest_block_func_3, &__BlockTypeViewController___blockTest_block_desc_3_DATA, (__Block_byref_localValue_0 *)&localValue, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)localVariableBlock)->FuncPtr)((__block_impl *)localVariableBlock);

}
複製代碼
  1. 訪問局部變量:查看__block_impl_結構體,能夠看到這裏把訪問的局部變量的值拷貝到結構體中了,在函數調用中,是直接訪問結構體中對應的值。所以,這種方式沒法修改外部局部變量的值!
struct __BlockTypeViewController___blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __BlockTypeViewController___blockTest_block_desc_0* Desc;
  NSInteger readonlyValue; //拷貝的值
  __BlockTypeViewController___blockTest_block_impl_0(void *fp, struct __BlockTypeViewController___blockTest_block_desc_0 *desc, NSInteger _readonlyValue, int flags=0) : readonlyValue(_readonlyValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __BlockTypeViewController___blockTest_block_func_0(struct __BlockTypeViewController___blockTest_block_impl_0 *__cself) {
    NSInteger readonlyValue = __cself->readonlyValue; // bound by copy
    ....
}
複製代碼
  1. 修改爲員變量:查看__block_impl_結構體,能夠看到這裏將BlockTypeViewController保存到結構體中,函數調用修改爲員變量時,直接經過結構體中保存的self引用來修改value變量。
struct __BlockTypeViewController___blockTest_block_impl_1 {
  struct __block_impl impl;
  struct __BlockTypeViewController___blockTest_block_desc_1* Desc;
  BlockTypeViewController *self;
  __BlockTypeViewController___blockTest_block_impl_1(void *fp, struct __BlockTypeViewController___blockTest_block_desc_1 *desc, BlockTypeViewController *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __BlockTypeViewController___blockTest_block_func_1(struct __BlockTypeViewController___blockTest_block_impl_1 *__cself) {
   BlockTypeViewController *self = __cself->self; // bound by copy
   ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)self, sel_registerName("setValue:"), (NSInteger)1);
   ....
}
複製代碼
  1. 修改全局變量:查看__block_impl_結構體,能夠看到結構體保存了globalValue的引用,函數修改時,直接修改其引用的值。
struct __BlockTypeViewController___blockTest_block_impl_2 {
  struct __block_impl impl;
  struct __BlockTypeViewController___blockTest_block_desc_2* Desc;
  NSInteger *globalValue;
  __BlockTypeViewController___blockTest_block_impl_2(void *fp, struct __BlockTypeViewController___blockTest_block_desc_2 *desc, NSInteger *_globalValue, int flags=0) : globalValue(_globalValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __BlockTypeViewController___blockTest_block_func_2(struct __BlockTypeViewController___blockTest_block_impl_2 *__cself) {
  NSInteger *globalValue = __cself->globalValue; // bound by copy
  (*globalValue) = 1;
  ...
}
複製代碼
  1. 修改局部變量:咱們會發現使用__block修飾符後,會對變量進行引用,並建立一個__Block_byref結構體。
__attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};

struct __Block_byref_localValue_0 {
  void *__isa;
__Block_byref_localValue_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger localValue;
};
複製代碼

而後將__Block_byref對象做爲引用傳入到__block_impl結構體中,並在函數修改值時,經過__block_impl獲取到__Block_byref的引用,再經過__Block_byref獲取到局部變量的引用,從而達到修改局部變量值的目的。

struct __BlockTypeViewController___blockTest_block_impl_3 {
  struct __block_impl impl;
  struct __BlockTypeViewController___blockTest_block_desc_3* Desc;
  __Block_byref_localValue_0 *localValue; // by ref
  __BlockTypeViewController___blockTest_block_impl_3(void *fp, struct __BlockTypeViewController___blockTest_block_desc_3 *desc, __Block_byref_localValue_0 *_localValue, int flags=0) : localValue(_localValue->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __BlockTypeViewController___blockTest_block_func_3(struct __BlockTypeViewController___blockTest_block_impl_3 *__cself) {
  __Block_byref_localValue_0 *localValue = __cself->localValue; // bound by ref
  (localValue->__forwarding->localValue) = 1;
  ...
}
複製代碼

總結:全局變量會保存其引用,成員變量會保存self指針,從而達到直接修改變量的目的;而局部變量,若未使用__block修飾,則直接將其值拷貝過去,所以block內的修改將沒法影響到外部的變量。使用__block修飾後,將會保存變量的引用。

5、BlockCopyRelease

上面談及到ACR下,對block賦值時,會對block進行copy操做,將其從棧中拷貝到堆中。接下來,咱們將經過源碼分析block的拷貝和釋放操做。

  • _Block_copy:該操做主要是將棧block拷貝到堆中,稱爲堆block。
enum {
    //引用計數
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    //堆block標識
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    //Block_layout中是否包含Block_descriptor_2的copy
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler 
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    //全局block標識
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    //Block_descriptor_3中signature和layout對應標識
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg; //1.
    if (aBlock->flags & BLOCK_NEEDS_FREE) { //2.
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) { //3.
        return aBlock;
    }
    else { 
        // Its a stack block. Make a copy.
        //4.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size); 
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first 
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // 5.
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // 6.
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        //7.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
複製代碼
  1. 將參數轉換爲Block_layout類型,咱們知道Block的本質其實就是Block_layout結構體。
  2. 根據BLOCK_NEEDS_FREE標識判斷block是否在堆中,如果,則直接增長其引用計數便可,不須要再進行拷貝操做。
static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) { //若超過引用計數的最大值,則直接返回最大值,避免值溢出
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) { //引用計數增長2,block的每次增長是以2位單位
            return old_value+2;
        }
    }
}
複製代碼
  1. 根據BLOCK_IS_GLOBAL標識判斷block是否爲全局block,如果,則直接返回。

  2. 首先使用malloc給新的block分配內存空間,而後再經過memmove方法將block信息拷貝到新的block中。

  3. 這裏先將block的引用計數設置爲0,而後設置標識BLOCK_NEEDS_FREE,並將引用計數設置爲2。

  4. 首先經過_Block_descriptor_2方法獲取到Block_layout中的Block_descriptor_2對象,而後調用其copy方法。這裏的做用主要是用於使用__block修飾的變量,會對局部變量進行copy操做,因此當對block進行copy時,同時也要對__block修飾的局部變量進行copy

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;
    (*desc->copy)(result, aBlock); // do fixup
}

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) {
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}
複製代碼
  1. 最後將block設置爲_NSConcreteMallocBlock
  • _Block_release:主要針對__NSMallocBlock__類型的block
void _Block_release(const void *arg) {
    //1.
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    //2.
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    //3.
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        //4.
        _Block_call_dispose_helper(aBlock);
        //5.
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}
複製代碼
  1. 將參數轉換爲Block_layout類型
  2. 若block爲全局block或棧block,則直接返回
  3. 根據引用計數判斷是否須要對block進行釋放
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) { //引用計數爲最大值,則直接返回
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) { //引用計數爲0,也直接返回
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2; 
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) { //引用計數爲2的話
            new_value = old_value - 1; //設置爲1,代表正在釋放
            result = true; //返回true,須要對block進行釋放操做
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) { //更新block的flag標識
            return result;
        }
    }
}
複製代碼
  1. 這裏其實就是對應上面的第6步操做,對_block修飾的局部變量進行釋放
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;
    (*desc->dispose)(aBlock);
}
複製代碼
  1. _Block_destructInstance默認是空操做的,最後調用free方法釋放內存
  • _Block_object_assign:該方法主要用於block內對外部變量的處理,針對不一樣狀況下,會對外部變量進行不一樣的處理。咱們能夠先看一個例子:
- (void)blockAssignTest {
    NSString *str = @"str";
    void(^block1)(void) = ^{
        NSLog(@"%@", str);
    };
    block1();
    
    __block NSNumber *value = @1;
    void(^block2)(void) = ^{
        NSLog(@"%@", value);
    };
    block2();
}
複製代碼

使用clang插件改寫爲c++文件後:

static void _I_BlockTypeViewController_blockAssignTest(BlockTypeViewController * self, SEL _cmd) {
    NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_BlockTypeViewController_3fbb00_mi_0;
    void(*block1)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_0((void *)__BlockTypeViewController__blockAssignTest_block_func_0, &__BlockTypeViewController__blockAssignTest_block_desc_0_DATA, str, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

    __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 33554432, sizeof(__Block_byref_value_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1)};
    void(*block2)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_1((void *)__BlockTypeViewController__blockAssignTest_block_func_1, &__BlockTypeViewController__blockAssignTest_block_desc_1_DATA, (__Block_byref_value_0 *)&value, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
}
複製代碼

能夠看到使用了__block修飾符的變量會建立一個__Block_byref_value_0來存儲變量,這個在講解__block的時候也說起到了。咱們先分別對比看下二者的__BlockTypeViewController__blockAssignTest_block_desc_0_DATA__BlockTypeViewController__blockAssignTest_block_desc_1_DATA

static struct __BlockTypeViewController__blockAssignTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*, struct __BlockTypeViewController__blockAssignTest_block_impl_0*);
  void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*);
} __BlockTypeViewController__blockAssignTest_block_desc_0_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_0), __BlockTypeViewController__blockAssignTest_block_copy_0, __BlockTypeViewController__blockAssignTest_block_dispose_0};

static struct __BlockTypeViewController__blockAssignTest_block_desc_1 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*, struct __BlockTypeViewController__blockAssignTest_block_impl_1*);
  void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*);
} __BlockTypeViewController__blockAssignTest_block_desc_1_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_1), __BlockTypeViewController__blockAssignTest_block_copy_1, __BlockTypeViewController__blockAssignTest_block_dispose_1};
複製代碼

二者都包含了copydispose函數指針,這裏先看看copy函數指針對應的函數實現:

static void __BlockTypeViewController__blockAssignTest_block_copy_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) {
    _Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __BlockTypeViewController__blockAssignTest_block_copy_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) {
    _Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼

能夠看到二者都調用了_Block_object_assign方法,但二者傳入了一個大小不一樣的flag。那麼接下來咱們繼續看_Block_object_assign對這二者有什麼不一樣的處理。

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /******* id object = ...; [^{ object; } copy]; ********/
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* void (^object)(void) = ...; [^{ object; } copy]; ********/
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; ********/
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/
        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/
        *dest = object;
        break;

      default:
        break;
    }
}
複製代碼

如上所示,_Block_object_assign會根據傳入的flag來作不一樣的處理

enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
複製代碼

這裏主要深挖下BLOCK_FIELD_IS_BYREF的狀況,即便用了__block修飾符的狀況,會調用_Block_byref_copy方法對變量進行拷貝。

static struct Block_byref *_Block_byref_copy(const void *arg) {
    //1.
    struct Block_byref *src = (struct Block_byref *)arg; 
 
    //2.
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        //3.
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        //4.
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;
            //5.
            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            //6.
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // 7.
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}
複製代碼
  1. 在咱們上面改寫的cpp文件中能夠看到,對於__block修飾的變量會被封裝成一個__Block_byref_value_0結構體,而這個結構體和Block_byref是一致的。因此這裏將參數強制轉換爲Block_byref結構體。
struct __Block_byref_value_0 {
  void *__isa;
__Block_byref_value_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSNumber *value;
};

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
複製代碼

其中Block_byref_2Block_byref_3會根據不一樣狀況,編譯器將其添加到Block_byref中,這一點跟Block_layout是一致的。

  1. 判斷當前引用計數是否爲0,若爲0,則須要進行拷貝操做;
  2. 調用malloc方法將變量拷貝到堆中,並將設置相關信息,這裏要注意flags標識的設置copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;這裏先設置其爲BLOCK_BYREF_NEEDS_FREE,代表存在堆中,而後將其引用計數設置爲4,其中調用者和棧各自持有。
- (void(^)(void))blockTest1 {
    __block NSNumber *value = @1;
    void(^block)(void) = ^{
        NSLog(@"%@", value);
    };
    return block;
}

- (void)blockTest2 {
    void(^blk)(void) = [self blockTest1];
    blk();
}
複製代碼

如上,對於blockTest1方法中__block修飾的value變量,引用計數分別由blockTest1所處棧和blockTest2調用方持有。

  1. Block_byref中包含Block_byref_2,則須要取出Block_byref_2,而後分別賦值給copy對象
  2. Block_byref中包含Block_byref_3,則須要取出Block_byref_3,而後賦值layout指針
  3. Block_byref中不包含Block_byref_2,則直接使用memmove方法拷貝。
  4. flags中含有BLOCK_BYREF_NEEDS_FREE,代表已經存在堆中了,則只須要增長其引用計數便可。
  • _Block_object_dispose:對block持有的外部變量的釋放操做,與_Block_object_assign相反。能夠回到上個例子的dispose函數
static void __BlockTypeViewController__blockAssignTest_block_dispose_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) {
    _Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __BlockTypeViewController__blockAssignTest_block_dispose_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) {
    _Block_object_dispose((void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼

如上所示,都調用了_Block_object_dispose方法對變量進行處理:

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}
複製代碼

這裏主要對BLOCK_FIELD_IS_BYREF的狀況進行分析_Block_byref_release方法:

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}
複製代碼

這裏首先根據flags標識判斷對象是否在堆中,若在堆中,而後再根據對象的引用計數判斷是否進行銷燬,若須要銷燬,則判斷Block_byref對象是否包含dispose指針,若包含,則調用該函數指針銷燬,最後調用free方法銷燬Block_byref

6、OC中的鏈式調用

Objective-C對比起Swift語法會顯得更加繁瑣些,對於一些連貫性的方法操做,使用普通的方法調用方式,可能會顯得很臃腫。這時候就能夠考慮使用Block來實現OC的鏈式調用。

以一個簡易計算器的實現爲例,一開始咱們可能會這樣實現:

@interface Calculator : NSObject
@property (nonatomic, assign, readonly) NSInteger result;
- (instancetype)initWithValue:(NSInteger)value;
- (void)add:(NSInteger)value;
- (void)sub:(NSInteger)value;
- (void)multiply:(NSInteger)value;
- (void)divide:(NSInteger)value;
@end
  
@interface Calculator () 
@property (nonatomic, assign, readwrite) NSInteger result;
@end

@implementation Calculator
- (instancetype)initWithValue:(NSInteger)value {
    if (self = [super init]) {
        self.result = value;
    }
    return self;
}
- (void)add:(NSInteger)value {
    self.result += value;
}
- (void)sub:(NSInteger)value {
    self.result -= value;
}
- (void)multiply:(NSInteger)value {
    self.result *= value;
}
- (void)divide:(NSInteger)value {
    if (value == 0) {
        return;
    }
    self.result /= value;
}
@end
複製代碼

調用方式:

// (2*3+4-8)/2
Calculator *cal = [[Calculator alloc] initWithValue:2];
[cal multiply:3];
[cal add:4];
[cal sub:8];
[cal divide:2];
複製代碼

明顯這樣的方式看起來邏輯很是不連貫,若是改爲使用鏈式調用呢?

- (Calculator *(^)(NSInteger))add;
- (Calculator *(^)(NSInteger))sub;
- (Calculator *(^)(NSInteger))multiply;
- (Calculator *(^)(NSInteger))divide;

- (Calculator * _Nonnull (^)(NSInteger))add {
    return ^(NSInteger value) {
        self.result += value;
        return self;
    };
}
- (Calculator * _Nonnull (^)(NSInteger))sub {
    return ^(NSInteger value) {
        self.result -= value;
        return self;
    };
}
- (Calculator * _Nonnull (^)(NSInteger))multiply {
    return ^(NSInteger value) {
        self.result *= value;
        return self;
    };
}
- (Calculator * _Nonnull (^)(NSInteger))divide {
    return ^(NSInteger value){
        self.result /= value;
        return self;
    };
}

//調用方式:
Calculator *cal = [[Calculator alloc] initWithValue:2];
cal.multiply(3).add(4).sub(8).divide(2);
複製代碼

如上所示,使用鏈式調用的方式,明顯更能使代碼更簡潔,更連貫。而實現這一方式的本質是返回一個返回值爲自身的Block對象。

固然,其實在平時使用的一些第三方庫裏,也常常能見到鏈式調用的影子,好比Masonry

__weak typeof(self) weakSelf = self;
[self.blockView mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.width.mas_equalTo(50).height.mas_equalTo(100);
  make.left.mas_equalTo(weakSelf.view.mas_left).offset(20).top.mas_equalTo(weakSelf.view.mas_top).offset(100);
}];
複製代碼

7、不定參數Block

咱們知道Block的格式爲返回類型(^名稱)(參數類型),一般咱們定義的時候,通常都是會定義固定個數的參數,好比void(^blk1)(NSString*)void(^blk2)(NSString*, NSNumber*),但有些場景下,爲了實現Block的通用性,會考慮使用不定參數Block。其格式爲void(^blk)(),即參數列表設置爲空

- (void)variableBlockTest {
    
    typedef void(^CommonBlock)();
    
    void(^nonParameterBlock)(void) = ^{
        NSLog(@"no parameter");
    };
    
    void(^oneParameterBlock)(NSString *) = ^(NSString *param){
        NSLog(@"parameter: %@", param);
    };
    
    void(^twoParameterBlock)(NSString *, NSNumber *) = ^(NSString *param1, NSNumber *param2) {
        NSLog(@"parameter: %@, %@", param1, param2);
    };
    
    CommonBlock blk1 = nonParameterBlock;
    CommonBlock blk2 = oneParameterBlock;
    CommonBlock blk3 = twoParameterBlock;
    
    blk1();
    blk2(@"str");
    blk3(@"str", @2);
}
複製代碼

如上所示,咱們能夠對不定參數CommonBlock傳入任意參數調用,但前提是須要保證CommonBlock的內部結構參數個數與傳入參數個數是一致的,不然會出錯,好比blk1(@"str")這樣調用是不容許的,由於blk1本質爲nonParameterBlock,參數個數爲0。這裏咱們只能經過名稱來肯定CommonBlock的具體參數個數,但有些狀況下,就沒法這麼肯定了。

- (void)doSomethingWithCommonBlock:(CommonBlock)blk {
    //沒法肯定blk參數個數
}
複製代碼

固然你也能夠增長一個參數,再調用的時候將blk的參數個數傳遞過去:

[self doSomethingWithCommonBlock:nonParameterBlock withArgumentsNum:0];
[self doSomethingWithCommonBlock:oneParameterBlock withArgumentsNum:1];
[self doSomethingWithCommonBlock:twoParameterBlock withArgumentsNum:2];

- (void)doSomethingWithCommonBlock:(CommonBlock)blk withArgumentsNum:(NSInteger)num{
    
}
複製代碼

這樣的處理方式顯然會比較」難看「,有沒什麼更優雅的處理方式呢?其實,根據上面的探究,咱們知道Block的本質就是一個Block_layout結構體。在編譯過程當中,clang插件也會將Block轉換爲對應的結構體,具體轉換規則,能夠看這裏

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};
複製代碼

咱們能夠看到Block_literal_1中有個signature變量,即函數簽名,若是能獲取到該變量,則能夠經過NSMethodSignature獲取到對應的numberOfArguments,即參數個數。這裏將經過對PromiseKit獲取Block函數簽名的方式來分析。

struct PMKBlockLiteral {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct block_descriptor {
        unsigned long int reserved;	// NULL
    	unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
    	void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
    	void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

typedef NS_OPTIONS(NSUInteger, PMKBlockDescriptionFlags) {
    PMKBlockDescriptionFlagsHasCopyDispose = (1 << 25),
    PMKBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code
    PMKBlockDescriptionFlagsIsGlobal = (1 << 28),
    PMKBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    PMKBlockDescriptionFlagsHasSignature = (1 << 30)
};

static NSMethodSignature *NSMethodSignatureForBlock(id block) {
    if (!block)
        return nil;
    //1.
    struct PMKBlockLiteral *blockRef = (__bridge struct PMKBlockLiteral *)block;
    //2.
    PMKBlockDescriptionFlags flags = (PMKBlockDescriptionFlags)blockRef->flags;
   
    //3
    if (flags & PMKBlockDescriptionFlagsHasSignature) {
        void *signatureLocation = blockRef->descriptor;
        signatureLocation += sizeof(unsigned long int);
        signatureLocation += sizeof(unsigned long int);
        //4.
        if (flags & PMKBlockDescriptionFlagsHasCopyDispose) {
            signatureLocation += sizeof(void(*)(void *dst, void *src));
            signatureLocation += sizeof(void (*)(void *src));
        }
        //5.
        const char *signature = (*(const char **)signatureLocation);
        return [NSMethodSignature signatureWithObjCTypes:signature];
    }
    return 0;
}
複製代碼
  1. 首先將block強制轉換爲PMKBlockLiteral結構體,以便獲取到對應的信息;
  2. 獲取到block的flags標識,用於判斷是否包含函數簽名;
  3. 若包含函數簽名,則須要經過指針移動到block_descriptorsignature;
  4. 咱們知道Block並非必定包含copy_helperdispose_helper指針,因此也要經過flags標識來判斷,從而肯定是否要移動指針位置;
  5. 最後將signature轉換爲NSMethodSignature
- (void)doSomethingWithCommonBlock:(CommonBlock)blk {
    NSMethodSignature *signature = NSMethodSignatureForBlock(blk);
    if (!signature) {
        return;
    }
    NSInteger arguments = signature.numberOfArguments-1;
    if (arguments == 0) {
        blk();
    } else if (arguments == 1) {
        blk(@"str");
    } else if (arguments == 2) {
        blk(@"str", @2);
    }
}
複製代碼

這樣咱們就不用額外傳入block對應的參數個數了,這裏要注意一點的是block的參數個數是signature.numberOfArguments-1。由於NSMethodSignaturenumberOfArguments是包含self_cmd這兩個參數的,因此對應方法真正的參數個數應該減2處理,而block不含_cmd,因此減1便可。

不定參數Block在實際應用中還有不少場景,比較典型的就是上面介紹的PromiseKit,利用不定參數block來實現then操做的通用性,固然不定參數Block也有一個比較明顯的缺點:沒法提供代碼提示,須要本身手動去寫參數。對於這個缺點,其實也能夠經過自定義代碼塊處理。

筆者學習完PromiseKitpromises後,針對二者優缺點,也造了一個關於promise概念的輪子JPromise

參考資料

相關文章
相關標籤/搜索