《objective-c高级编程》这本书是一本非常好的书,但是时间比较早,书中有一些理论不适合最新的ios版本,并且书中论述ARC和MRC的情况比较混乱,穿插讲,但丝毫不会掩饰这本书的好。瑕不掩瑜
iOS代码块Block
概述
代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调
Block变量的声明、赋值与调用
Block变量的声明
block和普通的变量一样可以作为属性,参数,返回值
作为属性时的声明如下:
形式为: 返回值类型 (^block名称) (参数声明);
int (^blockName) (int);
void (^blockName) (void);
void (^blockName) ();
typedef形式如下:
typedef int (^blk_t)();
blk_t blockName;
作为函数参数时的声明如下:
objective-c形式
- (id) fun: (int (^)())block
{
int var = 10;
NSArray *arry = [[NSArray alloc] initWithObjects:^{NSLog(@"%d",var);}, nil];
return arry;
}
typedef形式如下:
typedef int (^blk_t)();
- (id) fun: (blk_t)block
{
int var = 10;
NSArray *arry = [[NSArray alloc] initWithObjects:^{NSLog(@"%d",var);}, nil];
return arry;
}
c语言形式如下:
void func (int (^block)())
{
}
typedef int (^blk_t)();
void func (blk_t block)
{
}
作为返回值类型时
objective-c形式
- (int (^)()) funoc
{
return ^(int count){return count;};
}
typedef形式如下:
typedef int (^blk_t)();
- (blk_t) funoc
{
return ^(int count){return count;};
}
c语言形式如下:
int (^fun())()
{
return ^(int count){return count;};
}
typedef形式如下:
typedef int (^blk_t)();
blk_t fun()
{
return ^(int count){return count;};
}
Block变量的定义
^ 返回值类型 (参数) {};
说明:返回值类型可以省略,(参数)可以省略,最简形式为^{};
//完整形式
^ int (int count){return count;};//完整形式
//省略返回值
^ (int count){return count;};//返回值类型可以省略,默认与返回值类型相同
//省略参数
^ {
//方法体
};//如果没有参数,可以省略参数
//没有省略参数的形式
^ (void){
//方法体
};//参数为空可以省略参数
Block变量的调用
block的调用很简单,直接在block名称加括号,与函数调用一致
int (^blockName)(int) = ^ int (int count){return count;};//完整形式
NSLog(@"%d",blockName(10));
MRC
mrc下捕获自动变量的block会被存储在栈区,调用copy可以被复制到堆上
但是以下不需要主动复制
- 调用copy
- 传递给usingBlock方法
- GCD函数的参数
ARC
arc下捕获自动变量的block会从栈区复制到堆区,这是编译器默认做的
但并不是说,ARC下不存在栈区的block;如果栈上的block没有赋值给
- strong类型属性,
- copy类型属性,
- __strong类型的实例变量,
- 作为函数返回值,
- 传递给函数作为参数,
那么block不会自动复制到堆上
截获自动变量
首先我们看下变量所有类型
- 自动变量(局部变量)
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
block对自动变量的截获如下:
int var = 10;
void (^blockVar)() = ^{
NSLog(@"%d",var);
};
blockVar();
但是不能对截获的自动变量修改
NSMutableArray *array = [NSMutableArray array];
void (^blockVar)() = ^{
[array addObject:[[NSObject alloc] init]];//正确,因为没有对array赋值,只是向array添加数据
array = [NSMutableArray array];//错误,改变了array的数值
};
以上代码中,没有对array的值修改,是可以的
但是不能访问c语言数组类型,即不能捕获c语言数组
char c[10] = {'q','q'};
void (^blockVar)() = ^{
c[0];//编译报错
};
但是c语言数组,我们可以通过指针的方式访问
char *q = @"ustb";
void (^blockVar)() = ^{
q[0];//合法
};
截获自动变量是值拷贝,在由block编译生成的结构体中,声明与自动变量相对应的成员变量来存放截获的自动变量值,用于存放捕获的自动变量的值
MRC下编译器不会默认地把block拷贝到堆区,需要手动调用copy;ARC下,如果赋值给强指针会默认把block拷贝到堆区
截获对象
#import
#import "AppDelegate.h"
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
blk_t blk;
{
id arrayM = [NSMutableArray array];
blk = ^(id obj){
[arrayM addObject:obj];
NSLog(@"%ld",[arrayM count]);
};//arc环境下默认会拷贝到堆
}
blk([[NSObject alloc] init]);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我们可以看到在堆上的block对截获的对象会有retain操作,并不会在对象作用域结束时被释放
我们再来看下在栈上的block会怎么样
#import
#import "AppDelegate.h"
#import "TestObject.h"
extern int _objc_rootRetainCount(id);
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
__unsafe_unretained void (^block)(id);
id array = [NSMutableArray array];
block = ^(id obj){
[array addObject:obj];//不会retain array
NSLog(@"%@",array);
};
[array release];
block([[NSObject alloc] init]);//EXC_BAD_ACCESS
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我们看一下示例代码
#import
#import "AppDelegate.h"
#import "TestObject.h"
extern int _objc_rootRetainCount(id);
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
__unsafe_unretained void (^block)(id);
{
id array = [NSMutableArray array];
block = ^(id obj){
[array addObject:obj];//MRC下EXC_BAD_ACCESS,ARC下array为nil
NSLog(@"%@",array);
};//无论MRC,ARC,block都在栈上
// [array release];
}
block([[NSObject alloc] init]);//不管MRC,ARC都没有来的急释放,按理说超出作用域,栈上的对象会被回收的
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
可以看出栈上的block捕获的对象,相应的成员变量不会强引用被捕获的对象,但是当对象被释放的时候,如果是ARC环境,那么相应的成员变量会被负值nil;如果是MRC,不会负值nil,这时如果访问,会报EXC_BAD_ACCESS错误
截获对象且用__block修饰
__block修饰对象在mrc和ARC下不同,在ARC下会增加对象的引用计数,mrc不会增加引用计数,因此在mrc下可以用来防止引用计数
ARC下防止引用循环有两种方式,__block,__weak
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) void (^t)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block ViewController *strongSelf = self;
_t = [^{
NSLog(@"%@",Sself);
strongSelf = nil;//付空,打破引用循环
} copy];
}
- (void)dealloc
{
NSLog(@"view dealloc!!!!!");
}
@end
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) void (^t)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak ViewController *weakSelf = self;
_t = [^{
NSLog(@"%@",weakSelf);
} copy];
}
- (void)dealloc
{
NSLog(@"view dealloc!!!!!");
}
@end
MRC下使用 __block 防止引用循环
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, copy) void (^t)();
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__block ViewController *WeakSelf = self;
_t = [^{
NSLog(@"%@",WeakSelf);//__block在MRC下的作用是防止循环引用,不会增加引用计数,这和ARC不同(这在官方文档Transitioning to ARC Release Notes有详细解释)
} copy];
}
- (void)dealloc
{
[super dealloc];
[_t release];
NSLog(@"view dealloc!!!!!");
}
@end
截获自动变量且用__block 修饰
__block修饰变量,会把变量作为成员变量放到结构体里,如果使用__block变量的block被拷贝到堆上,那么__block变量也会被拷贝到堆上
循环引用
对于循环引用的问题,mrc下通过__block解决,arc下通过__weak解决,或者在block执行完时手动置相应指针nil打破循环引用,__weak和__block不能同时使用
Block的实现
block的实质
进入项目目录,然后我们可以通过 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxx.m
指令将xxx.m文件编译成.cpp文件
//objective-c
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^blk)() = ^{printf("Block\n");};
blk();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
转换C++代码如下:
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;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//在栈上,但是打印出来是__NSGlobalBlock__,按理说应该就是应该在全局区的,这个地方编译的时候可能有误差
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
我们看到原先的代码块,编译之后,变成了结构体
命名规则是根据block所属函数(main函数)和block在函数中出现(此处0)的位置命名的
我们看到结构体中含有isa指针,说明这是一个objective-c对象
所以我们说block是objective-c对象
截获自动变量值的实质
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
int var = 10;
const char *fmt = "var = %d\n";
void (^blk)() = ^{printf(fmt,var);};
var = 2;
fmt = "These values changed. var = %d\n";
blk();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
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;
const char *fmt;
int var;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _var, int flags=0) : fmt(_fmt), var(_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int var = __cself->var; // bound by copy
printf(fmt,var);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int var = 10;
const char *fmt = "var = %d\n";
void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, var));
var = 2;
fmt = "These values changed. var = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
我们看到捕获的自动变量被作为成员变量添加到__main_block_impl_0中啦,结构体如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int var;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _var, int flags=0) : fmt(_fmt), var(_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在初始化结构体时,根据传递给构造函数的参数对由自动变量添加的成员变量初始化
我们可以看到,这仅仅是值传递,所以在block的外部修改自动变量不会影响block内的值,因此block也禁止在block内对相应的成员变量修改
截获自动变量的意思指:声明block语法时,block内使用的自动变量被保存在block内的结构体中
__block说明符
如果想在block中修改截获的自动变量的值,有两种方式,第一c语言有一个变量允许在block内写值,
- 静态变量
- 静态全局变量
- 全局变量
虽然block的匿名函数部分简单的转换成c语言的函数,但从这个变换的函数中访问静态变量,全局变量没有改变
#import
#import "AppDelegate.h"
static int intS;
int intE;
int main(int argc, char * argv[]) {
@autoreleasepool {
int var = 10;
static int inlineInt = 10;
const char *fmt = "var = %d\n";
static NSString *str = @"ustb";
void (^blk)() = ^{printf(fmt,var);
intS = 10;
intE = 10;
inlineInt = 100;
str = @"value changed ustb";
};
var = 2;
fmt = "These values changed. var = %d\n";
blk();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static int intS;
int intE;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int var;
int *inlineInt;
NSString **str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _var, int *_inlineInt, NSString **_str, int flags=0) : fmt(_fmt), var(_var), inlineInt(_inlineInt), str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int var = __cself->var; // bound by copy
int *inlineInt = __cself->inlineInt; // bound by copy
NSString **str = __cself->str; // bound by copy
printf(fmt,var);
intS = 10;
intE = 10;
(*inlineInt) = 100;
(*str) = (NSString *)&__NSConstantStringImpl__var_folders_wh_ycjfh9y54fbg8172sbbfcsbc0000gp_T_main_a2e925_mi_1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int var = 10;
static int inlineInt = 10;
const char *fmt = "var = %d\n";
static NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_wh_ycjfh9y54fbg8172sbbfcsbc0000gp_T_main_a2e925_mi_0;
void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, var, &inlineInt, &str, 570425344));
var = 2;
fmt = "These values changed. var = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
我们可以看到全局变量和静态全局变量并没有加入到block结构体中,静态局部变量,会以指针的形式访问
个人理解:自动变量在超出作用域后会被释放,所以block内存放的是值拷贝,不可修改,但是全局变量和静态变量,并不会被销毁所以我们可以在block内公用一份复本,可以修改,特别的,静态全局变量超出作用域后无法直接访问,所以用block内添加指针成员变量,指向静态局部变量
第二使用__block修饰符
__block说明符像static,auto,register一样,用于说明变量存放在哪个地方
#import
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
__block int var = 10;
void (^blk)() = ^{
var = 1;
};
blk();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_var_0 {
void *__isa;
__Block_byref_var_0 *__forwarding;
int __flags;
int __size;
int var;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_var_0 *var; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__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_var_0 *var = __cself->var; // bound by ref
(var->__forwarding->var) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 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(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};
void (*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
我们可以看到多了结构
struct __Block_byref_var_0 {
void *__isa;
__Block_byref_var_0 *__forwarding;
int __flags;
int __size;
int var;
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}
这就是说__block修饰的变量作为成员变量被放在结构体里,我们称被__block修饰的变量叫__block变量
BLOCK存储域
block分三个存储区:
- 全局区(_NSConcreteGlobalBlock)
- 栈区(_NSConcreteStackBlock)
- 堆区(_NSConcreteMallocBlock)
除了全局变量,静态变量,静态全局变量外没有捕获其他自动变量的block会被存放在全局区,结构体isa = &_NSConcreteGlobalBlock;
对栈上的对象retain是不管用的
#import
#import "AppDelegate.h"
#import "TestObject.h"
extern int _objc_rootRetainCount(id);
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
int var = 10;
void (^b)() = ^{
printf("%d\n",var);
};
NSLog(@"%@",b);
NSLog(@"%d",_objc_rootRetainCount(b));
[b retain];
NSLog(@"%@",b);
NSLog(@"%d",_objc_rootRetainCount(b));
[b release];
NSLog(@"%@",b);
NSLog(@"%d",_objc_rootRetainCount(b));
b();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
习题:
#import
#import "AppDelegate.h"
#import "TestObject.h"
extern int _objc_rootRetainCount(id);
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^block)(id);
TestObject *obj = [[TestObject alloc] init];
block = [^(id obj){
NSLog(@"%@",obj);
} copy];//TestObject类型的obj对象没有retain操作
block(obj);
[obj release];//调用完,obj被释放
[block release];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
对比:
#import
#import "AppDelegate.h"
#import "TestObject.h"
extern int _objc_rootRetainCount(id);
typedef void (^blk_t)(id);
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^block)();
TestObject *obj = [[TestObject alloc] init];
block = [^{
NSLog(@"%@",obj);
} copy];//TestObject类型的obj对象有retain操作
[obj release];//调用完,obj不会被释放
[block release];//调用完,obj会被释放
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
修饰符问题:
ARC下:
void (^blk)();//默认强引用
__weak void (^blk)(); //blk编译为指向结构体的指针,类型为weak类型,修饰的是blk,block不会复制到堆上
__block void (^blk)();//blk会作为结构体成员变量
截获__block变量,其实截获blk所在结构体的指针,blk是strong类型