注册

iOS - Block 准备面试必须了解的东西

一.Block的本质

        block本质是一个OC对象,它里面有个isa指针,封装了函数调用环境的OC对象,封装了函数调用上下文的OC对象。

32d451656298a701df3308055f511bcc.png

查看Block源码:

struct __block_impl {

    void*isa;

    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,intflags=0) {

    impl.isa = &_NSConcreteStackBlock;

    impl.Flags = flags;

    impl.FuncPtr = fp;

    Desc = desc;

  }

};

// 封装了block执行逻辑的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);

        }

static struct __main_block_desc_0 {

  size_treserved;

  size_tBlock_size;

} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(intargc,constchar* 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);

    }

    return0;

}

说明:FuncPtr:指向调用函数的地址,__main_block_desc_0 :block描述信息,Block_size:block的大小

二.Block变量的捕获

2.1局部变量的捕获

        对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。

int age=10;

void(^Block)(void)=^{

NSLog(@"age:%d",age);

};

age=20;

Block();

2.2__block 修饰的外部变量

        对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值

__block int age=10;

myBlock block=^{

NSLog(@"age = %d",age);

};

age=18;

block();

输出:18;

auto int age=10;

static int num=25;

void(^Block)(void)=^{

NSLog(@"age:%d,num:%d",age,num);

};

age=20;

num=11;

Block();

        输出结果为:age:10,num:11,auto变量block访问方式是值传递,也就是当block定义的时候,值已经传到block里面了,static变量block访问方式是指针传递,auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可,block不需要对全局变量捕获,都是直接采用取值的,局部变量的捕获是因为考虑作用域的问题,需要跨函数访问,就需要捕获,当出了作用域,局部变量已经被销毁,这时候如果block访问,就会出问题。

2.2.block变量捕获机制


9c5fd501f641dc8ab4d57462dab94d71.png

 block里访问self,self是当调用block函数的参数,参数是局部变量,self指向调用者,所以它也会捕获self,block里访问成员,成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量。

3.3Block的类型

        block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__NSGlobalBlock __ ( _NSConcreteGlobalBlock )全局block即数据区

__NSStackBlock __ ( _NSConcreteStackBlock )堆区block

__NSMallocBlock __ ( _NSConcreteMallocBlock )栈区block

        说明:堆区,程序员自己控制,程序员自己管理,栈区,系统自动控制,一般我们使用最多的是堆区Block,判断类型的根据是没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段访问了auto变量的block是__NSStackBlock __;[__NSStackBlock __ copy]操作就变成了__NSMallocBlock __,__NSGlobalBlock __ 调用copy操作后,什么也不做__NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆__NSMallocBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆,在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况是:

                1.block作为函数返回值时

                2.将block赋值给__strong指针时

                3.block作为Cocoa API中方法名含有usingBlock的方法参数时

                4.block作为GCD API的方法参数时

三.对象类型的auto变量

typedefvoid(^XBTBlock)(void);

XBTBlock block;

{

Person*p=[[Person alloc]init];

p.age=10;

block=^{

NSLog(@"======= %d",p.age);

};}

Person.m

-(void)dealloc{

NSLog(@"Person - dealloc");

}

        说明:block为堆block,block里面有一个Person指针,Person指针指向Person对象。只要block还在,Person就还在。block强引用了Person对象。在MRC下,就会打印,因为堆空间的block会对Person对象retain操作,拥有一次Person对象。无论MRC还是ARC,栈空间上的block,不会持有对象;堆空间的block,会持有对象。

特别说明:block内部访问了对象类型的auto变量时,是否会强引用?

栈block

a) 如果block是在栈上,将不会对auto变量产生强引用

b) 栈上的block随时会被销毁,也没必要去强引用其他对象

堆block

1.如果block被拷贝到堆上:

a) 会调用block内部的copy函数

b) copy函数内部会调用_Block_object_assign函数

c) _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

2.如果block从堆上移除

a) 会调用block内部的dispose函数

b) dispose函数内部会调用_Block_object_dispose函数

c) _Block_object_dispose函数会自动释放引用的auto变量(release)

正确答案:

如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象

如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

3.2gcd的block中引用 Person对象什么时候销毁?

eg:-(void)touchesBegan:(NSSet *)toucheswithEvent:(UIEvent*)event{

    Person*person = [[Personalloc]init];

    person.age=10;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"age:%d",person.age);

    });

    NSLog(@"touchesBegan");

}

输出:touchesBegan

            age:10

            Person-dealloc

        说明:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放,如果上诉Person用__weak。即添加代码为__weak Person*weakPerson=person;,在Block中变成NSLog(@"age:%p",weakPerson);,它就不输出age,使用__weak修饰过后的对象,堆block会采用弱引用,无法延时Person的寿命,所以在touchesBegan函数结束后,Person就会被释放,gcd就无法捕捉到Person,gcd内部只要有强引用Person,Person就会等待执行完再销毁!如果gcd内部先强引用后弱引用,Person会等待强引用执行完毕后释放,只要强引用执行完,就不会等待后执行的弱引用,会直接释放的

eg:-(void)touchesBegan:(NSSet *)toucheswithEvent:(UIEvent*)event{

    Person*person = [[Personalloc]init];

    person.age=10;

    __weakPerson*weakPerson = person;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),

                   dispatch_get_main_queue(), ^{

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            NSLog(@"2-----age:%p",weakPerson);

        });

        NSLog(@"1-----age:%p",person);

    });

    NSLog(@"touchesBegan");

}

四.Block的修饰符

        block在修改NSMutableArray,不需要加__block,auto修饰变量,block无法修改,因为block使用的时候是内部创建了变量来保存外部的变量的值,block只有修改内部自己变量的权限,无法修改外部变量的权限。

        static修饰变量,block可以修改,因为block把外部static修饰变量的指针存入,block直接修改指针指向变量值,即可修改外部变量值。全局变量值,全局变量无论哪里都可以修改,当然block内部也可以修改。

eg:__block int age = 10,系统做了哪些---》编译器会将__block变量包装成一个对象

__block 修饰符作用:

        __block可以用于解决block内部无法修改auto变量值的问题

        __block不能修饰全局变量、静态变量(static)

        编译器会将__block变量包装成一个对象

        __block修改变量:age->__forwarding->age        

        __Block_byref_age_0结构体内部地址和外部变量age是同一地址

        __block的内存管理---->当block在栈上时,并不会对__block变量产生强引用

block的属性修饰词为什么是copy?

        block一旦没有进行copy操作,就不会在堆上

        block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期,会调用block内部的copy函数

        copy函数内部会调用_Block_object_assign函数

        _Block_object_assign函数会对__block变量形成强引用(retain)

        对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用,当block从堆中移除时,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动释放引用的__block变量(release),当block在栈上时,对它们都不会产生强引用,当block拷贝到堆上时,都会通过copy函数来处理它们,对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用

__block的__forwarding指针说明:

        栈上__block的__forwarding指向本身

        栈上__block复制到堆上后,栈上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身

五. block循环引用

        1.ARC下如何解决block循环引用的问题?

        三种方式:__weak、__unsafe_unretained、__block

        1)第一种方式:__weak

        Person*person=[[Person alloc]init];

        // __weak Person *weakPerson = person;

        __weaktypeof(person)weakPerson=person;

        person.block=^{

            NSLog(@"age is %d",weakPerson.age);

        };

        2)第二种方式:__unsafe_unretained

        __unsafe_unretained Person*person=[[Person alloc]init];

        person.block=^{

            NSLog(@"age is %d",weakPerson.age);

        };

        3)第三种方式:__block

        __block Person*person=[[Person alloc]init];

        person.block=^{

            NSLog(@"age is %d",person.age);

            person=nil;

        };

        person.block();

三种方法比较:__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil,__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,__block:必须把引用对象置位nil,并且要调用该block



dcdfa32bf641df118d41edbca80c469e.png





作者:枫紫
链接:https://www.jianshu.com/p/4bde3936b154






0 个评论

要回复文章请先登录注册