美文网首页
iOS中block和swift 的闭包的用法探究

iOS中block和swift 的闭包的用法探究

作者: 写代码的小农民 | 来源:发表于2021-01-21 22:34 被阅读0次

    面试

    (参考答案在文章中,也在底部)

    • block的内部原理?
    • swift中闭包表达式的种类?
    • 闭包和block相比有哪些相同点和不同点?
    • 闭包和和闭包表达式的区别?

    iOS Block

    block是封装了函数以及函数调用环境的OC对象;

     NSLog(@"%@",[block class]);
     NSLog(@"%@",[[block class] superclass]);
     NSLog(@"%@",[[[block class] superclass] superclass]);
     NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    2021-01-21 13:17:2 [44007:11115273] __NSMallocBlock__
    2021-01-21 13:17:2 [44007:11115273] __NSMallocBlock
    2021-01-21 13:17:2 [44007:11115273] NSBlock
    2021-01-21 13:17:2 [44007:11115273] NSObject
    
    //简单的block 的实现
     ^{
           NSLog(@"this is a block");
     }();
            
    void (^blcok)() =  ^{
           NSLog(@"this is a block");
     };
    blcok();
    

    __block 中有isa指针(继承于NSObject)、 以及捕获的变量、和封装的方法实现的地址。

    屏幕快照 2021-01-21 下午12.10.20.png

    封装一个block并且分别捕获auto局部变量(函数执行完就会销毁,所以值传递,要在block内部copy)和static局部变量(main函数之前就已经初始化,一直存储在数据段,所以是引用传递)。如果是全局变量则不会捕获(直接访问);

     int age = 10;
     static int height = 10;   
     block = ^{
           // age的值捕获进来(capture)
           NSLog(@"age is %d, height is %d", age, height);
     };
     block();
    

    __test_block_impl_0是一个C++的结构体,是block的底层数据结构,

    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      int age;//捕获变量
      int *height;//捕获变量地址
    //C++析构函数
      __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;//block类型
        impl.Flags = flags;
        impl.FuncPtr = fp; //block 封装的函数地址,就是下面的__test_block_func_0
        Desc = desc;
      }
    };
    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));
        }
    
    static struct __test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        static int height = 10;
        //(void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height风别为block的初始化参数
        block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
        //FuncPtr存储的是block封装的方法地址
       ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)   ((__block_impl *)block);
    
        }
        return 0;
    }
    

    不难发现block 其实是一个包含isa 指针、捕获的局部变量的copy或者地址、以及封装的方法实现的地址的C++结构体对象;凡是block内部访问的局部变量都会捕获到block 内部(Int/NSObject/self)都会捕获。

    1.block的类型

    block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    NSGlobalBlock ( _NSConcreteGlobalBlock )
    NSStackBlock ( _NSConcreteStackBlock )
    NSMallocBlock ( _NSConcreteMallocBlock )

    void (^block1)() = ^{
        NSLog(@"我是__NSGlobalBlock__");
    };
    
    int a = 10;
    void (^block2)() = ^{
        NSLog(@"我是__NSMallocBlock__%d",a);
    };
               
    NSLog(@"%@",[block1 class]);
    NSLog(@"%@",[block2 class]);
    2021-01-21 14:15:11.  __NSGlobalBlock__
    2021-01-21 14:15:11. __NSMallocBlock__
    

    因为__NSStackBlock__是不安全的,所以往往要copy到堆上,变成__NSMallocBlock__,ARC模式下自动copy到堆上;
    被__strong 引用的的block也会自动copy到堆上,系统的参数代use ingblock的也会被copy到堆上。

    2.block的内存管理

    只要是栈上的block都不会捕获变量。
    只要是堆上的block 如果引用了外部的局部变量,会根据局部变量使用__strong 还是用__weak来决定是否强引用;

    如果block被拷贝到堆上
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

    如果block从堆上移除
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量(release)

    typedef void (*Block)(void);
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__strong person;//捕获外部被__strong修饰局部变量,是强引用
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _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) {
    //通过获取block内部的地址引用找到捕获的对象
      Person *__strong person = __cself->person; // bound by copy
    
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
                }
    //block内部的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*/);}
    //block内部的dispose函数
    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; 
            Block block;
    
            {
                Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
                ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
                block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
            }
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_1);
        }
        return 0;
    }
    

    一旦外部的对象类型的auto局部对象被捕获引用计数+1,当block释放的时候引用计数-1;

    Block修改外部变量
    Soultion1:用static修饰或者改成全局变量
    Soultion2:用__block修饰或者改成全局变量
    编译器会将__block变量包装成一个对象,并且被Block强引用:

      __block int age = 10;
      Block block = ^{
          age = 30;
          NSLog(@"age is %d", age);
      };
      block();
    
    typedef void (*Block)(void);
    //__block 修饰的age封装成一个结构体对象,并且存储了age的值;
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;//指向自身
     int __flags;
     int __size;
     int age;
    };
    
    
    oldp
    //malloc生成__Block_byref_age_0对象
    __Block_byref_age_0 *p = malloc(sizeof(struct __Block_byref_age_0))
    
    
    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;
      }
    };
    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;//通过__Block_byref_age_0对象找到age的修改为20
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0, p);
            }
    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*/);
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->age, 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[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            //生成__Block_byref_age_0对象
            __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
            MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_1, &(age.__forwarding->age), block);
        }
        return 0;
    }
    
    

    Block循环引用问题
    1.__block MRC下不会强引用对象所以可以解决循环引用。
    ARC下必须要调用,且在调用的block内置为nil才能解决循环应用,解决的事三者之间的循环引用。

    2.__weak && __strong可以很好的解决两个对象循环引用;
    ARC下block 用strong和copy没有什么区别,因为都会copy到堆上。

    Swift闭包和闭包表达式

    1.闭包表达式

    在swift中可以用func定义一个函数,也可以用闭包表达式定义一个函数:

    func sum(v1:Int, v2:Int) -> Int { v1 + v2 }
    let sum2 = { (v1:Int, v2:Int) -> Int in
        return v1 + v2
    }
    sum(v1: 10, v2: 20)//func定义的函数
    sum2(10, 20)//闭包表达式定义的函数
    
     func sun(param1:Int, param2:Int, test:( Int, Int) -> Int) {
        print(test(param1, param2))
     }
    
     sun(param1:20 , param2:20, test:{
        (v1:Int, v2:Int) -> Int in
        return v1 + v2
     })
    //闭包表达式简写
     sun(param1:20 , param2:20, test:{
        v1, v2 -> Int in
        return v1 + v2
     })
     sun(param1:20 , param2:20, test:{
        v1, v2  in
        return v1 + v2
     })
     sun(param1:20 , param2:20, test:{
        v1, v2  in  v1 + v2
     })
    sun(param1:20 , param2:20, test:{
       $0 + $1
    })
    sun(param1: 2, param2: 3, test: +)
    

    尾随闭包

    最后一个参数是闭包表达式的时候,可以独立于前面的参数,写在函数参数的外面的闭包表达式,使结构看起来更加清晰整洁,属于结构优化。默认闭包表达式作为参数的时候采用尾随闭包的形式。

    sun(param1: 12, param2: 13) { (v1:Int, v2:Int) -> Int in
        return v1 + v2
     }
    //尾随闭包表达式
    sun(param1: 12, param2: 13) { v1, v2 in
         v1 + v2
     }
    //尾随闭包表达式简写
    sun(param1: 20, param2: 12) { return $0 + $1 }
    //尾随闭包表达式简写
    sun(param1: 10, param2: 12) { $0 + $1 }
    sun(param1: 10, param2: 12) { $0 + $1 }
    
    sun(param1: 2, param2: 3, test: +)
    

    自动闭包

    顾名思义根据传入的参数,自动生成闭包;延迟执行,属于性能优化,除此之外也属于结构优化,调用者无需关心参数类型,用 @autoclosure修饰。
    @autoclosure只支持()->T的格式的参数,并且不仅仅只支持最后一个参数.
    系统的合并运算符??属于自动闭包。

    public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
    

    对比一下尾随闭包和自动闭包,在()->T的参数格式下,自动闭包非常简洁;

    func test(v1:Int, v2:()-> Int) -> Int {
        return v1 > 0 ? v1 : v2()
    }
    test(v1: -10) { 10 }//尾随闭包
    test(v1: 1, v2: {() -> Int in return 10 })//闭包表达式
    test(v1: 15, v2: {10})//闭包表达式简写
    
    //自动闭包
    func test(v1:Int, v2: @autoclosure ()-> Int) -> Int {
        return v1 > 0 ? v1 : v2()
    }
    //参数20会自动封装成一个闭包
    test(v1: -1, v2: 20)
    

    自动闭包有性能的优越性:假如other() 是一个耗性能的操作,就体现出了自动闭包的优越性:简洁且高效。other()的值会被封装成一个闭包,仅当v1 < 0的时候才会执行。

    func other() -> Int {
        var i = 0
        for _ in 0...10000 {
            i += 1
        }
        return I
    }
    
    test(v1: 1, v2:other())
    

    逃逸闭包

    2.闭包

    闭包和闭包表达式并不是一个东西,闭包表达式是函数的另外一种定义,而闭包是一个函数和它所捕获的变量/常量环境组合起来,称为闭包:
    一般指定义在函数内部的函数;一般它捕获的是外层函数的局部变量/常量:

    typealias Test = (Int, Int) -> Int
    func creat() -> Test {
        var num = 3
        //定义在函数内部
        func add(v1:Int, v2:Int) -> Int{
            //捕获外层函数的局部变量
            num += 1
            return (v1 + v2) * num
        }
        //除了func之外也可以通过闭包表达式定义函数
        //let add = {(v1:Int, v2:Int) in return num + v2 + v1 }
        return add//返回一个闭包:一个函数或者闭包表达式和它所捕获的变量组成的环境;
    }
    let closure = creat()//获取一个闭包
    closure(15, 20)
    

    swift 中的闭包是一种提供了方法和局部变量的数据结构,可以简单把闭包看作是一个类,类的成员变量看作是需要捕获的局部变量,方法比作类的方法。闭包包含24个字节,前8个字节存储闭包的地址,后8个字节存储引用计数,接下来存储捕获的局部变量在堆上的内存地址。

    3.闭包的内存管理

    面试参考

    • block的内部原理?

    block 是一个封装了函数调用和局部变量的运行环境的结构体对象;

    • 闭包和block相比有哪些相同点和不同点?

    本来两个东西没有可比性,但是面试官非要这么问;
    相同点:都可以作为参数,都可以捕获局部变量;
    从使用上来相比较而言,闭包更灵活,有尾随闭包,自动闭包可供选择,简洁且高效;
    从内存管理上来说,前者捕获的是内存地址,后者局部变量是对象时捕获的是地址,当是int,string 时是copy。

    • 闭包和和闭包表达式的区别?

    闭包表达式是:函数的另外一种表现形式,为了实现简介和高效,有不同的表现形式,例如自动闭包,尾随闭包;
    闭包是:封装在函数中的函数或者闭包表达式,且捕获了局部变量的运行环境。它运用了闭包表达式,但闭包表达式不是闭包。

    • Swift中闭包表达式的种类?

    尾随闭包:当且仅当最后一个参数是闭包表达式的时候,可以独立于参数之外,属于结构优化;
    自动闭包:当且仅当参数类型是()->T的时候,且用@autoclosure 修饰,该参数会自动生成闭包表达式,达到延迟执行的效果,属于性能优化;

    相关文章

      网友评论

          本文标题:iOS中block和swift 的闭包的用法探究

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