<h6> 1. iOS Block用来封装一段代码块或者传递参数相对于代理使用起来方便,它本质上是一个匿名函数。</h6>
完整的block的申明: 返回值类型 (^blockName)(参数列表) = ^返回值类型 (参数列表){
// 表达式;what you want to do;
};
void(^blockTest)(void) = ^void(void){
printf("hello world");
};
<h6>2.使用block传值</h6>
顺传:定义属性
逆向传值:使用代理、通知、block、单例、全局变量、等传值手段传值
---------------------------------
假设现在有a,b两个页面,a是b的上一级页面,b此刻处于栈顶,a,压在它下面。
想要把a中的一个name值传到b,顺传,定义一个属性接收。
想要把b中的一个name值传到a,逆传,此处只说block。
---------------------------------
逆传方法有两种:
1.b中申明可以带参数的block,在你需要的时机调用,传递参数。a中合适的时机处理传递回来的参数。
----------------------------------------------------
b.h
@interface BViewController : UIViewController
@property (nonatomic ,copy) void (^blkTest)(UIColor*);
@end
----------------------------------------------------
b.m
#import "BViewController.h"
@interface BViewController ()
@end
@implementation BViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"bbbbb";
self.view.backgroundColor = [UIColor yellowColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (self.blkTest) {
self.blkTest([UIColor yellowColor]);
}
}
@end
----------------------------------------------------
a.m
#import "AViewController.h"
#import "BViewController.h"
@interface AViewController ()
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"aaaaa";
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
BViewController *vc = [[BViewController alloc] init];
vc.blkTest = ^(UIColor *color){
self.view.backgroundColor = color;
};
[self.navigationController pushViewController: vc animated:true];
}
@end
----------------------------------------------------
2个界面都变成黄色的了。
----------------------------------------------------
---------------------分割线----------------------
----------------------------------------------------
----------------------------------------------------
2.重写b初始化方法传入参数为一个block,在b的初始化方法中实现该block。
在a中创建b的时候就可以传递参数了。(这么做可以不暴漏属性给外界)
----------------------------------------------------
b.m
#import "BViewController.h"
@interface BViewController ()
@property (nonatomic ,copy) void (^blkTest)(UIColor*);
@end
@implementation BViewController
- (instancetype)initWithBlock:(void (^)(UIColor *))blkTest{
self.blkTest = blkTest;
return [self init];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"bbbbb";
self.view.backgroundColor = [UIColor yellowColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if (self.blkTest) {
self.blkTest([UIColor yellowColor]);
}
}
@end
----------------------------------------------------
a.m
#import "AViewController.h"
#import "BViewController.h"
@interface AViewController ()
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"aaaaa";
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
BViewController *vc = [[BViewController alloc] initWithBlock:^(UIColor * color) {
self.view.backgroundColor = color;
}];
[self.navigationController pushViewController: vc animated:true];
}
@end
----------------------------------------------------
<h6>3.block的类型</h6>3种:
_NSConcreteStackBlock 栈
_NSConcreteGlobalBlock 程序的数据区域(.data 区)
_NSConcreteMallocBlock 堆
<h5>__NSConcreteGlobalBlock 程序的数据区域(.data 区)
1.全局block,在数据区。
#include <stdio.h>
void(^blockTest)(void) = ^void(void){
printf("hello world");
};
int main(int argc, char * argv[]){
blockTest();
return 0;
}
---------------------clang后的关键代码-------------------------------
int a = 0;
struct __block_impl {
void *isa;//isa指针 所有对象都有,说明block也是一个对象
int Flags;//字面意思,标记。记录block的一些附加信息
int Reserved;//保留变量
void *FuncPtr;//函数指针,指向具体的block实现的函数调用地址
};
struct __blockTest_block_impl_0 {//blockTest通过clang看到的c
代码就是__blockTest_block_impl_0结构体
struct __block_impl impl;//定义一个结构体,纪录block的信息。即是上面的定义的结构体:
struct __blockTest_block_desc_0* Desc;//纪录block大小,保留变量等信息
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {//block的具体实现
impl.isa = &_NSConcreteGlobalBlock;//isa指向全局block,data区
impl.Flags = flags;//给其他属性赋值
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {//静态的函数传入实现block-__blockTest_block_impl_0
printf("hello world");//函数体
}
static struct __blockTest_block_desc_0 {//纪录block大小,保留变量等信息
size_t reserved;//保留变量
size_t Block_size;//大小
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//定义一个结构体变量,给上面属性赋值
static __blockTest_block_impl_0 __global_blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA);// 给__blockTest_block_impl_0赋值。定义__blockTest_block_impl_0类型的变量__global_blockTest_block_impl_0,传入参数:函数指针 __blockTest_block_desc_0的地址
void(*blockTest)(void) = ((void (*)())&__global_blockTest_block_impl_0);//把上一步定义的__global_blockTest_block_impl_0复制给blockTest
当block定义在局部代码内,但没有访问外部变量时,也在数据区。(其他c代码基本上都差不太多,篇幅过大,(每一行c代码都有注释)这里不在复制。)
在block内修改变量a的值会报错。代码自行编译。
看代码可知道,在block中引用的变量a实际是在申明block时,被复制到main_block_impl_0结构体中的那个变量a。因为这样,我们就能理解,在block内部修改变量a的内容,不会影响外部的实际变量a。
想要修改要加上__block,这时看代码会发现
<下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
printf("%d\n", (i->__forwarding->i));
(i->__forwarding->i) = 1023;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
return 0;
}
<h6>
1.增加了一个结构体,__Block_byref_i_0 ,用来保存我们要capture并且修改的变量i。
2.main_block_impl_0 中引用的是Block_byref_i_0的结构体指针,这样就可以达到修改外部变量的作用。
3.__Block_byref_i_0结构体中带有isa,说明它也是一个对象。
4.我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数
<h6>
<下面的内容摘录自《Objective-C 高级编程 iOS 与 OSX 多线程与内存管理》。http://vdisk.weibo.com/s/u65p8oUfGH52e>
除全局block外,定义block的时候,是分配在栈上的,block只在定义它的范围有效。
栈的生命周期结束,block销毁,此时想要保存数据或者__block变量用结构体成员__forwarding。就要用到堆block。oc中的block是从栈拷贝到堆上的。
什么时候栈上的 Block 会复制到堆?
调用 Block 的 copy 实例方法时
Block 作为函数返回值或者参数时
将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递 Block 时
<下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>
NSConcreteMallocBlock 类型的block的实现
NSConcreteMallocBlock类型的block通常不会在源码中直接出现,因为默认它是当一个block被copy的时候,才会将这个block复制到堆中。以下是一个block被copy时的示例代码(来自这里),可以看到,在第8步,目标的block类型被修改为_NSConcreteMallocBlock。
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
<h6>
在ARC开启的情况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。原本的NSConcreteStackBlock的block会被NSConcreteMallocBlock类型的block替代。证明方式是以下代码在XCode中,会输出 <NSMallocBlock: 0x100109960>。在苹果的官方文档中也提到,当把栈中的block返回时,不需要调用copy方法了。
</h6>
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
@autoreleasepool {
int i = 1024;
void (^block1)(void) = ^{
printf("%d\n", i);
};
block1();
NSLog(@"%@", block1);
}
return 0;
}
我个人认为这么做的原因是,由于ARC已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。