ios学习--block深度解析

2 篇文章 0 订阅

1. block的本质是一个Objective-C的对象,为什么这么说?

Objective-C中,runtime会在运行时根据对象的isa指针的指向,来度额定这个对象的类型,也可以认为一个对象,它具有isa指针,就是一个OC对象


2. 你怎么知道block有isa指针呢,我们可以通过clang命令将来看block的实现

//测试代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^blk)(void)=^{
            NSLog(@"hello lx");
        };

    }
    return 0;
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

转化后:block对象被编译器转化成了一个__main_block_impl_0 
的结构体如下

struct __main_block_impl_0 {
  struct __block_impl impl;//block实现的相关信息
  struct __main_block_desc_0* Desc;//block的描述信息
  __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;
  }
};
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这个结构体由三部分组成impl的结构体+Desc的结构体指针+构造函数,构造函数是用来对结构体做初始化的,剩下的那两个结构体对应的实现如下

//impl的类型,block的相关实现信息
struct __block_impl {
  void *isa;//看到了isa指针,说明block是一个isa指针,对于他的类后面会讲到
  int Flags;//标志
  int Reserved;//
  void *FuncPtr;//函数的实现,指向了block变量所对应的函数的实现地址
};
//对于block的描述信息
struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

总结一下上面的代码:

block会被编译器转化为__main_block_impl_0的结构体,这个结构体由三部分组成:存储block的相关实现信息的_block_impl的结构体impl以及存储block的描述信息的__main_block_desc_0的结构体指针Desc以及用来对block做初始化的构造方法,其中impl中的isa的成员说明了block是一个oc对象,还有一个一个函数指针指向了block的实现


3. block:其实相当于c语言中的匿名函数,他可以访问外部变量或者对象


  • block这个匿名函数和普通函数的区别 

 1.没有函数名,最纯洁的block ^{ printf("hello"); };,最纯洁的函数最起码要有个函数名 
2. 带有脱字符’^’ 
3.不管c语言还是oc函数都不支持函数嵌套,但是block实现了这个功能,它相当于一个函数,但是他的视线却是在另外一个函数的内部。

可以访问的类型

#
  1. 自动变量:就是局部变量。访问局部变量相当于函数调用时的值传递,不允许修改:当访问了外部的变量,他会把他访问到的自动变量作为结构体成员添加到他的结构体当中,而且和原变量同名同值,不能修改的原因是,他们除了值一样没有练习,上面说到,block相当于一个函数,两个函数里面有仙童的局部变量,一个变了并不会对另外一个产生影响,所以编译器干脆就禁止了,实现如下
//block访问外部自动变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;//将其访问到的变量添加的自己的结构体中,没有访问到的不会添加
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
#

2 . 静态变量,在其作用范围内只有一分内存,对其可读可写。相当于函数调用时的地址传递,block会将它访问到的外部静态变量的地址添加到自己的结构体成员中,有了地址,改值又有什么不可以呢,实现如下

//截获静态变量值
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;//比没有访问外部变量时多了这一行,访问到的外部静态变量的地址
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
#

3 . 全局变量:在全局可以访问到的,在全局符号表中,与在block外访问没有区别,可读可写 
对于静态、全局变量的区别,可以戳这里我觉得可以点

#

4 .对象,block可以读对象,不能修改对象指针的指向。

#

5 .对象的属性:可读可写


4. 如何让block可以修改截获的自动变量的值


4. 修改block所截获的外部变量的值

上面说到:block不可以修改自动变量或者对象的指向,这个时候,__block就像一个救世主一样出现在了我们面前,__block是如何实现修改自动变量值的,看一下他的实现



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; //多了一个__Block_byref_a_0结构体指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//被block修饰的自动变量被这样的一个结构体指针保存了其地址
struct __Block_byref_a_0 {
  void *__isa;//指向其所对应的类
__Block_byref_a_0 *__forwarding;//指向自身
 int __flags;
 int __size;
 int a;//自动变量的值
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 20;
            printf("%d",(a->__forwarding->a));
        }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

从上面可以看出,当用__block修饰自动变量的时候,这个变量变成了一个struct __Block_byref_a_0的结构体实例,这个结构体有一个指向自身的指针,forwarding,来保证这个变量不管在什么位置都能被正确访问到,(后面会详细解释) 
修改变量的过程:impl会根据自己保存的指向struct __Block_byref_a_0的结构体实例的指针,根据这个指针找到farWarding指针找到自己,在找到里面存储的自动变量,修改他的值。

5. block的存储域

在前面,我们发现block本质是一个对象,他有一个isa指针,指向其所对应的类,那么他所对应的类是什么呢,这就牵扯到block的存储域了

block的存储于域有三种类型,其中isa指针指向这三种中的其中一种 
1. _NSConcreteStackBlock:存储在栈上 
2. _NSConcreteGlobalBlock:存储在数据区 
3. _NSConcretMallocBlock:存储在堆上

对于这三种类型的使用情况

_NSConcreteGlobalBlock: 
1. 凡是所有的全局block都存储在数据区 
2. 如果block中没有截获自动变量,block也在数据区

_NSConcreteStackBlock: 
1. MRC:默认在栈区,block生命周期与其作用域相关 
2.ARC:大多数ARC情况下的block是存储在堆区的,只有少部分在栈区。在栈区需要我们手动 copy 到堆区 
_NSConcretMallocBlock 
MRC:通过copy的方法可以将block从栈区复制到堆区 
ARC:大多数默认情况在堆区,有栈区的可以通过copy/strong复制到堆区,只要有一个strong指向他,就会被复制到堆区


6. 通过copy将block从栈上复制到堆上

在栈上的变量的生命周期由编译器来管理,与其作用域相关联,出了作用域就会被释放,但是堆上有程序员自己管理或者ARC管理,不会因为其出了作用域就被释放

前面提到了一个forwarding指针,用block修饰的自动变量会被保存在一个__Block_byref_a_0的结构体指针中,这个指针中又有一个指向自己的forwarding,那么为什么不直接找到其对应的值,而要通过这个指针呢,因为当block对象被从栈上赋值到堆上的时候,其内部用到的被block修饰的变量也会被赋值一份放在堆上,然后让栈上forwarding结构体指针指向堆上的结构体就可以,这样就可以保证不管在栈上还是堆上,都可以正确访问到变量


ARC 下需要不需要手动copy 的情况

  1. 当 block 被强引用时
  2. 系统的 API 中带有 usingBlock 时
  3. block 作为函数返回值

那什么时候需要我们手动 copy 呢?

当block 作为函数参数的时候,在 arc 下我们自定义的 block 要写上 copy。

关于copy的特点

  1. 如果原来在栈上,通过copy,被复制到堆上。
  2. 如果原来在全局数据区,不会发生改变
  3. 如果在堆区:其引用计数加1

7. __block在ARC和MRC下的区别

1、 用__block修饰,不管在mrc还是arc下,都可以修改自动变量的值 
2、在MRC下会避免循环引用,因为mrc下用__block修饰的变量不会被reatin,所以不会被 block 持有,所以可以避免循环引用 
3、 在ARC下解决循环引用用的是weak:ARC下的循环引用并不是有__block引起的,是因为ARC的特性,默认是强引用,所以为了解决循环引用,self一般用weak来修饰


8. 为什么要用copy来修饰block

使用copy可以将block从栈上转移到堆上 
MRC下,默认是栈上为了控制block生命周期,需要将其copy的堆上,不可以用reatin代替。 ARC下大多数情况默认是在堆上,但是因为一般遵循传统,会写上copy,但是可以用strong来代替。




原文地址:http://blog.csdn.net/li15809284891/article/details/55033960?ref=myread


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的影城管理系统,源码+数据库+论文答辩+毕业论文+视频演示 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多生活之中,随之就产生了“小徐影城管理系统”,这样就让小徐影城管理系统更加方便简单。 对于本小徐影城管理系统的设计来说,系统开发主要是采用java语言技术,在整个系统的设计中应用MySQL数据库来完成数据存储,具体根据小徐影城管理系统的现状来进行开发的,具体根据现实的需求来实现小徐影城管理系统网络化的管理,各类信息有序地进行存储,进入小徐影城管理系统页面之后,方可开始操作主控界面,主要功能包括管理员:首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理,用户前台;首页、电影信息、电影资讯、个人中心、后台管理、在线客服等功能。 本论文主要讲述了小徐影城管理系统开发背景,该系统它主要是对需求分析和功能需求做了介绍,并且对系统做了详细的测试和总结。具体从业务流程、数据库设计和系统结构等多方面的问题。望能利用先进的计算机技术和网络技术来改变目前的小徐影城管理系统状况,提高管理效率。 关键词:小徐影城管理系统;Spring Boot框架,MySQL数据库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值