• iOS Block 最全解答


    什么是Block

    Block 又称为“块” 或 “代码块”,作用是用来保存代码,保存在其内部的代码块 如果Block不被调用 这段代码就不会执行

    在OC中Block的基本格式是这样的

    返回值类型  (^block名)  (参数类型 和 数量) = ^(形参 和 数量){   
        //code 
    };

    Block的本质

    Block的本质上也是一个OC对象 它内部也有个isa指针

    Block是封装了函数调用以及函数调用环境(比如参数和返回值)的OC对象

    Block是封装了函数及其上下文的OC对象

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

    如果我们定义了上面的一个最简单的Block,那么我们查看底层代码会是什么样的呢?

    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 __block_impl {
        void *isa;//Block的类型
        int Flags;
        int Reserved;
        void *FuncPtr; //封装代码块的地址
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      // 构造函数(类似于OC的init方法),返回结构体对象
      __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_impl 包含了isa指针和代码块的执行地址  __main_block_desc_0 包含了block的大小。和一个相关Block的构造函数

    下面就是main函数中编译过的底层代码

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            // 定义block变量
            void (*block)(void) = &__main_block_impl_0(
                                                       __main_block_func_0,
                                                       &__main_block_desc_0_DATA
                                                       );
    
            // 执行block内部的代码
            block->FuncPtr(block);
        }
        return 0;
    }

    通过main函数 我们可以看到定义了一个block变量 并且执行了它。

    局部变量和Block

    那么如果我们的block需要一个参数呢 如下的Block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            block = ^{
                // age的值捕获进来(capture)
                NSLog(@"age is %d, ", age);
            };
            age = 20;
            block();
        }
        return 0;              
    }

    那么clang编译后的底层代码是怎么样的呢?

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

    可以看到在block内部多了一个成员变量_age 并且在构造此Block的时候 会把传进去的age直接赋值给_age即_age = age; 即block内部的_age = 10;初始化完成后 里面的age和外面的age没有关系了,所以外面的无论怎么改变,都不影响里面的。所以会打印10.

    在我们OC中 局部变量可以分为自动变量 关键字为auto(值传递 可以省略 默认的 离开自己的作用域就会被销毁) 和 静态变量 关键字 static(指针传递)

    auto变量 会被捕获到block内部 而且传递方式为值传递 相当于Block在捕获这个变量的时候 直接传递的这个变量的值 所以这种变量被捕获后 在Block外部改变 不会影响Block内部的执行 就是上面的那种情况,如果我们再添加一个static局部变量呢?

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // auto:自动变量,离开作用域就销毁
            auto int age = 10;
            static int height = 10;
    
            void (^block)(void) = ^{
                // age的值捕获进来(capture)
                NSLog(@"age is %d, height is %d", age, height); // 10  20
            };
    
            age = 20;
            height = 20;
    
            block();
    
        }
        return 0;
    }

    我们编译后的结果是:

    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      int age;
      int *height;
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    //定义的时候
    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
    //执行的时候
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
      int *height = __cself->height; // bound by copy


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
        }

     

    可以看到我们Block内部有一个age变量 和 *height的成员变量 定义的时候 age传的是值 而height传递的是指针地址,所以执行的时候age10 height20

    在局部变量中 auto局部变量出了作用域就会销毁 为了避免执行block时 变量不存在 所以auto变量在Block内部被直接赋值为当前值。而static变量一直存在于内存中不必有此担忧,所以存储的是变量指针,可以随时取出最新值。

    全局变量和Block

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

    上面代码Block的被编译后的源码是什么样的呢?

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      // 构造函数(类似于OC的init方法),返回结构体对象
      __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 __test_block_func_0(struct __test_block_impl_0 *__cself) {
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age_, height_);
        }

    可以看到如果是全局变量 Block使用时不用捕获 直接访问 所以得到的也全部都是最新值。

    值得注意的是self这个变量也是局部变量 也会被block捕获,因为在OC中所有的方法编译后都是自带两个参数 一个是实例对象 一个是SEL _cmd

    比如

    - (instancetype)initWithName:(NSString *)name
    {
        if (self = [super init]) {
            self.name = name;
        }
        return self;
    }

    MJPerson类中的这个方法 会被编译成

    static instancetype _I_MJPerson_initWithName_(MJPerson * self, SEL _cmd, NSString *name) {
        if (self = ((MJPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJPerson"))}, sel_registerName("init"))) {
            ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
        }
        return self;
    }

    可以看到有两个默认的参数MJPerson * self, SEL _cmd 在OC中参数也为局部变量,所以Block中如果用到self,self也会被捕获。且Block内部的self属性 指向我们传进去的self

    所以 我们也能理解为什么会造成循环引用了吧。

    Block的类型

    void (^block)(void) = ^{
            NSLog(@"Hello");
        };
        
        NSLog(@"%@", [block class]);
        NSLog(@"%@", [[block class] superclass]);
        NSLog(@"%@", [[[block class] superclass] superclass]);
        NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
    2020-11-09 22:24:30.042560+0800 Interview01-Block的本质[1489:30598] __NSGlobalBlock__
    2020-11-09 22:24:30.043005+0800 Interview01-Block的本质[1489:30598] __NSGlobalBlock
    2020-11-09 22:24:30.043103+0800 Interview01-Block的本质[1489:30598] NSBlock
    2020-11-09 22:24:30.043163+0800 Interview01-Block的本质[1489:30598] NSObject

    很神奇 block的最终的父类竟然是NSObject 也从侧面说明了Block的本质是一个对象

    其实Block分为三种类型

    __NSGlobalBlock__(_NSConcreteGlobalBlock)(存放在数据区) (不访问auto变量的block 即便是访问了static局部变量 或者全局变量)

    __NSStackBlock__(_NSConcreteStackBlock)(存放在栈区 系统管理内存)(访问了auto变量)

    __NSMallocBlock__(_NSConcreteMallocBlock) (存放在堆区 程序员管理内存) (__NSStackBlock__调用了copy)

    应用程序的内存分配:

    程序区域 .text区(代码段)  数据区域(.data区)【一般存放全局变量】   堆区【alloc出来的对象 动态分配内存 需要程序员申请内存 也需要程序员管理内存】 栈区【存放局部变量 系统自动分配内存 自动销毁内存】

    其中_NSConcreteGlobalBlock 存放在数据区域   _NSConcreteStackBlock 存放在栈区  _NSConcreteMallocBlock存放在堆区

    如果我们的访问了auto的Block 不使用copy进行修饰的话 也就是单纯的使用__NSStaticBlock__会出现什么问题呢?

    #import <Foundation/Foundation.h>
    #import "MJPerson.h"
    
    void (^block)(void);
    void test2()
    {
        
        // NSStackBlock
        int age = 10;
        block = ^{
            NSLog(@"block---------%d", age);
        };
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            test2();
            block();
        }
        return 0;
    }

    上面就是直接使用栈Block 当我们执行了test2()这个函数后 由于是系统自动管理内存 这个Block会被释放 那么他内部的__block_impl impl,__test_block_desc_0  捕获的变量都会被释放 我们在执行block的时候 会发现age是乱码。

    这也是为什么我们使用copy让栈Block变成堆Block的原因,变成堆Block后捕获的变量也会存储在堆区,只要我们不释放,就不会消失。虽然增加了我们管理内存的难度,但是也避免了调用时,数据释放的情况。

    Block的copy

    _NSConcreteStackBlock copy 变成 _NSConcreteMallocBlock

    _NSConcreteGlobalBlock copy 还是全局block 什么也不做

    _NSConcreteMallocBlock copy 引用计数加1

    在ARC环境下 编译器会根据情况自动将栈上的Block复制到堆上 相当于对栈上的Block进行copy操作。

    1.Blcok 作为函数返回值的时候 会自动copy

    2.将Block赋值给__strong强指针的时候 也会自动做copy操作

    3.Block作为GCD的参数时 也会被copy到堆上

    4.Foundation框架下 block作为参数且方法名含有usingBlock时 会被自动copy

    Block捕捉对象的auto变量

    比如 我们有一个Person类,其有一个属性age, 我们在Block里访问了这个age属性 如果在ARC的情况下 这个Block会被拷贝到堆上 而且Block会对这个对象有一个强引用 即Block不被释放Person对象也释放不了 在MRC的情况下由于Block不会拷贝到堆上 Block还是一个栈Block 所以不会对Person对象有retain操作,Person可先于Block释放。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                MJPerson *person = [[MJPerson alloc] init];
                person.age = 10;
                
    //            __weak MJPerson *weakPerson = person;
                int age = 10;
                block = ^{
                    NSLog(@"---------%d", person.age);
                };
            }
            
            NSLog(@"------");
        }
        return 0;
    }

    我们一起 看看这个Block的结构吧

    //Block 底层结构
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      MJPerson *__strong person; //捕获的对象 可以看出是一个强引用
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    //可以看到相比于捕获普通变量 捕获对象的变量多了两个函数 copy函数和dispose函数
    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};
    
    //copy函数实现 这个函数会根据外面的指针类型判断是做强引用还是弱引用
    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*/);}
    //dispose函数实现 执行这个函数会断开block对捕获对象的引用关系
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

    栈Block在copy到堆上的时候 会调用copy函数 copy函数会调用_Block_object_assign这个函数 这个函数会根据要捕获变量的指针类型 来确定是对捕获变量是强引用还是弱引用。

    堆上的Block销毁的时候会调用dispose函数 dispose函数执行后 会断开对捕获变量的引用关系 也就是捕获对象变量Block内部多出来的这两个函数是用来管理内存的。下面我们会降到__block修饰的对象 他们被封装成特定对象的时候相较于普通变量也会多出来这两个函数

    总结来说 栈Block不会对对象有强引用或者retain操作 而堆上的Block会对对象进行强引用或者retain操作。

    总结:

    只要Block在栈上 不管是ARC 还是 MRC 都不会对捕获的auto变量进行强引用或者retain操作

    如果Block被拷贝到堆上:

    1.会调用block内部的copy函数 在block

    2.copy函数内部调用_Block_object_assign函数 这个函数会根据auto变量的修饰符(__strong __weak)作出相应的操作 是强引用还是弱引用

    如果Block从堆上移除 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 这个函数会自动断开引用的auto变量(断开这个引用) 相当于release

    block内部的copy函数和dispose函数 只会在捕获对象auto变量的时候才有(因为对象需要内存管理) 捕获简单的数据变量比如Int的时候 是没有的

    Block修改变量

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            block = ^{
                // age = 20;
                NSLog(@"age is %d, ", age);
            };
            block();
        }
        return 0;              
    }

    我相信大家都知道age = 20 这行代码是报错的,也就是说在Block内部不能修改这个age变量 那么是为什么呢?我们可以从本质上入手

    //block
    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      int age;
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    //执行的时候
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
    }
    
    //main函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            int age = 10;
            // 定义block变量
            void (*block)(void) = &__main_block_impl_0(
                                                       __main_block_func_0,
                                                       &__main_block_desc_0_DATA
                                                       ,age);
    
            // 执行block内部的代码
            block->FuncPtr(block);
        }
        return 0;
    }

    可以看到执行时候main函数中的age根本不在block执行的代码块内,也就是在Block方法的作用域中是访问不到main函数中的age的。main函数和Block的代码块是两个独立的函数

    变量不能互相访问。Block内部的age和main函数的age地址都不一样的。不能访问就不能改喽。(在另外一个函数中修改另外一个函数中声明的变量)

    而如果我们把变量改成 staic int age = 10 内不就可以修改了,如果你认真的读了这边文章,你应该知道static局部变量被捕获的是指向变量的指针,如果在block内部修改,相当于访问的是同一片地址,所以可以修改。全局变量也是能改的 因为block不会捕获全局变量 无论在哪修改 都可以。

    但是我们不可能总是使用全局变量或者static修饰的变量 因为他们总是在内存中 不易被销毁,我们怎么让auto变量也能在Block内部修改呢,其实我们可以使用__block  来修饰这个变量

    而且__block 只能修饰auto变量 那么为什么使用__block修饰后就能改变呢 其实是被修饰的变量被捕获后 变成了一个对象

    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, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    //main函数中__Block_byref_age_0的实现 可以看到__forwarding 指向的就是自己这个对象的地址 相当于__block int age = 10 被包装成了这个
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    //main函数中 block的定义
    block = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, p, &age, 570425344));
    //执行代码的Block
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
        (age->__forwarding->age) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
        
    }

    可以看到捕获的age 变成了一个 __Block_byref_age_0类型的对象 Blcok内部有个指向这个对象的指针 而这个对象拥有age变量(而且这个对象拥有的内存地址和我们声明的age的内存地址是一样的) 被执行Block的时候 通过被包装对象的__forwarding对象 访问到age 进行修改

     __block的内存管理

    当block在栈上的时候 并不会对__block变量产生强引用(ARC)或者retain操作(MRC)

    当block被拷贝到堆上时:

    1.会调用Block内部copy函数 这个函数内部会调用_Block_object_assign函数

    2._Block_object_assign函数会对__block变量形成强引用(retain) (__block修饰的变量也会被拷贝到堆上)

    3.只要是被__block修饰的变量 copy后都是强引用  如果没被__block修饰的对象 会根据对象的修饰符来确定是强引用还是弱引用

    当Block从堆中移除的时候:

    1.会调用Block内部的dispose函数 这个函数内部会调用_Block_object_dispose函数

    2._Block_object_dispose 这个函数会断开对__block修饰的变量的强引用(realse)

    block内部使用了被__block修饰的对象的话 那么block内部的 Desc 描述指针也会多两个函数 这两个函数就是用于内存管理的copy函数和dispose函数

    __block修饰对象类型

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block MJPerson *person = [[MJPerson alloc] init];
            
            MJBlock block = ^{
                NSLog(@"%p", person);
            };
            block();
        }
        return 0;
    }

    底层代码

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_person_0 *person; // Block内部对转化后的person对象就是强引用和外部修饰符无关
      __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;
      }
    };
    
    
    struct __Block_byref_person_0 {
      void *__isa; // 8
    __Block_byref_person_0 *__forwarding; // 8
     int __flags; // 4
     int __size; // 4
     void (*__Block_byref_id_object_copy)(void*, void*); // 8
     void (*__Block_byref_id_object_dispose)(void*); // 8
     MJPerson *__strong person; //指向我们外部的person 强引用还是弱引用 取决于 外部person的修饰符
    };

    可以看到和__block修饰的普通变量差不多 但是包装成的对象多了两个函数 copy函数和dispose函数 这两个和Block描述中的copy函数和dispose函数还不一样,当Block拷贝的到堆上的时候 被__block修饰的对象(person)转化的对象(__Block_byref_person_0)也会被拷贝到堆上,这个时候会触发被包装对象的copy函数 而这个函数会根据外部__block修饰对象(person)的引用计数修饰符来决定对外部对象(person)是强引用还是弱引用。当Block从堆中移除的时候 会调用内部的dispose函数 这个函数会断开对转化对象(__Block_byref_person_0)的引用关系 如果计数为0 就直接释放 释放后会调用自己的dispose函数 对自己内部对象(MJPerson) 断开引用关系 如果此时MjPerson的引用计数为0 就会被释放掉。(转化对象持有的person和我们外部声明的person对象有同一片内存地址 是同一个对象)

    注意:上面的分析 都是ARC的情况下。如果是MRC转化对象(__Block_byref_person_0) 对外部对象(person) 一直都是弱引用 不管是什么情况。这也是为什么在MRC下我们使用__block来解决循环引用的原因

    Block与循环引用

    循环引用的原因就是互相引用 导致双方都不能释放 造成内存泄漏 block内部使用实例对象 如果实例对象的修饰符是强引用 那么Block在拷贝到堆上时内部对实例对象也会产生一个强引用 如果这个实例对象再持有了这个block 那么一定会产生循环引用的。

    解决循环引用

    1.在ARC的情况下 我们常常使用__weak __unsafe__unretained来解决

    我们使用__weak 和 __unsafe__unretained 都有一个共同的作用 就是使Block持有的对象指向我们声明的对象的指针式弱引用,那么我们外部指向实例对象的指针一旦被释放 实例对象就会被释放 那么Block就会被释放 从而解决循环引用。区别就是如果我们使用的是__weak 一旦我们的实例对象被释放,block内部持有的指向我们实例对象的指针会被指nil 不会产生野指针,但是__unsafe__unretained 不会有这一步操作 所有如果使用__unsafe__unretained block内部持有的指向我们实例对象的指针会还会指向我们已经释放的实例对象的地址 产生野指针。这也是它不安全的原因。所以我们一般使用__weak来解决循环引用

    2.在MRC的情况下 我们使用__unsafe__unretained __block来解决 因为不支持weak指针

    这两个都可以使Block内部的指向我们实例对象的指针进行retain操作

    1.Block的原理是怎样的?本质是什么?

    Block是封装了函数调用以及函数调用环境的OC对象 本质上也是一个OC对象

    Block内部包含了一个__block_imp1 这个指针内部包含了一个isa指针(也证明了本质是个对象) 和 block内部封装的代码块的执行地址 以及捕获的变量的指针

    和一个__main_block_desc_0 里面包含了block的大小等 如果内部使用了实例对象或者__block修饰的变量 会包含一个copy函数和dispose函数.

    2.__block的作用是什么?有什么使用注意点?

    被__block修饰的变量 在block内部可以被改变,因为在block内部被__block修饰的变量会被包装成一个__Block_byref_xxx_0的对象,这个对象内部有一个指向我们声明的变量的指针 指向的地址和我们声明的变量是同一个地址,修改的时候也是通过这个对象拿到指针做修改。block内部对这个对象是强引用的。而如果被修饰的变量是实例对象 那么这个对象内部会添加一个copy函数和dispose函数,在被copy到堆上时,这个对象的copy函数会根据被修饰实例对象的关键字来确定对实例对象是强引用还是弱引用。再block释放的时候,会调用dispose函数 来断开对实例对象的引用关系。需要注意的是__block只能修饰实例对象 还有一些内存管理问题 比如说修饰实例对象且该实例对象拥有该Block 会造成循环引用。而且在MRC下使用__block修饰的实例对象 不会被__Block_byref_xxx_0的对象retain操作。对象可能会被提前释放。

    3.Block的属性修饰词为什么是copy? 使用Block有哪些注意?

    如果block不被copy 就是存储在栈空间上,我们就控制不了block的生命周期,可能我们使用的时候已经被释放了或者我们使用的时候 其内部捕获的变量已经释放了 导致程序错误。而拷贝到堆上,我们可以方便的控制其生命周期。虽然增加了管理内存的一些成本。但是可以减少错误。在ARC的情况下 如果有一个强引用指向Block 内部也会copy到堆上。使用strong也行。但是我们习惯使用copy。 需要注意的是不能引起循环引用。我们可以使用__weak来避免这个问题

    4.Block修改NSMutableArray 需不需要添加__block?

    不需要,如果是对数组进行增删改查 我们不需要对其添加__block 因为不是改变数组的指针指向 只是操作数组。添加__block反而会增加开销。

    在使用clang 转换OC为C++时 可能会遇到以下问题

    cannot create __weak reference in file using manual reference

    解决方法

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

  • 相关阅读:
    【leetcode】11. 盛最多水的容器
    【leetcode】8. 字符串转换整数 (atoi)
    【leetcode】6. Z 字形变换
    【leetcode】5. 最长回文子串
    【leetcode】LCP 19. 秋叶收藏集
    删除第一个节点问题
    问一个大学学习计算机这门专业的问题
    Struts文件上传页面上传后显示“连接已重置”
    2013-12-6 思杨没吃饱 饿醒了
    2013-12-7 snoopy乐园中的思杨
  • 原文地址:https://www.cnblogs.com/huanying2000/p/13950221.html
Copyright © 2020-2023  润新知