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

[精通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;
}
时间: 2024-10-11 07:59:14

[精通Objective-C]专家级技巧:使用ARC的相关文章

Objective - C基础: 第六天 - 2.ARC转换以及第三方框架的管理

在我们之前的项目中, 或多或少都会有手动引用计数的存在, 看到apple出了自动引用计数, 再看看自己的项目, 就有一种砸电脑的冲动, 别着急~~其实apple很为我们开发者着想, 旧项目一样可以转成ARC, 废话少说, 直接上图: 后面的操作不需要我讲了吧? 傻瓜式的操作, 一直下一步, 直到完成为止, 这样子你的项目就会焕然一新~~~全世界都舒服晒(粤语)~~~ 但问题来了, 有一些第三方框架不支持ARC, 那肿么办?? 别着急, apple为开发者考虑的很多, 一样有办法可以解决~~~继续

Swift中KVO(监听)的使用方法及注意事项

---恢复内容开始--- 相信研究swift语言的开发者都多多少少了解或者精通Objective—C语言,熟练掌握Objective—C语言的开发者,在学习swift语言的过程中,是比较快速,而又轻松的.本人就是一位熟练掌握OC语言,后开始研究的swift.在学习swift语言的过程中,笔者建议有OC基础的开发者,在写swift的代码过程中,再写一下OC中的代码,二者相互比较,相信你能找到快速学会swift语言的方法.资深,有耐心和有天赋的开发者,相信能在一周左右,能够运用swift开发项目.其

iOS完全自学手册——[一]Ready?No!

1.前言 今天开始我会不定期写一些iOS自学的相关文章.毕竟,自己是自学开始,知道自学有哪些坑,知道自学对于开发欠缺什么,此外,加上现在的实际开发经验,希望能给自学的iOS开发者一些建议. 2.Ready? 2.1 工欲善其事,必先利其器 —— 务必要有自己的 Mac 微博上 @不知霜舞哀伤udspj (http://weibo.com/udspj ,公众号 udspj_manga )妹纸画过一副图,关于iOS新手的建议,漫画上给的回复是“不要怕花钱”.我很认,舍不得孩子套不着狼,没有Mac干毛

12个步骤把孩子打造成下一个马化腾

1.让他掌握选择的权利 不要试图让孩子按照你安排的道路,循规蹈矩的成长.而是让他们了解,每一个选择意味着什么.让他们明白实现人生梦想的各种机会,给他们选择的权利. 2.避免常规式教育 如果你只想常规的思维,那就让他们接受常规的教育.但这样是无法培养出有创造力的孩子的.像亿万富翁那样不平常的人士也需要一种不寻常的教育,比如让他们在10岁之前就开始从事种不同的工作等. 3.告诉他,要热爱工作 不要让他们偷懒,致富需要工作.你需要让孩子坚信这句话,并为之充满毅力. 4.教育他尊重并理解他人 确保孩子理

Objective C ARC 使用及原理

手把手教你ARC ,里面介绍了ARC的一些特性, 还有将非ARC工程转换成ARC工程的方法 ARC 苹果官方文档 下面用我自己的话介绍一下ARC,并将看文档过程中的疑问和答案写下来.下面有些是翻译,但不是全部,请一定要看一遍官方文档 不考虑 iOS4 的 ARC 规则 简单地说,ARC在编译时刻为代码在合适的位置加上retain 和 release. 复杂点,它还提供其它一些功能,还为解决一些问题,添加了一些关键字和功能,后面会说. ARC强制要求的新规则 不可以调用dealloc, 不可以实现

[精通Objective-C]进阶技巧:使用运行时系统API

[精通Objective-C]进阶技巧:使用运行时系统API 参考书籍:<精通Objective-C>[美] Keith Lee 什么是运行时系统? 目录 精通Objective-C进阶技巧使用运行时系统API 目录 动态加载可选包 创建命令行程序 创建可选包 传入包路径 使用可选包 运行时系统API 动态代理 创建实现横切功能的协议和类 编写代理类 添加代理的目标类 测试动态代理程序 动态加载可选包 下面是使用NSbundle API动态加载自己编写的框架包的示例,共需要创建两个工程,一个命

arc下内存泄漏的解决小技巧

一定要注意,我们运行app时,一定要关心内存的使用,尽量不要超过20M,即使有很多图片要显示也绝对不能超过30M.所以运行自己开发的app时多关心内存的使用是个很好的习惯. 对于性能,内存的优化,这个涉及的就太多了.现在先讲讲几个最基本常见的内存泄露下的解决方法. 1. instruments instrument可以报出不少内存泄露的错误,方法:http://www.cocoachina.com/ios/20141203/10519.html,很容易操作. 不过它最大的功能还是可以自己看出哪里

深入理解Objective C的ARC机制

前言 本文的ARC特指Objective C的ARC,并不会讲解其他语言.另外,本文涉及到的原理部分较多,适合有一定经验的开发者. 什么是ARC? ARC的全称Auto Reference Counting. 也就是自动引用计数.那么,为什么要有ARC呢? 我们从C语言开始.使用C语言编程的时候,如果要在堆上分配一块内存,代码如下 //分配内存(malloc/calloc均可) int * array = calloc(10, sizeof (int)); //释放内存 free(array);

十五天精通WCF——第五天 你需要了解的三个小技巧

一: 服务是端点的集合 当你在开发wcf的时候,你或许已经注意到了一个service可以公布多个endpoint,确实是这样,在wcf中有一句很经典的话,叫做“服务是端点的集合",就 比如说一个普普通通的服务,它就公布了一个服务端点,一个元数据端点,对吧... 仔细一想,这个问题就好玩了,既然一个service可以公布多个endpoint,而且我还知道wcf中有很多的binding,这些binding对应着很多的传输方式,那是不是 说我一个service可以用多种协议方法对外公布,比如说同时以n