[精通Objective-C]进阶技巧:使用ARC

[精通Objective-C]进阶技巧:使用ARC

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

目录

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框架数据类型
CFArrayRefNSArray
CFDataRefNSData
CFDateRefNSDate
CFDictionaryRefNSDictionary
CFMutableArrayRefNSMutableArray
CFMutableDataRefNSMutableData
CFMutableDictionaryRefNSMutableDictionary
CFTableSetRefNSTableSet
CFTableStringRefNSTableString
CFNumberRefNSNumber
CFReadStreamRefNSInputStream
CFWriteStreamRefNSOutputStream
CFSetRefNSSet
CFStringRefNSString

以下是一个用直接桥接的方式将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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值