ios开发之block的使用,及注意事项
2015-12-03 18:00
645 查看
Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return
cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅
实现函数的功能,还能携带函数的执行环境。
可以这样理解,Block其实包含两个部分内容
Block执行的代码,这是在编译的时候已经生成好的;
一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。
Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象,后面将详细说明)。
Block基本语法
基本语法在本文就不赘述了,同学们自学。
Block的类型与内存管理
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock,
NSMallocBlock。
NSGlobalBlock:类似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
1、NSGlobalBlock如下,我们可以通过是否引用外部变量识别,未引用外部变量即为NSGlobalBlock,可以当做函数使用。
{
//create a NSGlobalBlock
float (^sum)(float, float)
= ^(float a, float b){
return a + b;
};
NSLog(@"block
is %@", sum); //block is <__NSGlobalBlock__: 0x47d0>
}
NSStackBlock如下:
{
NSArray *testArr = @[@"1", @"2"];
void (^TestBlock)(void) =
^{
NSLog(@"testArr :%@",
testArr);
};
NSLog(@"block
is %@", ^{
NSLog(@"test Arr :%@",
testArr);
});
//block is <__NSStackBlock__: 0xbfffdac0>
//打印可看出block是一个 NSStackBlock, 即在栈上, 当函数返回时block将无效
NSLog(@"block is %@", TestBlock);
//block is <__NSMallocBlock__: 0x75425a0>
//上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock
//即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.
}
NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取,但是retain操作就不行,会在下面说明
Block的copy、retain、release操作 (还是copy一段)
不同于NSObjec的copy、retain、release操作:
Block_copy与copy等效,Block_release与release等效;
对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
NSGlobalBlock:retain、copy、release操作都无效;
NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry
addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock
copy到堆上,然后加入数组:[mutableAarry
addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
尽量不要对Block使用retain操作。
Block对外部变量的存取管理
基本数据类型
1、局部变量
局部自动变量,在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
{
int base = 100;
long (^sum)(int, int)
= ^ long (int a, int b)
{
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是103,而不是3, 因为块内base为拷贝的常量 100
}
2、STATIC修饰符的全局变量
因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.
{
static int base
= 100;
long (^sum)(int, int)
= ^ long (int a, int b)
{
base++;
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是4,而不是103, 因为base被设置为了0
printf("%d\n", base);
// 这里输出1, 因为sum中将base++了
}
3、__BLOCK修饰的变量
Block变量,被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
注:BLOCK被另一个BLOCK使用时,另一个BLOCK被COPY到堆上时,被使用的BLOCK也会被COPY。但作为参数的BLOCK是不会发生COPY的
OBJC对象
block对于objc对象的内存管理较为复杂,这里要分static
global local block变量分析、还要分非arc和arc分析
非ARC中的变量
先看一段代码(非arc)
@interface MyClass : NSObject {
NSObject* _instanceObj;
}
@end
@implementation MyClass
NSObject* __globalObj = nil;
- (id) init {
if (self =
[super init])
{
_instanceObj = [[NSObject alloc] init];
}
return self;
}
- (void) test {
static NSObject*
__staticObj = nil;
__globalObj = [[NSObject alloc] init];
__staticObj = [[NSObject alloc] init];
NSObject* localObj = [[NSObject alloc] init];
__block NSObject*
blockObj = [[NSObject alloc] init];
typedef void (^MyBlock)(void)
;
MyBlock aBlock = ^{
NSLog(@"%@", __globalObj);
NSLog(@"%@", __staticObj);
NSLog(@"%@", _instanceObj);
NSLog(@"%@", localObj);
NSLog(@"%@", blockObj);
};
aBlock = [[aBlock copy] autorelease];
aBlock();
NSLog(@"%d", [__globalObj
retainCount]);
NSLog(@"%d", [__staticObj
retainCount]);
NSLog(@"%d", [_instanceObj
retainCount]);
NSLog(@"%d", [localObj retainCount]);
NSLog(@"%d", [blockObj retainCount]);
}
@end
int main(int argc, char *argv[])
{
@autoreleasepool {
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];
return 0;
}
}
执行结果为1 1 1 2 1。
__globalObj和__staticObj在内存中的位置是确定的,所以Block
copy时不会retain对象。
_instanceObj在Block copy时也没有直接retain
_instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。
localObj在Block copy时,系统自动retain对象,增加其引用计数。
blockObj在Block copy时也不会retain。
ARC中的变量测试
由于arc中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。因此我们可以通过下面的代码来测试。
代码片段1(globalObject全局变量)
NSString *__globalString = nil;
- (void)testGlobalObj
{
__globalString = @"1";
void (^TestBlock)(void) =
^{
NSLog(@"string
is :%@", __globalString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
__globalString = nil;
TestBlock();
}
- (void)testStaticObj
{
static NSString *__staticString
= nil;
__staticString = @"1";
printf("static address: %p\n",
&__staticString); //static address: 0x6a8c
void (^TestBlock)(void) =
^{
printf("static address: %p\n",
&__staticString); //static address: 0x6a8c
NSLog(@"string
is : %@", __staticString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
__staticString = nil;
TestBlock();
}
- (void)testLocalObj
{
NSString *__localString = nil;
__localString = @"1";
printf("local
address: %p\n", &__localString); //local address: 0xbfffd9c0
void (^TestBlock)(void) =
^{
printf("local address: %p\n",
&__localString); //local address: 0x71723e4
NSLog(@"string is : %@",
__localString); //string is : 1
};
__localString = nil;
TestBlock();
}
- (void)testBlockObj
{
__block NSString *_blockString
= @"1";
void (^TestBlock)(void) =
^{
NSLog(@"string
is : %@", _blockString); // string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
_blockString = nil;
TestBlock();
}
- (void)testWeakObj
{
NSString *__localString = @"1";
__weak NSString *weakString
= __localString;
printf("weak
address: %p\n", &weakString); //weak address: 0xbfffd9c4
printf("weak
str address: %p\n", weakString); //weak str address: 0x684c
void (^TestBlock)(void) =
^{
printf("weak address: %p\n",
&weakString); //weak address: 0x7144324
printf("weak
str address: %p\n", weakString); //weak str address: 0x684c
NSLog(@"string is : %@",
weakString); //string is :1
};
__localString = nil;
TestBlock();
}
由以上几个测试我们可以得出:
1、只有在使用local变量时,block会复制指针,且强引用指针指向的对象一次。其它如全局变量、static变量、block变量等,block不会拷贝指针,只会强引用指针指向的对象一次。
2、即时标记了为__weak或__unsafe_unretained的local变量。block仍会强引用指针对象一次。(这个不太明白,因为这种写法可在后面避免循环引用的问题)
循环引用retain
cycle
循环引用指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。如声明一个delegate时一般用assign而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。在以往的项目中,我几次用动态内存检查发现了循环引用导致的内存泄露。
这里讲的是block的循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用,如:
- (void)dealloc
{
NSLog(@"no
cycle retain");
}
- (id)init
{
self =
[super init];
if (self)
{
#if TestCycleRetainCase1
//会循环引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//会循环引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不会循环引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不会循环引用
__unsafe_unretained TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif
NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void)doSomething
{
NSLog(@"do
Something");
}
int main(int argc, char *argv[])
{
@autoreleasepool {
TestCycleRetain* obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
经过上面的测试发现,在加了__weak和__unsafe_unretained的变量引入后,TestCycleRetain方法可以正常执行dealloc方法,而不转换和用__block转换的变量都会引起循环引用。
因此防止循环引用的方法如下:
__unsafe_unretained TestCycleRetain *weakSelf = self;
end
补充:
In manual reference
counting mode,
the effect of not retaining
In ARC mode,
to retaining
like all other values). To get the manual reference counting mode behavior under ARC, you could use
however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use
you don’t need to support iOS 4 or OS X v10.6), or set the
to
break the retain cycle.
转自:http://my.oschina.net/u/1432769/blog/390401?fromerr=jv1tIXAQ
cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅
实现函数的功能,还能携带函数的执行环境。
可以这样理解,Block其实包含两个部分内容
Block执行的代码,这是在编译的时候已经生成好的;
一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。
Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象,后面将详细说明)。
Block基本语法
基本语法在本文就不赘述了,同学们自学。
Block的类型与内存管理
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock,
NSMallocBlock。
NSGlobalBlock:类似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
1、NSGlobalBlock如下,我们可以通过是否引用外部变量识别,未引用外部变量即为NSGlobalBlock,可以当做函数使用。
{
//create a NSGlobalBlock
float (^sum)(float, float)
= ^(float a, float b){
return a + b;
};
NSLog(@"block
is %@", sum); //block is <__NSGlobalBlock__: 0x47d0>
}
NSStackBlock如下:
{
NSArray *testArr = @[@"1", @"2"];
void (^TestBlock)(void) =
^{
NSLog(@"testArr :%@",
testArr);
};
NSLog(@"block
is %@", ^{
NSLog(@"test Arr :%@",
testArr);
});
//block is <__NSStackBlock__: 0xbfffdac0>
//打印可看出block是一个 NSStackBlock, 即在栈上, 当函数返回时block将无效
NSLog(@"block is %@", TestBlock);
//block is <__NSMallocBlock__: 0x75425a0>
//上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock
//即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.
}
NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取,但是retain操作就不行,会在下面说明
Block的copy、retain、release操作 (还是copy一段)
不同于NSObjec的copy、retain、release操作:
Block_copy与copy等效,Block_release与release等效;
对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
NSGlobalBlock:retain、copy、release操作都无效;
NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry
addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock
copy到堆上,然后加入数组:[mutableAarry
addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
尽量不要对Block使用retain操作。
Block对外部变量的存取管理
基本数据类型
1、局部变量
局部自动变量,在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。
{
int base = 100;
long (^sum)(int, int)
= ^ long (int a, int b)
{
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是103,而不是3, 因为块内base为拷贝的常量 100
}
2、STATIC修饰符的全局变量
因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.
{
static int base
= 100;
long (^sum)(int, int)
= ^ long (int a, int b)
{
base++;
return base + a + b;
};
base = 0;
printf("%ld\n",sum(1,2));
// 这里输出是4,而不是103, 因为base被设置为了0
printf("%d\n", base);
// 这里输出1, 因为sum中将base++了
}
3、__BLOCK修饰的变量
Block变量,被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
注:BLOCK被另一个BLOCK使用时,另一个BLOCK被COPY到堆上时,被使用的BLOCK也会被COPY。但作为参数的BLOCK是不会发生COPY的
OBJC对象
block对于objc对象的内存管理较为复杂,这里要分static
global local block变量分析、还要分非arc和arc分析
非ARC中的变量
先看一段代码(非arc)
@interface MyClass : NSObject {
NSObject* _instanceObj;
}
@end
@implementation MyClass
NSObject* __globalObj = nil;
- (id) init {
if (self =
[super init])
{
_instanceObj = [[NSObject alloc] init];
}
return self;
}
- (void) test {
static NSObject*
__staticObj = nil;
__globalObj = [[NSObject alloc] init];
__staticObj = [[NSObject alloc] init];
NSObject* localObj = [[NSObject alloc] init];
__block NSObject*
blockObj = [[NSObject alloc] init];
typedef void (^MyBlock)(void)
;
MyBlock aBlock = ^{
NSLog(@"%@", __globalObj);
NSLog(@"%@", __staticObj);
NSLog(@"%@", _instanceObj);
NSLog(@"%@", localObj);
NSLog(@"%@", blockObj);
};
aBlock = [[aBlock copy] autorelease];
aBlock();
NSLog(@"%d", [__globalObj
retainCount]);
NSLog(@"%d", [__staticObj
retainCount]);
NSLog(@"%d", [_instanceObj
retainCount]);
NSLog(@"%d", [localObj retainCount]);
NSLog(@"%d", [blockObj retainCount]);
}
@end
int main(int argc, char *argv[])
{
@autoreleasepool {
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];
return 0;
}
}
执行结果为1 1 1 2 1。
__globalObj和__staticObj在内存中的位置是确定的,所以Block
copy时不会retain对象。
_instanceObj在Block copy时也没有直接retain
_instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。
localObj在Block copy时,系统自动retain对象,增加其引用计数。
blockObj在Block copy时也不会retain。
ARC中的变量测试
由于arc中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。因此我们可以通过下面的代码来测试。
代码片段1(globalObject全局变量)
NSString *__globalString = nil;
- (void)testGlobalObj
{
__globalString = @"1";
void (^TestBlock)(void) =
^{
NSLog(@"string
is :%@", __globalString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
__globalString = nil;
TestBlock();
}
- (void)testStaticObj
{
static NSString *__staticString
= nil;
__staticString = @"1";
printf("static address: %p\n",
&__staticString); //static address: 0x6a8c
void (^TestBlock)(void) =
^{
printf("static address: %p\n",
&__staticString); //static address: 0x6a8c
NSLog(@"string
is : %@", __staticString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
__staticString = nil;
TestBlock();
}
- (void)testLocalObj
{
NSString *__localString = nil;
__localString = @"1";
printf("local
address: %p\n", &__localString); //local address: 0xbfffd9c0
void (^TestBlock)(void) =
^{
printf("local address: %p\n",
&__localString); //local address: 0x71723e4
NSLog(@"string is : %@",
__localString); //string is : 1
};
__localString = nil;
TestBlock();
}
- (void)testBlockObj
{
__block NSString *_blockString
= @"1";
void (^TestBlock)(void) =
^{
NSLog(@"string
is : %@", _blockString); // string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null)
};
_blockString = nil;
TestBlock();
}
- (void)testWeakObj
{
NSString *__localString = @"1";
__weak NSString *weakString
= __localString;
printf("weak
address: %p\n", &weakString); //weak address: 0xbfffd9c4
printf("weak
str address: %p\n", weakString); //weak str address: 0x684c
void (^TestBlock)(void) =
^{
printf("weak address: %p\n",
&weakString); //weak address: 0x7144324
printf("weak
str address: %p\n", weakString); //weak str address: 0x684c
NSLog(@"string is : %@",
weakString); //string is :1
};
__localString = nil;
TestBlock();
}
由以上几个测试我们可以得出:
1、只有在使用local变量时,block会复制指针,且强引用指针指向的对象一次。其它如全局变量、static变量、block变量等,block不会拷贝指针,只会强引用指针指向的对象一次。
2、即时标记了为__weak或__unsafe_unretained的local变量。block仍会强引用指针对象一次。(这个不太明白,因为这种写法可在后面避免循环引用的问题)
循环引用retain
cycle
循环引用指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。如声明一个delegate时一般用assign而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。在以往的项目中,我几次用动态内存检查发现了循环引用导致的内存泄露。
这里讲的是block的循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用,如:
- (void)dealloc
{
NSLog(@"no
cycle retain");
}
- (id)init
{
self =
[super init];
if (self)
{
#if TestCycleRetainCase1
//会循环引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//会循环引用
__block TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不会循环引用
__weak TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不会循环引用
__unsafe_unretained TestCycleRetain *weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif
NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void)doSomething
{
NSLog(@"do
Something");
}
int main(int argc, char *argv[])
{
@autoreleasepool {
TestCycleRetain* obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
经过上面的测试发现,在加了__weak和__unsafe_unretained的变量引入后,TestCycleRetain方法可以正常执行dealloc方法,而不转换和用__block转换的变量都会引起循环引用。
因此防止循环引用的方法如下:
__unsafe_unretained TestCycleRetain *weakSelf = self;
end
补充:
In manual reference
counting mode,
__block id x;has
the effect of not retaining
x.
In ARC mode,
__block id x;defaults
to retaining
x(just
like all other values). To get the manual reference counting mode behavior under ARC, you could use
__unsafe_unretained __block id x;. As the name
__unsafe_unretainedimplies,
however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use
__weak(if
you don’t need to support iOS 4 or OS X v10.6), or set the
__blockvalue
to
nilto
break the retain cycle.
转自:http://my.oschina.net/u/1432769/blog/390401?fromerr=jv1tIXAQ
相关文章推荐
- iOS在线安装环境搭建
- iOS常用宏定义
- iOS8--关于regular和compact的理解
- 一切重新开始--一个iOS工程师的工作之旅-001
- iOS用户点击退出按钮,跳到登录界面的代码控制
- iOS开发 @synthesize + @property 用法
- nagios 被监控端一键配置脚本
- iOS 事件处理机制与图像渲染过程
- iOS-数据持久化-第三方框架FMDB的使用
- 项目适配iOS9 遇到的坑坑娃娃
- iOS-cocopods换源
- 一个ios手势密码功能实现
- IOS-TextField控件详解
- 开发讯飞语音插件--iOS
- ios blog收集
- iOS——RSA加密方法
- iOS 开发过程中----清理缓存的实现
- ios 9 适配
- ios 内购资料
- 如何启动instruments