码迷,mamicode.com
首页 > 其他好文 > 详细

[精通Objective-C]专家级技巧:使用ARC

时间:2016-07-06 00:31:00      阅读:224      评论:0      收藏:0      [点我收藏+]

标签:

[精通Objective-C]专家级技巧:使用ARC

参考书籍:《精通Objective-C》【美】 Keith Lee

PS:博主并不是专家,不敢认定专家级技巧,该专家级技巧是由原书作者认定的。

目录

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;
}

[精通Objective-C]专家级技巧:使用ARC

标签:

原文地址:http://blog.csdn.net/sps900608/article/details/51833840

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!