block的本质问题

作者: 目前运行时 | 来源:发表于2018-07-18 15:26 被阅读27次
  • block的基本使用在这里就不说了,
  • 将oc的代码转换成c或者c++代码的命令(比如我转换的main.m文件)
    首先切换到main.m所在的文件位置,然后执行这段命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

如果代码中有__weak
执行这段命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

下面是我执行代码截图:


image.png

下面我们来看下block的本质是什么?

// __block_impl的结构
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
// __main_block_desc_0 什么东西
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
}
// 内部函数调用
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;
    }
};

解释:其中__main_block_impl_0是整个block的函数结构,__block_impl装着:
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
isa 指向对象的指针,flag标示 还有Reserved预留参数,FuncPtr是内部的具体指向的代码。由此可以看出 它的本质是一个oc对象。
__main_block_desc_0 装着:
size_t reserved;
size_t Block_size;
其中reserved预留参数,Block_size是这个block占用内存的大小。
其中

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;
    }
};

这在c++中叫构造函数,相当于在初始化之前进行付值。

  • 下面代码这样写,看看底层怎么编译的
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^myBlcok)(void) = ^{
            NSLog(@"age = %d",age);
        };
        age = 20;
        myBlcok();
    }
    return 0;
}

打印的结果是 10 我们这个毫无疑问,age 这里面是局部变量,block内部参与编译的 具体底层的执行代码如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
    }
    return 0;
}
结构体中是这样的:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到,age直接进行的值传递


image.png

这就是为什么age是10而不是20,因为block在写的时候直接将age参与编译,进行值传递,局部变量。

  • 如果我们使用下面这种情况:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int age = 10;
        static int weight = 10;
        
        void (^myBlcok)(void) = ^{
            NSLog(@"age = %d, weight = %d",age,weight);
        };
        age = 20;
        weight = 20;
        
        myBlcok();
    }
    return 0;
}

可以看到底层的执行的代码是:

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

        int age = 10;
        static int weight = 10;

        void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));
        age = 20;
        weight = 20;

        ((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
    }
    return 0;
}
结构体为:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *weight;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到weight都参与编译了,但是weight与age不同的是weight传递的是地址,而age直接传递的是值,所以weight打印的是20,其实主要是static作用,static一个作用是:分配独立的内存空间,一直存在的 所以block内部直接传递地址,这样才符合我们的逻辑。

  • 下面是这样情况:
int height = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int age = 10;
        static int weight = 10;
        
        void (^myBlcok)(void) = ^{
            NSLog(@"age = %d, weight = %d ,height = %d",age,weight,height);
        };
        age = 20;
        weight = 20;
        height = 30;
        myBlcok();
    }
    return 0;
}

底层的代码为:

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

        int age = 10;
        static int weight = 10;

        void (*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));
        age = 20;
        weight = 20;
        height = 30;
        ((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
    }
    return 0;
}
结构体为:
int height = 10;


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

可以看到,底层并没有编译

image.png
所以得出的结论
全局变量,block不参与编译
局部变量,blcok参与编译
  • block的有参数的情况是:
    如下面的例子:
 void (^myBlock)(int,int) = ^(int a,int b){
            NSLog(@"a = %d -- b = %d",a,b);
        };
        myBlock(10,10);

底层的代码实现为:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*myBlock)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
    }
    return 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的类型,block分为三种类型 分别为:NSGlobalBlockNSStackBlockNSMallocBlock,他们分别对应应用程序的那个区域:
    image.png
    可以看到:NSGlobalBlock 主要对应于 data区:(这块区域主要是用来存储全局变量的)NSStackBlock 主要对应于:栈区(这个区域主要是用来存储局部变量的,系统自动分配内存进行处理,先进后出),NSMallocBlock主要是对应于堆区(主要是一般创建对象,程序员可以进行管理内存分配的区域)。
  • 什么情况是NSGlobalBlock
    代码1如下:
 void (^myBlock)(void) = [^{
            NSLog(@"-------------------------------------");
        } copy];
        myBlock();
        NSLog(@"%@",[myBlock class]);
image.png

代码2如下:

 void (^myBlock)(void) = ^{
            NSLog(@"---------------height = %d----------------------",height);
        };
        myBlock();
        NSLog(@"%@",[myBlock class]);
image.png

可以看到 没有引用auto的block 默认情况下生成的都是 NSGlobalBlock

  • NSGlobalBlock的继承关系 以及他的父类:


    image.png
  • NSStackBlock的产生情况,首先要在编译器的build setting中 搜索 automatic re 将xcode修改为非arc。
    代码1如下:
 int age = 10;
        void (^myBlock)(void) = ^{
            NSLog(@"---------------age = %d----------------------",age);
        };
        myBlock();
        NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);
image.png

可以看到NSStackBlock 是在引用了 auto的情况下 出现的,其中NSStackBlock的继承关系如上图。

  • NSMallocBlock的产生情况:它是由NSStackBlock 的copy产生的操作。
    代码1如下:
int age = 10;
        void (^myBlock)(void) = [^{
            NSLog(@"---------------age = %d----------------------",age);
        }copy];
        myBlock();
        NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);  
image.png

代码2如下:

 int age = 10;
        void (^myBlock)(void) = [^{
           // NSLog(@"---------------age = %d----------------------",age);
        }copy];
        myBlock();
        NSLog(@"%@+++++%@++++++++%@++++++%@",[myBlock class],[[myBlock class] superclass],[[[myBlock class] superclass] superclass],[[[[myBlock class] superclass] superclass] superclass]);
image.png
  • 得出结论:没有引用auto的block默认情况下 产生的是NSGlobalBlock 引用了auto的block 产生的是NSStackBlock,NSStackBlock调用了copy产生的是NSMallocBlock,NSGlobalBlock的copy操作还是NSGlobalBlock
  • 特殊情况说明:
    看如下的代码:
void (^TestBlock)(void);
void test(){
    int weight = 10;
    TestBlock = ^{
        NSLog(@"weight = %d",weight);
    };
    
}

int height = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
        TestBlock();    
}
    return 0;
}
image.png
  • 可以看到并不是我们想要知道的10,它是一个随机的数字,原因是这个blcok是一个NSStackBlock 她在非arc的情况下 作用区域在栈上,test函数执行过,他已经消失,所以他随便执行的操作。解决的办法是进行copy操作,是他的作用区域由栈上变到堆上,也就是变成NSMallocBlock.
  • block的copy操作,什么情况下缠身copy(当前前提是在arc的环境下)
    1.返回是block的block 进行了copy操作。
    代码如下:
typedef void (^TestBlock)(void);
TestBlock test(){
    int age = 3;
    return ^{
        NSLog(@"-------------%d",age);
    };
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        TestBlock testBlock = test();
        NSLog(@"class ++++ %@",[testBlock class]);
    }
    return 0;
}
image.png

解释:按道理将应该是 返回的是NSStackBlock 但是返回的是NSMallocStack原因是:在arc的情况下自动进行了copy了。

  1. 强引用block的情况下,也对block进行了copy操作。
    代码如下:
typedef void (^TestBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 3;
        TestBlock testblock = ^(){
            NSLog(@"age = %d",age);
        };
        testblock();
        NSLog(@"class ++++ %@",[testblock class]);
    }
    return 0;
}
image.png

解释:按道理将应该是 返回的是NSStackBlock 但是返回的是NSMallocStack原因是:在arc的情况下自动进行了copy了。

  1. 还有就是使用了blcok 的cocoaAPI方法中含有 usingBlock作为参数时,例如:
    代码如下:
NSArray *array = [NSArray array];
        [array sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) { 
        }];
  1. block 作为方法参数时:
    代码如下:
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        });
  • 对象类型的auto变量:
  1. 如果block的作用域作用在栈上,那么内部调用外部对象的auto变量的时候不会对外部产生强引用。
    代码1:
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyBlock myBlock;
        
        {
            DGPerson *person = [[DGPerson alloc] init];
            person.age = 20;
            myBlock = ^{
                NSLog(@"age = %d",person.age);
            };
            [person release];
            
            
        }
        NSLog(@"--------------");
    }
    return 0;
}

我们都知道这个block 是NSStackBlock类型的,所以是作用在栈上的,那么即使person对象是强引用的,block也不会对它强引用。


image.png

代码2:如果我们block 进行了copy操作, 那就是把block从栈上copy到堆上,那它就变成了NSMallocBlock ,如下代码:

typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyBlock myBlock;
        
        {
            DGPerson *person = [[DGPerson alloc] init];
            person.age = 20;
            myBlock = [^{
                NSLog(@"age = %d",person.age);
            } copy];
            [person release];
            
            
        }
        NSLog(@"--------------");
        
    }
    return 0;
}

执行结果为:


image.png

可以看到block变成NSMallocBlock 的话,person对象不会释放 需要等到block执行完了才会释放。(执行blcok() 将其释放)。
具体例子证明:
2.如果block作用在堆上,那么block内部调用外部对象的auto变量时候,如果外部对象是请引用的那么block对它产生强引用,如果外部对象是若引用的block会对它产生若引用。
具体例子证明:
代码1:

typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyBlock myBlock;
        
        {
            DGPerson *person = [[DGPerson alloc] init];
            person.age = 20;
            myBlock = ^{
                
                NSLog(@"age = %d",person.age);
            };
            
        }
        NSLog(@"--------------");
    }
    return 0;
}
image.png

可以看到先执行的是打印 后来person对象才销毁(当前前提是在arc的环境下)
说明block对它产生的强引用,如果是若引用 那么person对象应该是先销毁。

  1. 代码二:

typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyBlock myBlock;
        
        {
            DGPerson *person = [[DGPerson alloc] init];
            person.age = 20;
            __weak DGPerson *weakPerson = person;
            myBlock = ^{
                NSLog(@"age = %d",weakPerson.age);
            };
            
        }
        NSLog(@"--------------");
    }
    return 0;
}
image.png

可以看到person对象是先销毁了才执行的打印。
3.具体看下,如果block 从栈上copy到堆上 执行了,我将代码转换成了c++代码(使用命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m):


image.png

可以看到强引用多了copy 和dispose方法。
代码(强引用的):

typedef void(*MyBlock)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  DGPerson *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DGPerson *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  DGPerson *person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_8f3440_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        MyBlock myBlock;

        {
            DGPerson *person = ((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DGPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);

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

        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_8f3440_mi_1);
    }
    return 0;
}

代码(弱引用的):
首先会报这个错误:


image.png

(使用这个命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m)

typedef void(*MyBlock)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  DGPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, DGPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  DGPerson *__weak weakPerson = __cself->weakPerson; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_4d3bc6_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        MyBlock myBlock;

        {
            DGPerson *person = ((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((DGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DGPerson"), sel_registerName("alloc")), sel_registerName("init"));
            ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
            __attribute__((objc_ownership(weak))) DGPerson *weakPerson = person;
            myBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakPerson, 570425344));

        }
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_1p_2n5k1fqd4fjd7bg4vz_38qmc0000gn_T_main_4d3bc6_mi_1);
    }
    return 0;
}

可以看到区别是:


image.png
image.png

copy操作首先调用的_Block_object_assign这个函数,这个函数会根据auto变量的修饰符(__strong 、__weak 、__unsafe__retain)进行产生强引用或者若引用。
如果block从堆上移除,它会内部调用_Block_object_dispose方法,这个方法会堆block进行release操作。

  • __block的作用
    作用:使block内部能够改变auto变量的值,但是__block修改全局变量和static变量。
    将main.m文件转化成c++文件,可以看到底层的代码为:
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

可以看到将__block的转化为对象了,__Block_byref_age_0 *age; 进行了指针的付值,所以能够修改了。
下面看一个这个现象:
代码如下:

struct __Block_byref_age_0 {
    void *__isa;
    struct __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    struct __Block_byref_age_0 *age; // by ref
};



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        void (^myBlock)(void) = ^(){
            age = 20;
            NSLog(@"age = %d",age);
        };
        myBlock();
        
        // 将block 替换成我们写的那个结构体
        struct __main_block_impl_0 *block = (__bridge struct __main_block_impl_0 *)myBlock;
        
        NSLog(@"age 的 address = %p",&age);
}
    return 0;
}

我们分析一下这个age是那个age ,首先可以看到


image.png

也就是说当前的age不是我们的age,那block中的age是哪个呢?我们来分析一下:


image.png
image.png

可以分析得到:age就是__struct_byref_age_0 中的age
另外我们打印一下 看一下结果呢


image.png
可以看到age确实是我们分析的age
  • __block的内存管理问题
    当block作用在栈上时,block不会对__block产生强引用。当block从栈上copy到堆上时,block内部会对__block产生强引用。
    我们来看一个例子和源码:
    代码1:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        NSObject *object = [[NSObject alloc] init];
        
        void (^myBlock)(void) = ^(){
            age = 20;
            NSLog(@"object = %@ ---age = %d",object,age);
        };
        myBlock();
    }
    return 0;
}

其中的一段底层代码为:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);}

由此可见:block从栈copy到堆上,会进行调用copy函数 ,copy函数又调用_Block_object_assign函数,这个函数对__block产生强引用。(值得注意的是:现在说对__block产生强引用还证据不足,暂时这么说着)
当block从堆中移除的时候,会调用dispose方法,这个方法内部会调用_Block_object_dispose函数,这个函数会自动释放__block。
下面来看研究一下__forwarding的作用:
因为底层代码的有这么一句(当我们用__block 修饰age的时候)

(age->__forwarding->age) = 20;

这个__forwarding有什么作用呢?
作用:当我们的blcok在栈上的时候,那么这个__forwarding自然指向的就是栈上的block ,那么当block copy到堆上,那么这个__forwarding指向的就是堆上的block了,可以通过这段代码看出:

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
}

发现__forwarding又是自身的结构体,这样的好处是我们无论什么时候调用age(以此为例子)那么指向都是我们想要的age
下面的一幅图更好的证明了这个:


image.png
  • __block修饰对象
    代码1:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block DGPerson *person = [[DGPerson alloc] init];
        
        void (^myBlock)(void) = ^(){
            person.age = 20;
            NSLog(@"age = %d",person.age);
        };
        myBlock();
    }
    return 0;
}

对应的底层代码为:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

代码2:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        DGPerson *person = [[DGPerson alloc] init];
        __block __weak DGPerson *weakPerson = person;
        void (^myBlock)(void) = ^(){
            person.age = 20;
            NSLog(@"age = %d",weakPerson.age);
        };
        myBlock();
    }
    return 0;
}

对应的底层代码:

struct __Block_byref_weakPerson_0 {
  void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 DGPerson *__weak weakPerson;
};

由此可以看出:__block 在堆上 一直是被强引用的,当它修饰的对象为若引用的时候 ,_Block_object_assign会对对对象产生若引用,反之为强引用,但是block对__block产生的一直都是强引用

  • block的循环引用问题
    下面的代码为:
 DGPerson *person = [[DGPerson alloc] init];
        person.myBlock = ^{
            person.age = 20;
        };
        person.myBlock();
        NSLog(@"-------------");

代码执行结果为:


image.png

可以看到没有执行dealloc方法 ,按道理将我们打括号执行完毕,对象该销毁但是没有销毁那就是循环引用了,为什么 我们将main.m转换成c++代码

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

然后我们知道DGPerson 强引用block,

@interface DGPerson : NSObject

@property (assign, nonatomic) int age;
@property (strong, nonatomic) void(^myBlock)(void);

@end

所以结构图为:


image.png

这样我们的解决办法是__weak ,但是我们把那个线变成弱的呢,我们应该把绿色箭头变成绿色的,因为person 我们需要strong或者copy,好让他作用在堆上 我们可以控制他的生命周期,所以正确的解决办法是:

 DGPerson *person = [[DGPerson alloc] init];
        __weak typeof(person)weakPerspon = person;
        person.myBlock = ^{
            weakPerspon.age = 20;
        };
        NSLog(@"-------------");
image.png

其中还有一个办法就是:把__weak 换成 __unsafe_unretained

DGPerson *person = [[DGPerson alloc] init];
        __unsafe_unretained typeof(person)weakPerspon = person;
        person.myBlock = ^{
            weakPerspon.age = 20;
        };
        NSLog(@"-------------");
image.png

可以看到 也能解决循环引用的问题 ,但是这样做是不安全的,因为__weak
是我们这个对象调用完毕的时候,或者说离开作用域的时候他会自动将对象设置为nil,而__unsafe_unretained 他不会设置为nil,也就是内存已经回收了,但是他还是指着那个内存空间,这样就可能造成野指针,所以不建议使用。
还有我们用__block 也可以解决这个问题

 __block DGPerson *person = [[DGPerson alloc] init];
        person.myBlock = ^{
            NSLog(@"age = %d",person.age);
            person = nil;
        };
        person.myBlock();
        NSLog(@"-------------");

可以看到__block 必须执行myBlock()和将对象设置为nil,这样的话虽然能够解决 但是还是比较麻烦,所以在arc的情况下最好的解决办法就是__weak

  • 在非arc的情况下 解决循环引用问题:(可以不看)
 DGPerson *person = [[DGPerson alloc] init];
        __unsafe_unretained typeof(person) weakPerson = person;
        person.myBlock = [^{
            NSLog(@"age = %d",weakPerson.age);
        } copy];
        person.myBlock();
        [person release];
        NSLog(@"-------------");
image.png

因为mrc 情况都是手动管理内存,所以__unsafe_unretained没事。
完毕!!!!!!!!!!!!!

相关文章

  • 06-OC中block的底层原理

    06-block的本质 在讲解block的底层原理前,我们先抛出如下block相关的问题: block的本质,底层...

  • OC语法 Block

    问题: Block的原理、本质是什么? Block的分类? Block的捕获机制? Blcok内部的内存管理? _...

  • Objective - C block(一)block的底层结构

    (一)Block的常见问题 首先我们先看下面几个问题: block的原理是怎样的?本质是什么? __block的作...

  • Block

    回顾一下在工作中使用block遇到一些问题和新的认识. block本质 block的本质就是C语言的函数指针,本身...

  • ios底层原理-代码块(block)的本质(一)

    问题 1.什么是block,block的本质是什么?2.block的属性修饰词为什么是copy?使用block有哪...

  • ios底层原理-代码块(block)的本质(二)

    问题 1.什么是block,block的本质是什么?2.block的属性修饰词为什么是copy?使用block有哪...

  • block的本质问题

    block的基本使用在这里就不说了, 将oc的代码转换成c或者c++代码的命令(比如我转换的main.m文件)首先...

  • block的本质1

    问题 block的原理是怎样的?本质是什么? __block的作用是什么?有什么使用注意点? block的属性修饰...

  • Block总结

    一、Block的底层结构及本质 (1)block本质: 从代码可以看出,Block的本质就是NSObject. 也...

  • OC底层原理(八):Block

    block是经常使用的一种技术,那么block的本质是什么呢? Block的本质 block本质上也是OC对象,它...

网友评论

    本文标题:block的本质问题

    本文链接:https://www.haomeiwen.com/subject/pecspftx.html