引子
作为ios初学者,看了许多关于介绍Block的文章,但是终究都是纸上谈兵,这次我们来实践以下,只为加强理解和记忆。
对于Block的理解可以参考以下文章:
Block编程值得注意的那些事儿(使用)
ios之Block研究 (Block内部数据结构)
谈Objective-C Block的实现 (Block内部数据结构)
对Objective-C中Block的追探 (Block内部数据结构,讲得很好)
例子
这里,定义一个类TestBlock,包含一个成员变量m_age,以及一个成员方法AccessVarInBlock
TestBlock.h与TestBlock.m的实现
//
// TestBlock.m
// BlockDemo
//
// Created by fenglh on 14-8-18.
// Copyright (c) 2014年 yons. All rights reserved.
//
#import "TestBlock.h"
@implementation TestBlock
@synthesize m_age;
//初始化
- (id) init:(int)age
{
self = [super init];
if (nil != self) {
m_age = age;
}
return self;
}
- (void)AccessVarInBlock
{
typedef void ( ^ MyBlock )(void);
int outside_age=20; //Block 外部的整形变量
int *p_age = &outside_age; //Block 外部的指针变量
MyBlock aBlock = ^(){ //start Block
NSLog(@" class member variable:\tm_age = %d", self.m_age);
NSLog(@" outside variable: \t\toutside_age = %d", outside_age);
NSLog(@" outside point variable:p_age = %d", *p_age);
}; //end Block
self.m_age = 100; //语句1
*p_age = 100; //语句2
outside_age = 100;//语句3
aBlock();
}
@end
main函数:
//
// main.m
// BlockDemo
//
// Created by fenglh on 14-8-18.
// Copyright (c) 2014年 yons. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TestBlock.h"
int main(int argc, const char * argv[])
{
TestBlock *test = [[TestBlock alloc ] init:10];
[test AccessVarInBlock];
}
输出结果:
2014-08-18 23:40:35.334 BlockDemo[2136:303] class member variable: m_age = 100
2014-08-18 23:40:35.336 BlockDemo[2136:303] outside variable: outside_age = 20
2014-08-18 23:40:35.337 BlockDemo[2136:303] outside point variable:p_age = 100
先看,AccessVarInBlock方法,如果把代码1、2、3(即aBlock(); 上面三句)都注释掉的话,其输出结果为:
2014-08-18 23:35:06.513 BlockDemo[2111:303] class member variable: m_age = 10
2014-08-18 23:35:06.516 BlockDemo[2111:303] outside variable: outside_age = 20
2014-08-18 23:35:06.517 BlockDemo[2111:303] outside point variable:p_age = 20
可得出以下结论:
1. 指针变量和类成员变量,在Block的内部实现中,是一种引用而非拷贝。
2. 其他基本类型的变量,在Block的内部实现中,是一种拷贝。
简单探讨Block内部实现
1.先来看一段简单的代码
文件:block.c
include <stdio.h>
int main()
{
int i = 10;
int *p =&i;
typedef void (^ typeBlock )(void);
typeBlock aBlock = ^{
printf("i = %d\n", i);
printf("*p = %d", *p);
} ;
aBlock();
return 0;
}
你可以在终端下找到该工具所在的目录,命令“ sudo find / -name "clang" ”
编译使用clang -rewrite-objc block.c 命令,有时候需要设置包含文件
3.编译后会产生一个block.cpp文件,查看以下关键的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i;// 定义一个整形变量(并非引用)
int *p; //定义了一个指针变量,也就是一个引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int *_p, int flags=0) : i(_i), p(_p) {//构造函数,赋值
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以发现:
1)原本的变量i,在block的内部重新定义了一个i并且在该结构体的构造函数进行赋值,也就是对i进行了一份拷贝。
2)指针变量p ,在block内部也有一个变量p,但是在构造函数的时候,它是指针地址的赋值,也就是说是一份引用。
3)可以猜测,类成员变量,在block的内部数据结构中也是类似一个指针的这样的实现!
4.其他知识点(暂做简单记录)
1)__block 修饰符
2)block的类型有三种:_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
3)block的copy, copy了block后一定要release,防止内存泄露!
参考文章:对Objective-C中Block的追探,文章描述如下:
我在这里想探讨的另外一个问题是设计原则,对于一个对象,当外传的时候我们都会想着把对象autorelease掉,比如:
- (NSArray *) myTestArray { NSArray *array = [[NSArray alloc] initWithObjects: @"a", @"b", @"c", nil]; return [array autorelease]; }
同样,我们在向外传递block的时候一定也要做到,传给外面一个在堆上的,autorelease的对象。
- (blk) myTestBlock { __blockint val = 10; blk stackBlock = ^{NSLog(@"val = %d", ++val);}; return [[stackBlock copy] autorelease]; }
第一步,copy将block上从栈放到堆上,第二步,autorelease防止内存泄露。
同样,有时我们会去将block放到别的类中做回调,如放到AFNetworking中的回调。
这时根据统一的设计原则,我们也应该给调用对象一个堆上的autorelease的对象。
总之,在把block对象外传的时候,我们要传出一个经过copy,再autorelease的block在堆上的__NSMallocBlock__对象。(个人观点,block是模仿NSObject对象发明的,就不要让调用方做与其他对象不一样的事)
注:其他关于block内部各种数据结构的说明可以查看文章开始说的 参考文章!!如有错误,请指正,共同进步!