block的使用总结
来源:互联网 发布:python udp编程 编辑:程序博客网 时间:2024/05/16 01:21
一.block的原理:
Block的实质是指向结构体的指针,
查看block的底层代码:在终端中cd到工程路径,然后执行
clang -rewrite-objc main.m
block的数据结构定义
我们通过大师文章中的一张图来说明:
上图这个结构是在栈中的结构,我们来看看对应的结构体定义:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
从上面代码看出,Block_layout就是对block结构体的定义:
isa指针:指向表明该block类型的类。
flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。
reserved:保留变量,我的理解是表示block内部的变量数。
invoke:函数指针,指向具体的block实现的函数调用地址。
descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。
variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。
3.2 block的类型
block有几种不同的类型,每种类型都有对应的类,上述中isa指针就是指向这个类。这里列出常见的三种类型:
_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:
#include int main()
{
^{ printf(
"Hello, World!\n"
); } ();
return
0;
}
_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:
#include int main()
{
char a =
'A'
;
^{ printf(
"%c\n"
,a); } ();
return
0;
}
_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:
void exampleB_addBlockToArray(NSMutableArray *array) {
char b =
'B'
;
[array addObject:^{
printf(
"%c\n"
, b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
总结一下:
_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。
_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了(block所在的函数结束就出栈 了),所以无法多次使用。
_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。
===========================
先看一个只输出一句话的block是怎么样的。
生成中间代码,得到片段如下:
首先出现的结构体就是__main_block_impl_0,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的。如果是全局block,就根据变量名和出现序列进行命名。__main_block_impl_0中包含了两个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。
接着出现的是__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。
再下面是__main_block_desc_0结构体,其中比较有价值的信息是block大小。
最后就是main函数中对block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体。
这里,block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。
由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。
Block是一种比较特殊的数据类型。它可以保存一段代码,在合适的时候取出来调用。
ARC情况下1.如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。解决方法:新建一个指针(__weak typeof(self) weakSelf = self)指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。
2.如果用weak修饰Block,该Block就会存放在栈空间。不会出现循环引用问题。
MRC情况下用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。
Block的定义格式
返回值类型(^block变量名)(形参列表) = ^(形参列表) { }; 调用Block保存的代码:如下 block变量名(实参);
1,默认情况下,在Block内部不能修改block外的局部变量,
2.在block内部可以修改所有函数以外的全局变量,
4, 在block内部可以定义和外部同名的变量;
5.如果是block外的局部变量,当定义block的时候,会把外部的变量的值放到栈区中,把外部的局部变量拷贝一份到block中,是以const的形式拷贝过来的,所以不能改变.
6.下面这种情况可以修改block外的局部变量: 在block外部定义: __block int m=10;
在block内部可以改变 m 的值;
只要加上__block,以后的m都存在堆区的,block以后的变量m的值都取block中的m值;
7.常量变量(NSString *a = @"abc"中 a是变量,@“abc”是常量); 不加__block类型修饰,block会引用常量的地址(浅拷贝)。加__block类型修饰block会去引用【常量变量】(如:a变量,a = @“abc”,可以任意修改a指向的内容)的地址.
在ARC模式下,在定义block的时候回默认返回一个堆block,即拷贝一份block内部的代码到堆中.
============================================
//如果一个block外部的变量加了 __block修饰系统就会创建一个结构体内部封装了变量的地址
__block int a =10;
//创建了一个变量
//结构体__Block_byref_a_0的变量
struct __Block_byref_a_0 {
void *__isa; 0
__Block_byref_a_0 *__forwarding; //保存自己的地址
int __flags; // 0
int __size; //当前结构体的大小
int a; 20
};
__attribute__((__blocks__(byref)))__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0, sizeof(__Block_byref_a_0),10};
void (^block)() = ^{
printf("%d",a);
};
//传址 int a =10;替换成了结构体并且把结构体的地址传给了 block的结构体
// &a
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a,570425344));
//__main_block_impl_0 函数 参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数3 就是block之外的变量添加了__block修饰的变量 a ->会被创建成结构对象 取其地址传递进去当做参数
a = 20;
block();
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
===============传值:
void (*block)() = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA,10));
//__main_block_impl_0 函数 参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数3 就是block之外的变量 a
//
struct __main_block_impl_0 {
//
struct __block_impl impl; __main_block_func_0 =>impl.FuncPtr :函数 这个函数封装了之前block中代码
struct __main_block_desc_0* Desc;
int a; //10
//
__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;
}
};
//block 结构体 里面有一个函数 __main_block_impl_0构造函数
//C++ 代码 里面所有的构造函数跟结构体(相当于对象:属性和 方法 )名称一致 ===> oc init开头 ==> 初始化对象
void (*block)() : 函数指针
//=====
a = 20;
block();
//====
FuncPtr 函数指针
函数名字
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//====
FuncPtr((__block_impl *)block);
block内部是调用了一个结构体中的函数:
static struct main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (copy)(struct __main_block_impl_0, struct main_block_impl_0);
void (dispose)(struct __main_block_impl_0*);
}
然后经过分析该c++文件我们知道
block实际上是: 指向结构体的指针
编译器会将block的内部代码生成对应的函数
============================================
二.block的基本使用
1,申明一个block:
我们通过^符号来声明block类型,形式如下:
void (^lamboBlock)();
其中第一个void是返回值,可以是任意类型,中间括号中^后面的是这个block变量的名字,我把它命名为lamboBlock,最后一个括号中是参数,如果多参数,用逗号隔开,可以写成如下样式:
int (^lamboBlock)(int,int);
同样,你也可以加上参数名,如:
int (^lamboBlock)(int a,int b);
很多时候,我们需要将我们声明的block类型作为函数的参数,也有两种方式:
1、-(void)func:(int (^)(int a,int b))block;//作为函数的参数
@property(nonatomic,copy)int (^lamboBlock)(int a,int b);//作为属性名
作为函数参数举例:
- (void)viewDidLoad {
[superviewDidLoad];
[selffunc:^int(int a, int b) {
NSLog(@"dddd");
return 2;//这个地方必须要要返回值,因为参数是有返回值的block
}];
}
-(void)func:(int (^)(int a,int b))block{
NSLog(@"我是");
}
第二种方式是通过typedef定义一种新的类型(也就是起个别名),这也是大多数情况下采用的方式:
2、typedef int (^lamboBlock)(int a,int b) ;//起别名
-(void)func:(lamboBlock)block ;//作为函数参数
@property(nonatomic,copy) lamboBlock block;//作为属性名
===================================
2.实现block:
无参数无返回值
void (^emptyBlock)() = ^(){ NSLog(@"无参数,无返回值的Block"); }; emptyBlock();
NSLog(@"无参数,无返回值的Block"); }; emptyBlock();
void (^sumBlock)(int ,int ) = ^(int a,int b){ NSLog(@"%d + %d = %d",a,b,a+b); }; /** * 调用这个sumBlock的Block,得到的结果是20 */ sumBlock(10,10);
有参数有返回值
方法一:申明和定义(实现)分开(有参数有返回值)
NSString* (^lamboBlock)(NSString*,int);
lamboBlock = ^(NSString *name, int age){ return [NSString stringWithFormat:@"My name is %@,I‘m %d !",name,age];//有返回值};NSString *str = lamboBlock(@"lamboBlock",19);//block 的调用
方法二:同时申明和定义(有参数有返回值)
NSString* (^lamboBlock)(NSString*,int)= ^(NSString *name,int age){
return [NSString stringWithFormat:@"My name is %@,I‘m %d !",name,age];//有返回值};NSString *str = lamboBlock(@"lamboBlock",19);//block 的调用
- block的使用总结
- block的使用总结
- block的使用总结
- iosiOS学习总结----block的简单使用
- Block 使用总结
- iOS block使用总结
- Block使用总结
- iOS-block的总结
- iOS-Block的总结
- Block的陷阱总结
- iOS Block在ARC/非ARC下的使用总结
- iOS Block在ARC/非ARC下的使用总结
- iOS Block在ARC/非ARC下的使用总结
- iOS中关于block使用的一些总结
- Block的使用 iphone
- block的简单使用
- oc block的使用
- block的使用
- 微软段落问题
- AES加解密算法
- 电商网站分类导航效果--CSS实现(一) 一级菜单
- 71-类的定义
- 设计模式之Iterator(一)
- block的使用总结
- pixhawk飞控中添加uORB主题
- 72-访问控制
- C++单向链表之合并链表
- 保存操作,同时进行自动提交、审核
- 2015 年度新增开源软件排名 TOP100
- 实现一个储蓄账户对象的存钱取钱操作
- jdk和jre有什么不同?_jdk与jre的区别
- 生活流水