oc篇-Block底层原理(一)

基本知识点回顾

我们知道按照变量的作用域划分的话,变量可划分为局部变量全局变量,而局部变量又分为自动变量静态变量全局变量分为静态全局变量非静态全局变量

#import <Foundation/Foundation.h>


static int a = 10;
int b = 20;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
        auto int  c = 30; // auto 可省略
        static int d = 40;
        
        NSLog(@"\n全局静态变量a:%d\n全局非静态变量b:%d\n自动变量c:%d\n静态局部变量d:%d\n",
              a,
              b,
              c,
              d);
        
        
    }
    return 0;
}


Block的内存结构

新建一个命令行项目 mian.m

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        void (^myBlock) (void) = ^ {
             NSLog(@"0000");
        };
        
    }
    return 0;
}

通过clang命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

得到c++的代码,找到main函数的实现为以下代码,

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void (*myBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

====去除掉强制类型转换的代码实际就是下面的形式====
int main(int argc, const char * argv[]) {
{

        void (*myBlock) (void) = &__main_block_impl_0(__main_block_func_0,    
                                                    &__main_block_desc_0_DATA));

    }
    return 0;
}

__main_block_impl_0 相关的结构以及相关说明如下

//__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)
   { //这个是C++的结构体 该函数是C++结构体的初始化函数 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//__block_impl
struct __block_impl { // 这是block函数相关的结构体
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//__main_block_desc_0  
static struct __main_block_desc_0 {//这是对__main_block_impl_0结构体的描述信息
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


对于__main_block_impl_0结构体组成,类似于我们平时开发过程中把一些相关的东西封装成一个对象是一个道理的。

再回到main.m函数的实现上:

myBlock也就是保存了__main_block_impl_0地址,__main_block_impl_0初始化函数的第一个参数这里赋值了__main_block_func_0函数其实就是那句NSLog(@"0000");打印,
__main_block_func_0实现如下

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    //为了方便理解我直接写成了 打印读者请根据自己的生成的代码对比着看
    NSLog(@"0000");
 }

而第二个参数就是__main_block_desc_0_DATA就是对block的描述,由于构造函数接受的指针类型,所以这里提供的是__main_block_desc_0_DATA的地址

接下来改造下main.m的代码

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        void (^block) (void) = ^ {
            NSLog(@"0000");
        };
        
        block();
        
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    }
    return 0;
}

输出

__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject

发现block的类型的链是__NSGlobalBlock__ -> __NSGlobalBlock -> NSBlock -> NSObject

所以我们得出以下结论,block的内存结构是一个结构体,并且也是一个oc对象;
内存结构图如下

15346658296514.jpg

Block调用

上面说的都是block的申明,关于block调用就是下面这行代码了


block();

// 转成C++就是下面的形式

 block->FuncPtr(block);

读者可能有疑惑既然block是指向__main_block_impl_0结构体的但是__main_block_impl_0又没有FuncPtr,那怎么可以直接调用呢,应该是block->impl.FuncPtr(block)才对吧,由于__main_block_impl_0是直接拥有了__block_impl的并且处于第一个位置的,所以block的地址也就是impl的地址,所以可以这样写。

Block的类型

block有3种类型

__NSGlobalBlock__ 对应 _NSConcreteGloablBlock
__NSStackBlock__ 对应 _NSConcreteStackBlock
__NSMallocBlock__对应 _NSConcreteMallocBlock;

在内存中存放的位置如图

15346678342585.jpg

接下来验证以下

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)
   { //这个是C++的结构体 该函数是C++结构体的初始化函数 
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

由上面的源码中可以看到一个impl.isa成员,看过我前面的文章的应该知道isa是用来标识该对象是谁的实例的,为了更好的理解block的类型,我们先把项目变成MRC,将targets -> Build Setting -> Object-C Automatic Reference Counting 改为NO

MRC环境

再讲main代码改为

int a  = 10;
static int b  = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        void (^myBlock1) (void) = ^{
            
        };
        
        void (^myBlock2) (void) = ^{
            NSLog(@"----%d",a);
        };
        
        
        void (^myBlock3) (void) = ^{
            NSLog(@"----%d",b);
        };
        
        
        static int c  = 20;
        void (^myBlock4) (void) = ^{
            NSLog(@"----%d",c);
        };
        
        int d  = 20;
        void (^myBlock5) (void) = ^{
              NSLog(@"----%d",d);
        };
        
        
        void (^myBlock6) (void) = ^{
            NSLog(@"----%d--%d",d,a);
        };
        
        
        void (^myBlock7) (void) = [myBlock5 copy];
        
        void (^myBlock8) (void) = [myBlock1 copy];
        
        void (^myBlock9) (void);
        {
            MyPerson *person = [[MyPerson alloc] init];
            myBlock9 = [^ {
                NSLog(@"----%p",person);
            } copy];
            
            [person release];
        }
        
        
        NSLog(@"myBlock1:%@\nmyBlock2:%@\nmyBlock3:%@\nmyBlock4:%@\nmyBlock5:%@\nmyBlock6:%@\nmyBlock7:%@\nmyBlock8:%@\nmyBlock9:%@",
              [myBlock1 class],
              [myBlock2 class],
              [myBlock3 class],
              [myBlock4 class],
              [myBlock5 class],
              [myBlock6 class],
              [myBlock7 class],
              [myBlock8 class],
              [myBlock9 class]
              );
        
    }
    return 0;
}

输出

myBlock1:__NSGlobalBlock__
myBlock2:__NSGlobalBlock__
myBlock3:__NSGlobalBlock__
myBlock4:__NSGlobalBlock__
myBlock5:__NSStackBlock__
myBlock6:__NSStackBlock__
myBlock7:__NSMallocBlock__
myBlock8:__NSGlobalBlock__
myBlock9:__NSMallocBlock__

对于block9:

不使用copy则打印__NSStackBlock__,并且MyPerson正常释放;
使用copy则打印__NSMallocBlock__MyPerson无法正常释放;

得出以下规律:

  1. block内部没有访问auto变量则为__NSGlobalBlock__
  2. 访问了auto变量则为__NSStackBlock__
  3. __NSStackBlock__ copy以后就会成为__NSMallocBlock__
  4. __NSMallocBlock__ copy以后引用计数增加;
  5. __NSGlobalBlock__ copy以后什么也不做
15346693124195.jpg

对于block进行copy操作

15346693288345.jpg

ARC环境

再把项目调回到arc环境看看输出

myBlock1:__NSGlobalBlock__
myBlock2:__NSGlobalBlock__
myBlock3:__NSGlobalBlock__
myBlock4:__NSGlobalBlock__
myBlock5:__NSMallocBlock__
myBlock6:__NSMallocBlock__
myBlock7:__NSMallocBlock__
myBlock8:__NSGlobalBlock__
myBlock9:__NSMallocBlock__

发现有变化的是myBlock5myBlock6,这是因为在ARC环境下,会根据一些特定的场景会自动调用copy方法,规律如下

  1. 赋值给有__strong修饰的指针变量;
  2. COcoa API 中有usingBlock的方法参数时;
  3. block作为GCD API方法入参时
  4. block作为函数返回值时

block作为函数返回值

typedef   void (^myBlock) (void);

myBlock  func() {
    
    int a = 10;
    
    myBlock block =  ^ {
        NSLog(@"---%d",a);
    };
    
    return block;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
     
        myBlock temp  = func();
        
        NSLog(@"%@",[temp class]);
        
        temp();
        

    }
    return 0;
}


读者可以切换是否为arc来观看不同,其他情况无需做说明了 读者自行写demo测试吧

下篇将带来block的值捕获,以及如何修改捕获的值

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容