美文网首页Objective-C
iOS:Block(一)

iOS:Block(一)

作者: 码小菜 | 来源:发表于2020-01-16 15:25 被阅读0次
风景

目录
一,本质
二,变量捕获
三,类型
四,对象类型的auto变量

一,本质

1,实例代码

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

2,底层代码(用clang进行转换)

  • main
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        // 创建(调用构造函数)
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        // 调用(执行FuncPtr指针指向的函数)
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  • __main_block_impl_0
// 底层结构
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    // 构造函数(第一个参数是__main_block_func_0,第二个参数是__main_block_desc_0)
    __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;
    }
};

struct __block_impl {
    void *isa;
    int Flags; 
    int Reserved;
    void *FuncPtr; // 指向__main_block_func_0
};

// 描述信息
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)};
  • __main_block_func_0
// block中需要执行的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_0d_7m56tzw16qs_vsxqtxhnsq2m1myhmv_T_main_887410_mi_0);
}

3,结构图

结构图

4,结论

  • block的本质是结构体
  • block也是OC对象,因为它内部有isa指针
二,变量捕获

1,实例代码

// 全局变量
int age3 = 3;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // auto变量(auto可以省略)
        auto int age1 = 1;
        // static变量
        static int age2 = 2;
        void(^block)(void) = ^{
            NSLog(@"%d---%d---%d", age1, age2, age3);
        };
        age1 = 4;
        age2 = 5;
        age3 = 6;
        block();
    }
    return 0;
}

// 打印
1---5---6

2,底层代码

  • main
int age3 = 3;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        auto int age1 = 1;
        static int age2 = 2;
        // age1传值,age2传地址
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age1, &age2));
        age1 = 4;
        age2 = 5;
        age3 = 6;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  • __main_block_impl_0
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int age1; // 保存外部age1的值
    int *age2; // 保存外部age2的地址
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age1, int *_age2, int flags=0) : age1(_age1), age2(_age2) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 取出age1的值
    int age1 = __cself->age1; // bound by copy
    // 取出age2的地址
    int *age2 = __cself->age2; // bound by copy
    // *age2取出地址中的值,age3直接访问
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_0d_7m56tzw16qs_vsxqtxhnsq2m1myhmv_T_main_10171a_mi_0, age1, (*age2), age3);
}

3,结论

变量类型 是否捕获到block内部 访问方式
auto(局部) YES 值传递
static(局部) YES 指针传递
全局 NO 直接访问
  • 局部变量和全局变量

由上面底层代码可知
1>age1和age2是定义在main函数里面的,而在__main_block_func_0函数中访问它们,这种跨函数访问必须先捕获,先把变量保存到block中,在访问时再从block中取出
2>age3是定义在所有函数之外的,任何函数都可以访问,不用捕获

  • auto变量和static变量

1>auto变量超出作用域就会销毁,block可能在其销毁后才调用,为了保证能访问到该变量,只能先把变量值捕获进来
2>static变量超出作用域不会销毁,可以直接捕获变量地址,在访问时用变量地址取值即可

4,注意点

self是局部变量,会被捕获到block内部

  • 实例代码
@implementation Person
- (void)eat {
    void(^block)(void) = ^{
        NSLog(@"%@", self);
    };
    block();
}
@end
  • 底层代码
// self是作为参数传递进来的,并不是全局变量
static void _I_Person_eat(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__eat_block_impl_0((void *)__Person__eat_block_func_0, &__Person__eat_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

struct __Person__eat_block_impl_0 {
    struct __block_impl impl;
    struct __Person__eat_block_desc_0* Desc;
    Person *self; // 指向外部self
    __Person__eat_block_impl_0(void *fp, struct __Person__eat_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
三,类型

1,三种类型

  • 实例代码
// MRC环境
void(^block1)(void) = ^{
    NSLog(@"hello block!");
};
NSLog(@"block1---%@", [block1 class]);

auto int age = 1;
void(^block2)(void) = ^{
    NSLog(@"%d", age);
};
NSLog(@"block2---%@", [block2 class]);

void(^block3)(void) = [^{
    NSLog(@"%d", age);
} copy];
NSLog(@"block3---%@", [block3 class]);

// 打印
block1---__NSGlobalBlock__
block2---__NSStackBlock__
block3---__NSMallocBlock__
  • 结论

NSGlobalBlock:没有访问auto变量
NSStackBlock:访问了auto变量
NSMallocBlockNSStackBlock调用copy

2,copy操作

block类型 内存区域 释放时刻 copy结果
NSGlobalBlock 全局静态区 程序结束系统释放 什么也不做
NSStackBlock 超出作用域系统释放 从栈复制到堆
NSMallocBlock 程序员手动释放 引用计数增加

⚠️注意⚠️:NSStackBlock超出作用域就会释放,如果在作用域外使用的话会crash,所以需要copy到堆上,这也是block要用copy修饰的原因,不过ARC环境下系统会自动copy到堆上,所以用strong修饰也可以

3,ARC环境下系统自动将栈上的block复制到堆上的情况

  • 作为函数返回值时
typedef void(^Block)(void);

Block test() {
    int age = 1;
    return ^{
        NSLog(@"%d", age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block = test();
        NSLog(@"%@", [block class]);
    }
    return 0;
}

// 打印
__NSMallocBlock__
  • 赋值给__strong指针时
typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 1;
        // 默认就是__strong
        __strong Block block = ^{
            NSLog(@"%d", age);
        };
        NSLog(@"%@", [block class]);
    }
    return 0;
}

// 打印
__NSMallocBlock__
  • 作为Cocoa API中方法名含有usingBlock的方法参数时
NSArray *array = [NSArray new];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    // 此block为NSMallocBlock
}];
  • 作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 此block为NSMallocBlock
});
四,对象类型的auto变量

1,栈上的block不会强引用auto变量

  • 实例代码
// MRC环境
typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        Block block = ^{
            NSLog(@"%@", person);
        };
        [person release];
        NSLog(@"block未释放:%@", [block class]);
    }
    return 0;
}

// 打印
Person dealloc
block未释放:__NSStackBlock__
  • 底层代码
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *person; // 指向外部person
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 分析

block捕获了person对象,但block还未释放person就已经销毁了,所以blockperson没有产生强引用

2,堆上的block会强引用auto变量

  • 实例代码
typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [Person new];
            block = ^{
                NSLog(@"%@", person);
            };
        }
        NSLog(@"block未释放:%@", [block class]);
    }
    return 0;
}

// 打印
block未释放:__NSMallocBlock__
Person dealloc
  • 底层代码
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__strong person; // 指向外部person
    __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;
    }
};
  • 分析

person对象超出作用域并没有立即销毁,而是在block释放之后才销毁,所以blockperson产生了强引用

3,如果用__weak修饰auto变量,堆上的blockauto变量就是弱引用了

  • 实例代码
typedef void(^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [Person new];
            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"%@", weakPerson);
            };
        }
        NSLog(@"block未释放:%@", [block class]);
    }
    return 0;
}

// 打印
Person dealloc
block未释放:__NSMallocBlock__
  • 底层代码
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *__weak weakPerson; // 指向外部weakPerson
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 分析

person对象超出作用域就立即销毁了,而block还没有释放,所以blockperson是弱引用

4,copydispose

  • 底层代码
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    // 指向__main_block_copy_0
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    // 指向__main_block_dispose_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};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    // 强/弱引用person
    _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) {
    // 释放person
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
  • 分析

block捕获的auto变量如果是对象类型的,在__main_block_desc_0函数中会多两个指针,在底层代码中会多两个函数,用来管理该对象的内存

1>__main_block_copy_0:当block复制到堆上时会通过copy指针调用此函数,此函数会对auto变量产生强/弱引用
2>__main_block_dispose_0:当block从堆上移除时会通过dispose指针调用此函数,此函数会释放引用的auto变量

5,实例练习

  • 实例一
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [Person new];
    __weak typeof(person) weakPerson = person;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"block1---%@", weakPerson);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"block2---%@", person);
        });
    });
    NSLog(@"person作用域结束");
}

// 打印
14:30:21 person作用域结束
14:30:22 block1---<Person: 0x600002774990>
14:30:24 block2---<Person: 0x600002774990>
14:30:24 Person dealloc
  • 实例二
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [Person new];
    __weak typeof(person) weakPerson = person;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"block1---%@", person);

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"block2---%@", weakPerson);
        });
    });
    NSLog(@"person作用域结束");
}

// 打印
14:33:06 person作用域结束
14:33:07 block1---<Person: 0x6000011cc450>
14:33:07 Person dealloc
14:33:09 block2---(null)

推荐阅读:Block原理分析(二)

相关文章

网友评论

    本文标题:iOS:Block(一)

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