请稍侯

Objective-C当中的Block

07 December 2020

Objective-C当中的Block

Blocks是C语言的扩充功能,即带有自动变量(局部变量)的匿名函数。 使用clang可以通过添加-rewrite-objc编译选项将含有Block语法的源代码变换为C++的源代码,如用clang -rewrite-objc main.m会生成一个与main.m对等的main.cpp文件。

先看总结:

  • 当没有引入外部自动变量时,Block__NSGlobalBlock__
  • 当只引入了非自动变量(如void *、变量的类型)时,Block仍然是__NSGlobalBlock__
  • 当引入了外部自动变量时,Block会变成__NSMallocBlock__
  • __weak修饰之后,Block变成了__NSStackBlock__
  • 引入静态变量、全局变量或全局静态变量,Block__NSGlobalBlock__
  • Block截获的自动变量值被保存在Block结构体实例(即Block自身)中,即__main_block_impl_0结构体中;
  • Block优先设置在__NSStackBlock__上,只有当Block引入的变量的作用域结束时,才会把Block__block变量从栈复制到堆上,同时栈上的Block__block变量被废弃,堆上的不受影响;
  • __NSGlobalBlock__类型的Block是存放在全局数据区的,只要Block不截获自动变量,那Block结构体实例就会存放在全局数据区,即程序的__DATA__段中。
  • 当通过函数返回的Block,函数返回时编译器会自动生成复制到堆上的代码 objc_retainBlock,该函数内直接调用了_Block_copy,而此函数就会将Block从栈上复制到堆上;

  • Block结构体类似于基于objc_object结构体的Ojbective-C类对象的结构体,其中的成员变量isa在构造函数中是这样isa = &_NSConcreteStackBlock;被初始化的。
  • blk()的调用,实际上是调用__main_block_impl_0__block_impl实例impl的成员FuncPtr,而FuncPtr即是构造Block对象(__main_block_impl_0)时传入的__main_block_func_0函数指针;
  • __main_block_copy_0__main_block_dispose_0是拷贝和释放赋值在对象类型的结构体成员变量中的对象,在Block从栈复制到堆时以及堆上的Block被废弃时会调用这两函数;

  • 当变量用__block修饰变量a后,在block捕获变量a后会为变量生成一个叫__Block_byref_a_0__block结构体,用来存放a变量,并且在__main_block_impl_0的结构体中引用__Block_byref_a_0对象的指针作为结构体的成员变量。

  • 以下几种情况栈上的Block会复制到堆上:
    • 调用Blockcopy实例方法;
    • Block作为函数返回值返回时;
    • Block赋值给附有__strong修饰的id类型变量或Block类型成员变量时;
    • 在方法名中含有usingBlockcocoa框架方法或CGD的API中传递Block时;

编译源码libclosure

  • Base SDK设置为macOS;

  • Apple Source code下载源码;
  • 当编译源码libclosure<platform/string.h> file not found时,把libplatform下载解压后,把头文件导入macosxsdk的路径下的/usr/local/include下,使用如下命令:

     sudo ditto $PWD/private $(xcrun -sdk macosx -show-sdk-path)/usr/local/include
        
     //查看`macosx``sdk`的路径:`xcrun -sdk macosx -show-sdk-path`
  • 同理,如果报<os/assumes.h> file not found,把Libc下下来并把其中os下的头文件拷贝到$(xcrun -sdk macosx -show-sdk-path)/usr/local/include/os/下。

  • 如果报*included file ‘/Makefiles/CoreOS/Xcode BSD.xcconfig does not exist*,就把[ CoreOSMakefiles](https://luowei.github.io/https://opensource.apple.com/tarballs/CoreOSMakefiles/CoreOSMakefiles-79.tar.gz)下载下来解压后,把里面的`BSD.xcconfig`拷贝到`/Applications/Xcode.app/Contents/Developer`下。

  • 当然更好的方式,是在项目target的 Build Settings -> Header Search Paths 里添加,如这样:
    $(SRCROOT)/LWCommon  //自己创建的文件夹,放缺失的头文件
    $(DSTROOT)/usr/include
    $(DSTROOT)/usr/local/include
    $(CONFIGURATION_BUILD_DIR)/usr/include
    $(CONFIGURATION_BUILD_DIR)/usr/local/include
    /System/Library/Frameworks/System.framework/PrivateHeaders
    
  • 源码编译成功后,建立新的 target 来执行你程序,从而跑进源码,在target上设置target -> objc -> Build Settings -> Enable Hardened Runtime -> NO 即可。

下面用代码来验证这些结论:

#import <Foundation/Foundation.h>

#pragma clang diagnostic push

@interface Hello : NSObject
-(NSString *)uname;
@end
@implementation Hello
-(NSString *)uname{
    return @"张三";
}
@end

typedef int(^Block_t) (int);

//1. 当没有引入外部自动变量时,blk是__NSGlobalBlock__
void testBlock1() {
    void (^blk)(void) = ^{
        NSLog(@"Hello");
    };
    
    blk();
    NSLog(@"====blk1:%@",blk);
    
    // for(int i=0;i<10;i++){
    //     Block_t blk_t = ^(int count){return count * i;};
    //     blk_t(5);
    //     NSLog(@"====blk_t1:%@",blk_t);
    // }
}

//2. 当引入了非自动变量(如void*、变量的类型)时,blk还是__NSGlobalBlock__
void testBlock2() {
    //void *hello = (__bridge void *)([[Hello alloc] init]);
    Hello *hello = [[Hello alloc] init];
    
    //分配1个32字节的空间
    void *p1 = calloc(1, 32);
    void *p2 = calloc(1, 16);

    void (^blk)(void) = ^{
        NSLog(@"hello size:%lu",sizeof(hello)); //这样引入就还是__NSGlobalBlock__
        // NSLog(@"hello:%@",hello);  //这样引入就变成了__NSMallocBlock__

        NSLog(@"p1:%lu p2:%lu",sizeof(p1),sizeof(p2));
        // 如果此时调用free释放掉p1,p2,则相当于引入了自动变量
        // free(p1);
        // free(p2);
    };
    
    blk();
    NSLog(@"====blk2:%@",blk);
}

//3. 当引入了外部自动变量时,blk会变成__NSMallocBlock__
void testBlock3() {
    int a = 10;
    void (^blk)(void) = ^{
        NSLog(@"a:%i",a);
    };
    
    blk();
    NSLog(@"====blk3:%@",blk);
}

#pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"

//4.用__weak修饰之后,变成了__NSStackBlock__
void testBlock4() {
    int a = 10;
    void (__weak ^blk)(void) = ^{
        NSLog(@"a:%i",a);
    };
    
    blk();
    NSLog(@"====blk4:%@",blk);
}

//5.引入静态变量、全局变量或全局静态变量,是__NSGlobalBlock__
int global_var = 1;
static int static_global_var = 2;
void testBlock5() {

    //静态变量
    static int static_var = 3;
    void (^blk)(void) = ^{
        global_var *= 3;
        static_global_var *= 3;
        static_var *= 3;
        NSLog(@"global_var:%i",global_var);
        NSLog(@"static_global_var:%i",static_global_var);
        NSLog(@"static_var:%i",static_var);
    };
    blk();
    NSLog(@"====blk5:%@ static_var:%i",blk,static_var);
}

//6.通过函数返回的block,函数返回时编译器会自动生成复制到堆上的代码 objc_retainBlock,
//  该函数内直接调用了_Block_copy,将Block复制到堆上
Block_t getBlock(int rate){
    return ^(int count){ return rate * count; };
}
void testBlock6(){
    Block_t blk = getBlock(3);
    int count = blk(2);
    NSLog(@"====blk6:%@ returnValue:%i",blk,count);
}

//7.block存放到复数,会自动复制到堆上
NSArray* getBlockList(){
    int a = 10;
    return @[
        ^{NSLog(@"block list 0:%i",a);},
        ^{NSLog(@"block list 1:%i",a);},
    ];
}
void testBlock7(){
    NSArray *blockList = getBlockList();
    typedef void(^blk_t)(void);
    blk_t blk = (blk_t)blockList.firstObject;
    blk();
    NSLog(@"====blk7:%@",blk);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        testBlock1();
        testBlock2();
        testBlock3();
        testBlock4();
        testBlock5();
        testBlock6();
        testBlock7();

    }
    return 0;
}

#pragma clang diagnostic pop

输出:

hello_block[56269:5472036] Hello
hello_block[56269:5472036] ====blk1:<__NSGlobalBlock__: 0x100399050>
hello_block[56269:5472036] hello size:8
hello_block[56269:5472036] p1:8 p2:8
hello_block[56269:5472036] ====blk2:<__NSGlobalBlock__: 0x100399070>
hello_block[56269:5472036] a:10
hello_block[56269:5472036] ====blk3:<__NSMallocBlock__: 0x7fa50ec06440>
hello_block[56269:5472036] a:10
hello_block[56269:5472036] ====blk4:<__NSStackBlock__: 0x7ffeef867f98>
hello_block[56269:5472036] global_var3
hello_block[56269:5472036] static_global_var6
hello_block[56269:5472036] static_var9
hello_block[56269:5472036] ====blk5:<__NSGlobalBlock__: 0x1003990b0> static_var:9
hello_block[56269:5472036] ====blk6:<__NSMallocBlock__: 0x7fa50ef04470> returnValue:6
hello_block[56269:5472036] block list 0:10
hello_block[56269:5472036] ====blk7:<__NSMallocBlock__: 0x7fa50ef04790>