zoukankan      html  css  js  c++  java
  • iOS之Block总结以及内存管理

    block定义

    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    struct Block_layout {
        void *isa;
        int flags;
        int reserved; 
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };

    从上面代码看出,Block_layout就是对block结构体的定义:

    isa指针:指向表明该block类型的类。

    flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。

    reserved:保留变量,我的理解是表示block内部的变量数。

    invoke:函数指针,指向具体的block实现的函数调用地址。

    descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

    variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

    举例,定义一个最简单block 打印hello world:

    int main(int argc, const char * argv[]) {
    
        void (^block)()=^{printf("hello world");};
        block();
        return 0;
    }

    使用clang指令

    clang -rewrite-objc main.m

    得到一个cpp文件,编译后,你就会看到什么是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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
            printf("hello world");
    }
    
    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)};
    int main(int argc, const char * argv[]) {
    
        void (*block)()=((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;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    View Code

    你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。

    再看看值捕获的问题

    int main(int argc, const char * argv[]) {
    
        int a=10;
         //__block int a=10; //__block前缀
        void (^block)()=^{printf("打印a=%d",a);};
        block();
        
        return 0;
    }

    定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会影响到外面的a变量的。

    而加了__block前缀,编译后:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    printf("打印a=%d",(a->__forwarding->a));}
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    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[]) {
    
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    
        void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    View Code

    并不是直接传递a的值了,而是把a的地址(&a)传过去了,所以在block内部便可以修改到外面的变量了。

    isa:isa指针,在Objective-C中,任何对象都有isa指针。block 有三种类型:
    _NSConcreteGlobalBlock 全局静态,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

    #include int main()
    {
        ^{ printf("Hello, World!
    "); } ();
        return 0;
    }

    _NSConcreteStackBlock 保存在栈中,出函数作用域就销毁,例如:

    #include int main()
    {
        char a = 'A';
        ^{ printf("%c
    ",a); } ();
        return 0;
    }

    _NSConcreteMallocBlock 保存在堆中,retainCount == 0销毁

    该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

    void exampleB_addBlockToArray(NSMutableArray *array) {
        char b = 'B';
        [array addObject:^{
                printf("%c
    ", b);
        }];
    }
    void exampleB() {
        NSMutableArray *array = [NSMutableArray array];
        exampleB_addBlockToArray(array);
        void (^block)() = [array objectAtIndex:0];
        block();
    }

    总结一下:

    _NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。

    _NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。

    _NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

    而ARC和MRC中,还略有不同

    题目:下面代码在按钮点击后,在ARC下会发生什么,MRC下呢?为什么?

    @property(nonatomic, assign) void(^block)();
    
    - (void)viewDidLoad {
        [superviewDidLoad];
        int value = 10;
        void(^blockC)() = ^{
            NSLog(@"just a block === %d", value);
        };
    
        NSLog(@"%@", blockC);
        _block = blockC;
    
    }
    
    - (IBAction)action:(id)sender {
        NSLog(@"%@", _block);
    }

    在ARC 打印:

    mytest[25284:7473527] test:<__NSMallocBlock__: 0x60000005f3e0>
    mytest[25284:7473527] NSShadow {0, -1} color = {(null)}

    虽然不会crash,第二个是野指针

    MRC 会打印:test:<__NSStackBlock__: 0x7fff54941a38> 然后crash

    例如:

     NSArray *testArr = @[@"1", @"2"];
        NSLog(@"block is %@", ^{
            NSLog(@"test Arr :%@", testArr);
            
        });//结果:block is <__NSStackBlock__: 0x7fff54f3c808>
        
        void (^TestBlock)(void) = ^{
            NSLog(@"testArr :%@", testArr);
        };
        NSLog(@"block2 is %@", TestBlock);//block2 is <__NSMallocBlock__: 0x600000045e80>
    //其实上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock
    //即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.

    循环引用

      Block的循环引用是比较容易被忽视,原本也是相对比较难检查出来的问题。当然现在苹果在XCode编译的层级就已经做了循环引用的检查,所以这个问题的检查就突然变的没有难度了。

      简单说一下循环引用出现的原理:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏。下面举个例子说明一下:

    @interface ObjTest () {
        NSInteger testValue;
    }
    @property (copy, nonatomic) void (^block)();
    @end
    
    @implement ObjTest
    - (void)function {
        self.block = ^() {
            self.testValue = 100;
        };
    }
    @end

    在这个例子中,ObjTest拥有了一个名字叫block的Block对象;然后在这个Block中,又对ObjTest的一个成员变量testValue进行了赋值。于是就产生了循环引用:ObjTest->block->ObjTest。

      要避免循环引用的关键就在于破坏这个闭合的环。在目前只考虑ARC环境的情况下,笔者所知的只有一种方法可以破坏这个环:在Block内部对拥有者使用弱引用。

    @interface ObjTest () {
        NSInteger testValue;
    }
    @property (copy, nonatomic) void (^block)();
    @end
    
    @implement ObjTest
    - (void)function {
        __weak ObjTest* weakSelf = self;
        self.block = ^() {
            weakSelf.testValue = 100;
        };
    }
    @end

    在单例模式下 Block避免循环引用,如下:

    @interface Singleton : NSObject
    @property (nonatomic, copy) void(^block)();
    + (instancetype)share;
    @end
    
    @implementation Singleton
    + (instancetype)share {
        static Singleton *singleton;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            singleton = [[Singleton alloc] init];
        });
        return singleton;
    }
    @end
    
    //============分割线=================
    //控制器中代码的实现
    
    @implementation NextViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf=self;
        
        void (^blockTest)()=^(){
    //        NSLog(@"print %@", self);//会内存泄漏
            NSLog(@"print %@", weakSelf);
        };
        
        Singleton *singleton = [Singleton share];
        singleton.block = blockTest;
    }
    - (IBAction)btnClick:(UIButton *)sender {
        
        [Singleton share].block();
    }
    
    - (void)dealloc {
        NSLog(@"%s", __FUNCTION__);
    }
    @end

     为什么iOS中系统的block方法可以使用self

    因为:首先循环引用发生的条件就是持有这个block的对象,被block里边加入的对象持有。当然是强引用。
    所以UIView的动画block不会造成循环引用的原因就是,这是个类方法,当前控制器不可能强引用一个类,所以循环无法形成。

    ARC情况下:
    1、如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。

    解决方法:新建一个指针(__weak typeof(Target) weakTarget = Target )指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。
    2、如果用weak修饰Block,该Block就会存放在栈空间。不会出现循环引用问题。MRC情况下用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。
    返回值类型(^block变量名)(形参列表) = ^(形参列表) {};调用Block保存的代码block变量名(实参);默认情况下,,Block内部不能修改外面的局部变量Block内部可以修改使用__block修饰的局部变量

    参考 收藏:https://www.zhihu.com/question/30779258/answer/49492783

    Objective-C中的Block原理

    云端之巅 Objective-C中block的底层原理

    iOS学习之block总结及block内存管理(必看)

    Block总结以及内存管理

  • 相关阅读:
    使用 yo 命令行向导给 SAP UI5 应用添加一个新的视图
    SAP Fiori Elements 应用的 manifest.json 文件运行时如何被解析的
    SAP UI5 标准应用的多语言支持
    微软 Excel 365 里如何设置下拉菜单和自动高亮成指定颜色
    SAP Fiori Elements 应用里的 Title 显示的内容是从哪里来的
    本地开发好的 SAP Fiori Elements 应用,如何部署到 ABAP 服务器上?
    如何在 Cypress 测试代码中屏蔽(Suppress)来自应用代码报出的错误消息
    教你一招:让集群慢节点无处可藏
    应用架构步入“无服务器”时代 Serverless技术迎来新发展
    MySQL数据库事务隔离性的实现
  • 原文地址:https://www.cnblogs.com/dhui69/p/6541324.html
Copyright © 2011-2022 走看看