[精通Objective-C]专家级技巧:使用ARC
参考书籍:《精通Objective-C》【美】 Keith Lee
PS:博主并不是专家,不敢认定专家级技巧,该专家级技巧是由原书作者认定的。
目录
- 精通Objective-C专家级技巧使用ARC
- 目录
- ARC和对象所有权
- 测试ARC
- Objective-C 桥接
- 直接桥接
- ARC桥接转换
ARC和对象所有权
之前的章节[精通Objective-C]内存管理已经提到过ARC的基本概念和使用方式。ARC通过(在编译时)插入代码,使向对象发送的retain和release消息达到平衡,从而自动化了该任务。ARC禁止程序员手动控制对象的生命周期,因此,了解ARC内存管理方式中的对象所有权规则就非常重要。
Objective-C程序能够获得由名称以alloc、new、copy或mutableCopy开头的方法创建的任何对象的所有权。如下所示,main()函数用alloc方法创建一个Atom对象,因而声明了这个对象的所有权。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Atom *atom = [[Atom alloc] init];
}
return 0;
}
如前所述,ARC禁止以手动方式向对象发送release、autorelease、dealloc或其他相关的消息。在执行以下3种操作时才会放弃对象的所有权:1.重新分配变量;2.将nil赋予变量;3.释放对象的所有者。
1.重新分配变量
Atom *atom = [[Atom alloc] initWithName:@"Atom1"];
atom = [[Atom alloc] initWithName:@"Atom2"];
这段代码首先创建了一个Atom对象(命名为Atom1),并将之赋予变量atom,稍后又将另一个Atom对象(命名为Atom2)赋予变量atom。因为变量atom已经被重新分配给Atom2,所以Atom1会失去一个所有者,而且如果没有其他所有者的话,它就会被释放掉。
2.将nil赋予变量
Atom *atom = [[Atom alloc] init];
atom = nil;
这段代码创建一个Atom对象,并将之赋予变量atom,稍后将变量atom设置为nil,由于变量atom已被设置为nil,所以Atom会失去一个所有者,而且ARC会在将变量atom设置为nil的语句后面,插入向这个Atom对象发送release消息的代码。
3.释放对象的所有者
@interface OrderEntry : NSObject
{
@public OrderItem *item;
NSString *orderId;
Address *shippingAddress;
}
OrderEntry类的初始化方法会创建其两个子类OrderItem类和Address类的实例,因此,当程序创建并初始化一个OrderEntry对象时,会声明它拥有这些子对象的所有权。如果程序释放了该OrderEntry对象,ARC会自动向它的子对象发送release消息。
测试ARC
这里继续使用[精通Objective-C]内存管理中创建的工程,也可以重新创建一个。下面是测试时需要用到的3个类。
Address类不用做任何改动
#import <Foundation/Foundation.h>
@interface Address : NSObject
@end
#import "Address.h"
@implementation Address
-(id) init{
if ((self = [super init])) {
NSLog(@"Initializing Address object");
}
return self;
}
-(void) dealloc{
NSLog(@"Deallocating Address object");
}
@end
OrderItem类中把实例变量name修改为只读属性
#import <Foundation/Foundation.h>
@interface OrderItem : NSObject
@property(readonly) NSString *name;
-(id) initWithName:(NSString *)itemName;
@end
#import "OrderItem.h"
@implementation OrderItem
-(id) initWithName:(NSString *)itemName{
if ((self = [super init])) {
_name = itemName;
NSLog(@"Initializing OrderItem object %@", _name);
}
return self;
}
-(void) dealloc{
NSLog(@"Deallocation OrderItem object %@", self.name);
}
@end
OrderEntry类中,把orderId,item两个实例变量修改为只读属性,并更新初始化方法
#import <Foundation/Foundation.h>
#import "OrderItem.h"
#import "Address.h"
@interface OrderEntry : NSObject
{
Address *shippingAddress;
}
@property(readonly) NSString *orderId;
@property(readonly) OrderItem *item;
-(id) initWithId:(NSString *)oid name:(NSString *)order;
@end
#import "OrderEntry.h"
@implementation OrderEntry
-(id) initWithId:(NSString *)oid name:(NSString *)order{
if ((self = [super init])) {
NSLog(@"Initializing OrderEntry object");
_orderId = oid;
_item = [[OrderItem alloc] initWithName:order];
shippingAddress = [[Address alloc] init];
}
return self;
}
-(void) dealloc{
NSLog(@"Deallocation OrderEntry object with ID %@",self.orderId);
}
@end
接下来在main.m中进行调试:
#import <Foundation/Foundation.h>
#import "OrderEntry.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个OrderEntry对象
OrderEntry *entry1 = [[OrderEntry alloc] initWithId:@"A-1" name:@"2 Hot dogs"];
NSLog(@"Order 1, ID = %@, item = %@", entry1.orderId, entry1.item.name);
// 创建另一个OrderEntry对象
OrderEntry *entry2 = [[OrderEntry alloc] initWithId:@"A-2" name:@"Cheeseburger"];
NSLog(@"Order 2, ID = %@, item = %@", entry2.orderId, entry2.item.name);
// 向集合中添加两个OrderEntry对象
NSArray *entries = [[NSArray alloc] initWithObjects:entry1, entry2, nil];
NSLog(@"Number of order entries = %li", [entries count]);
// 将指向OrderEntry对象的变量这只为nil,ARC会向该对象发送一条release消息
NSLog(@"Setting entry2 variable to nil");
entry2 = nil;
// 将指向对象集的变量这只为nil,ARC会向其中包含的每个对象发送一条release消息
NSLog(@"Setting entries to nil");
entries = nil;
// 将指向OrderEntry对象的变量这只为nil,ARC会向该对象发送一条release消息
NSLog(@"Setting entry1 variable to nil");
entry1 = nil;
// 退出自动释放池代码块
NSLog(@"Leaving autoreleasepool block");
}
return 0;
}
得到的运行结果如下:
2016-07-05 17:16:11.004 ARC Orders[18242:188553] Initializing OrderEntry object
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Initializing OrderItem object 2 Hot dogs
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Initializing Address object
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Order 1, ID = A-1, item = 2 Hot dogs
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Initializing OrderEntry object
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Initializing OrderItem object Cheeseburger
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Initializing Address object
2016-07-05 17:16:11.005 ARC Orders[18242:188553] Order 2, ID = A-2, item = Cheeseburger
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Number of order entries = 2
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Setting entry2 variable to nil
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Setting entries to nil
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocation OrderEntry object with ID A-2
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocation OrderItem object Cheeseburger
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocating Address object
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Setting entry1 variable to nil
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocation OrderEntry object with ID A-1
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocation OrderItem object 2 Hot dogs
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Deallocating Address object
2016-07-05 17:16:11.006 ARC Orders[18242:188553] Leaving autoreleasepool block
现在对结果进行分析,首先初始化了两个OrderEntry类的对象entry1和entry2,然后将两个OrderEntry对象添加到了NSArray实例之中,之后将变量entry2设置为nil,然而由于NSArray实例仍旧拥有这个OrderEntry对象,所以该对象不会被释放,接下来将变量entries设置为空,这会导致NSArray实例被释放,而且ARC还会向该集合中的每个对象都发送一套release消息。因为entry2对象现在没有其他拥有者了,因此这时候它会被释放,而且所有依赖它的对象会被一起释放。最后将entry1也设置为nil,entry1和所有依赖它的对象也被一起释放。
Objective-C 桥接
ARC能够自动管理Objective-C对象和块对象的内存。而苹果公司提供的基于C语言的API软件库没有与ARC整合。因此,当通过动态方式为这些基于C语言的API分配内存时,必须手动管理内存。实际上,ARC不允许在Objective-C对象的指针和其他数据类型的指针之间进行直接转换,为了方便开发人员在Objective-C程序中使用基于C语言的API,苹果公司提供了如直接桥接和ARC桥接转换等多种机制。
直接桥接
苹果公司为基于C语言的Core Foundation框架和基于Objective-C的Foundation框架中的许多数据类型提供了互用性,这种功能称为直接桥接,以下是一些较常用的直接桥接数据类型。
Core Foundation框架数据类型 | Foundation框架数据类型 |
---|---|
CFArrayRef | NSArray |
CFDataRef | NSData |
CFDateRef | NSDate |
CFDictionaryRef | NSDictionary |
CFMutableArrayRef | NSMutableArray |
CFMutableDataRef | NSMutableData |
CFMutableDictionaryRef | NSMutableDictionary |
CFTableSetRef | NSTableSet |
CFTableStringRef | NSTableString |
CFNumberRef | NSNumber |
CFReadStreamRef | NSInputStream |
CFWriteStreamRef | NSOutputStream |
CFSetRef | NSSet |
CFStringRef | NSString |
以下是一个用直接桥接的方式将CFStringRef类型变量用作参数的Objective-C方法:
int main(int argc, const char * argv[]) {
@autoreleasepool {
CFStringRef cstr = CFStringCreateWithCString(NULL, "Hello,World!", kCFStringEncodingASCII);
// 等价于 NSArray *data = [NSArray arrayWithObject:(NSString *)cstr];
NSArray *data = [NSArray arrayWithObject:cstr];
NSLog(@"Array size = %ld", [data count]);
}
return 0;
}
但是在使用ARC时,直接桥接是无法通过编译的,这时候就需要使用ARC桥接转换。
ARC桥接转换
ARC桥接转换的操作必须将特殊标志__bridge,__bridge_retained和__bridge_transfer用作前缀转换。
使用__bridge标记可以在不改变所有权的情况下,将对象从Core Foundation框架数据类型转换为Foundation框架数据类型(反正亦然)
使用__bridge_retained标记可以将Foundation框架数据类型对象转化为Core Foundation框架数据类型对象,并从ARC接管对象的所有权,对象将可以手动管理。
使用__bridge_transfer标记可以将Core Foundation框架数据类型对象转化为Foundation框架数据类型对象,并将对象的所有权交给ARC管理。
以下是使用不同前缀来进行ARC桥接转换的例子:
int main(int argc, const char * argv[]) {
@autoreleasepool {
CFStringRef cstr = CFStringCreateWithCString(NULL, "Hello,World!", kCFStringEncodingASCII);
// 使用__bridge标记不会改变对象所有权管理方式,所以必须手动管理CFStringRef类型对象的声明周期
NSArray *data = [NSArray arrayWithObject:(__bridge NSString *)cstr];
// 如果注释掉该语句,程序可以正常输出结果,但通过Product->Analyze会检测出内存泄漏,需要手动释放cstr
CFRelease(cstr);
NSLog(@"Array size = %ld", [data count]);
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
CFStringRef cstr = CFStringCreateWithCString(NULL, "Hello,World!", kCFStringEncodingASCII);
// 使用__bridge_transfer标记会将对象所有权交给ARC自动管理
NSArray *data = [NSArray arrayWithObject:(__bridge_transfer NSString *) cstr];
NSLog(@"Array size = %ld", [data count]);
}
return 0;
}