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

面试

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

  • 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 修饰,该参数会自动生成闭包表达式,达到延迟执行的效果,属于性能优化;

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

推荐阅读更多精彩内容