注册

iOS Block浅谈

一.Block的本质


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

Block底层结构图
查看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,structmain_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变量捕获机制


block变量捕获机制
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


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


0 个评论

要回复文章请先登录注册