iOS开发之block解析


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;
}

转化后:block语法被编译器转化成了以下的结构

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;
  }
};

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

//impl的类型,block的相关实现信息
struct __block_impl {
  void *isa;//看到了isa指针,说明block是一个isa指针。对于他的类后面会讲到
  int Flags;//标志
  int Reserved;//
  void *FuncPtr;//函数的实现。指向了block所相应的函数的实现地址:由于 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)};

总结一下上面的代码:

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相当于一个函数,两个函数里面有同样的的局部变量。一个变了并不会对另外一个产生影响,所以编译器干脆就禁止了。实现例如以下
//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;
  }
};
#

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;
  }
};
#

3 . 全局变量:在全局能够訪问到的。在全局符号表中,与在block外訪问没有差别,可读可写,无论怎样都能够訪问到

对于静态、全局变量的差别,能够戳这里我觉得能够点

#

4 .对象。block能够读对象。

#

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

#

6:对于成员变量

self 隐式循环引用:对于一个或者多个成员变量,无论是否由 __block 修饰。block结构体会自己主动生成一个成员 Self。并且会引用成员变量所属的对象实例,self 对于成员变量的改动都是通过对象 self 指针引用来实现的,block 内部对于成员变量的引用也是通过 block 结构体对象的成员 self 指针引用实现(可是会造成循环引用)。


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));
        }

从上面能够看出,当用__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来取代。

时间: 2024-12-19 02:07:56

iOS开发之block解析的相关文章

李洪强iOS开发之Block和协议

李洪强iOS开发之Block和协议 OC语言BLOCK和协议 一.BOLCK (一)简介 BLOCK是什么?苹果推荐的类型,效率高,在运行中保存代码.用来封装和保存代码,有点像函数,BLOCK可以在任何时候执行. BOLCK和函数的相似性:(1)可以保存代码(2)有返回值(3)有形参(4)调用方式一样. 标识符 ^ (二)基本使用 (1)定义BLOCK变量 Int (^SumBlock)(int,int);//有参数,返回值类型为int Void (^MyBlock)()://无参数,返回值类型

iOS开发之XML解析

iOS开发之XML解析 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示XML文件版本, 内部文本使用的编码 <root> 表示根节点 <CityName>北京</CityName>  一个结点, CityName是结点名, 北京结点值 <Item key="1" value="A"></Item>  

iOS开发之Block

iOS开发之Block 1.block的含义和作用 UI开发和网络常见功能实现回调, 按钮的事件处理方法是回调方法, 网络下载后的回调处理 (1) 按钮 target-action 一个方法传入按钮中 (2) 表格视图 传入一个指针self, 回调视图控制器中的方法 (3) block 语句块, 解决回调, 理解为"匿名函数", 定义在方法里面 2.block的基本使用(语法) 涉及知识点: 定义block变量,定义block语句块 block参数和返回值 block捕获外部变量(包括

IOS开发之XML解析以及下拉刷新上拉加载更多的分享

IOS开发之XML解析 1.XML格式 <?xml version="1.0" encoding="utf-8" ?> 表示XML文件版本, 内部文本使用的编码 <root> 表示根节点 <CityName>北京</CityName>  一个结点, CityName是结点名, 北京结点值 <Item key="1" value="A"></Item>  

iOS开发之XML解析代码

iOS开发之XML解析代码 //1.加载和解析XML文件 NSString *path = [[NSBundle mainBundle] pathForResource:@"xml.txt" ofType:nil]; NSData *data = [[NSData alloc] initWithContentsOfFile:path]; // GDataXMLDocument 表示XML文档对象 // initWithData 使用NSData初始化, 就是解析 GDataXMLDoc

ios开发之block的使用,及注意事项

转自:http://my.oschina.net/u/1432769/blog/390401 Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事.需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash. Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外.词法

IOS开发之block应用

非常长时间都是在学习各位大神的力作,并汲取了不少养料,在此一并谢过各位大神了. 当然了.好东西是要跟大家一起分享的,近期发现了几个很不错的个人网站,都是介绍IOS开发的.当中有唐巧.破船之长.池建强.王维等各位,当中不乏供职于腾讯和阿里这种IT巨头.希望大家也能从他们的博客中学习到一些技术之外的东西. 就不再啰嗦啦,附上地址:http://www.ityran.com/archives/4647 这几天在学习IOS7 CookBook.由于没有找到中文版.就硬着头皮啃原著吧.还真学到了不少东西,

iOS开发之JSON解析

JSON解析步骤: - (NSArray *)products { if (_products == nil) { //第一步:获取JSON文件的路径: NSString *path = [[NSBundle mainBundle] pathForResource:@"products.json" ofType:nil]; //第二步:加载JSON文件: NSData *data = [NSData dataWithContentsOfFile:path]; //第三步:将JSON数据

iOS开发之html解析

使用XPath解析html 可以从此处https://github.com/topfunky/hpple下载工程,将TFHpple.h,TFHpple.m,TFHppleElement.h,TFHppleElement.m,XPathQuery.h,XPathQuery.m加到自己的项目中,在Frameworks中导入libxml2.x 在项目中找到Other Linker Flags,加入-libxml2 在项目中找到Header Search Paths,加入/usr/include/lib