您的位置:首页 > 移动开发 > IOS开发

iOS编程基础-OC(六)-专家级技巧:使用ARC

2017-11-20 18:09 453 查看


该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

     第6章 专家级技巧:使用ARC

     

     本章是第一部分的最后一章;

         本章介绍ARC内存管理中的细微之处;

         如直接桥接对象使用ARC的方法;

     

     6.1 ARC和对象所有权

     

     我们已经知道OC代码创建的对象存储在以动态方式分配的内存(堆内存)中;需要内存管理;

         确保将不再使用的对象从该内存区域中移除;

         确保不错误地移除仍在使用中的对象;

     

     OC通过引用计数模式实现内存管理,该模式通过计算对象的被引用数量判定对象是否在使用中;

     ARC通过在编译时,插入代码,使向对象发送的retain和release消息达到平衡,从而自动化该任务;

         ARC禁止程序员手动控制对象的生命周期;

         也不能通过发送retain和release消息控制对象的所有权;

     

     了解ARC内存管理中的对象所有权规则很重要;

     

     6.1.1 声明对象的所有权

     

     OC能够通过获得由名称以alloc new copy mutableCopy开头的方法创建的任何对象的所有权;

         A * a = [[A alloc] init];//这里使用alloc方法创建了A对象,也就声明了对象的所有权;

         在编写的代码中,如果使用copy消息创建块对象,也可以获得这类对象的所有权;

     

     注:

         块是指闭包的实现代码,这类函数允许访问它们作用范围之外的变量;后续会详细介绍;

     

     6.1.2 释放对象的所有权

     

     ARC禁止以手动方式向对象发送release、autorelease、dealloc或其他相关的消息;

     当你编写的代码执行下列操作时,会放弃对象的所有权:

         1)重新分配变量;

         2)将nil赋予变量;

         3)释放对象的所有者;

     

     1.重新分配变量:

         变量中指向以动态方式创建的对象更改为指向另一个对象,那么原来对象就会失去一个所有者;

         在编译时,ARC会插入向这个对象发送release消息的代码;此时如果该对象没有其他的所有者,那么它就会被释放;

     

     2.将nil赋予变量:

         如果是以动态的方式创建的对象的变量设置为nil,该对象也会失去一个所有者;

         ARC会在将变量设置为nil的语句后面,插入向这个对象发送release消息的代码;

     

     3.释放对象的所有者:

         释放对象的所有者既与对象的所有权有关;又与ARC管理对象(包括对象图和对象集)生命周期的方式有关;

     

     对象图:(Object graph)

         面向对象的程序由相互关联的对象网络组成;这些对象通过OOP继承或合成法连接到一起,统称为对象图;

     

     在对象图中,对象通过合成法规则引用其他(子)对象;

         如果某个(父)对象创建另一个(子)对象,那么这个父对象就声明了它拥有孩子对象的所有权;

         我们定义了C6OrderEntry类及C6OrderItem、C6Address类,通过对象图创建了一段程序;

(注:类实现代码附在文末,建议先浏览一下代码)

C6OrderEntry * entry = [[C6OrderEntry alloc] initWithId:@"001" name:@"AD"];
NSLog(@"OrderEntry:%@ \nOrderItem:%@ name:%@",entry.orderId,entry.item,entry.item.name);


      如上述代码所示:

          当程序创建并初始化了一个OrderEntry对象后,其初始化方法还会创建其他两个子类的实例;

          因此,这个OrderEntry对象会声明它拥有这些子对象的所有权;

          当程序释放OrderEntry对象时,ARC会自动向其子类对象发送release消息;

      

      值得注意的是:

          在编译时ARC还会为这个OrderEntry对象插入dealloc方法(没有的话);

          并为它的每个子类对象插入一条release消息;

          然后向OrderEntry对象的父类发送一条dealloc消息;

      

      上述代码的log如下:

      2017-11-17 15:30:09.834033+0800 精通Objective-C[36322:2311019] Initial orderEntry object with ID 001

      2017-11-17 15:30:09.834217+0800 精通Objective-C[36322:2311019] Initial OrderItem object AD

      2017-11-17 15:30:09.834369+0800 精通Objective-C[36322:2311019] OrderEntry:001

      OrderItem:<C6OrderItem: 0x60400000f790> name:AD

      2017-11-17 15:30:09.834463+0800 精通Objective-C[36322:2311019] Dealloc orderEntry object with ID 001

      2017-11-17 15:30:09.834546+0800 精通Objective-C[36322:2311019] Dealloc OrderItem object AD

      

      这样ARC就实现了对象图生命周期管理的自动化;

      

      Foundation框架中还有各种集合类,当一个对象存储在集合类的一个实例中时,该集合类会声明该对象的所有权;

          当这个集合类被释放时,ARC会自动向该集合类中的每个对象发送一条release消息;

      

      

     6.2 扩展订单条目工程

      

      (Code)

      类C6OrderEntry;

      类C6OrderItem;

      

      基于上述类测试声明对象所有权,以及释放对象所有权的几种方式,我们会发现:ARC以正确的方式管理了这些对象图的生命周期;

      

      我们来写一段程序测试一下:

  
NSLog(@"Entering autoreleasepool block");
@autoreleasepool{
C6OrderEntry * entry1 = [[C6OrderEntry alloc] initWithId:@"A-1" name:@"Hot dog"];
C6OrderEntry * entry2 = [[C6OrderEntry alloc] initWithId:@"A-2" name:@"Potato"];

NSArray * entrys = [[NSArray alloc] initWithObjects:entry1,entry2, nil];
NSLog(@"Setting entry2 to nil");
entry2 = nil;
NSLog(@"Setting entrys to nil");
entrys = nil;
NSLog(@"Setting entry1 to nil");
entry1 = nil;

NSLog(@"Leaving autoreleasepool block");
}


       log:

       2017-11-17 16:43:10.115083+0800 精通Objective-C[36981:2402627] Entering autoreleasepool block

       2017-11-17 16:43:10.115430+0800 精通Objective-C[36981:2402627] Initial orderEntry object with ID A-1

       2017-11-17 16:43:10.115565+0800 精通Objective-C[36981:2402627] Initial OrderItem object Hot dog

       2017-11-17 16:43:10.115658+0800 精通Objective-C[36981:2402627] Initial orderEntry object with ID A-2

       2017-11-17 16:43:10.115736+0800 精通Objective-C[36981:2402627] Initial OrderItem object Potato

       2017-11-17 16:43:10.115820+0800 精通Objective-C[36981:2402627] Setting entry2 to nil

       2017-11-17 16:43:10.115905+0800 精通Objective-C[36981:2402627] Setting entrys to nil

       2017-11-17 16:43:10.116067+0800 精通Objective-C[36981:2402627] Dealloc orderEntry object with ID A-2

       2017-11-17 16:43:10.116966+0800 精通Objective-C[36981:2402627] Dealloc OrderItem object Potato

       2017-11-17 16:43:10.117205+0800 精通Objective-C[36981:2402627] Setting entry1 to nil

       2017-11-17 16:43:10.118076+0800 精通Objective-C[36981:2402627] Dealloc orderEntry object with ID A-1

       2017-11-17 16:43:10.118704+0800 精通Objective-C[36981:2402627] Dealloc OrderItem object Hot dog

       2017-11-17 16:43:10.118909+0800 精通Objective-C[36981:2402627] Leaving autoreleasepool block

  
NSLog(@"Entering autoreleasepool block");
@autoreleasepool{
C6OrderEntry * entry3 = [[C6OrderEntry alloc] initWithId:@"A-3" name:@"Hot dogs"];
printf("entry3 retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(entry3)));
NSLog(@"%@",entry3.item.name);
printf("entry3 retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(entry3)));

C6OrderEntry * entry4 = [[C6OrderEntry alloc] initWithId:@"A-4" name:@"Potatos"];
NSLog(@"%@",entry4.item.name);

NSArray * entrys = [[NSArray alloc] initWithObjects:entry3,entry4, nil];
NSLog(@"Setting entry3 to nil");
entry3 = nil;
NSLog(@"Setting entrys to nil");
entrys = nil;
NSLog(@"Setting entry4 to nil");
entry4 = nil;

NSLog(@"Leaving autoreleasepool block");
}


    

     log:

     2017-11-17 16:48:27.110462+0800 精通Objective-C[37036:2409143] Entering autoreleasepool block

     2017-11-17 16:48:27.110675+0800 精通Objective-C[37036:2409143] Initial orderEntry object with ID A-3

     2017-11-17 16:48:27.110892+0800 精通Objective-C[37036:2409143] Initial OrderItem object Hot dogs

     entry3 retain count = 1

     2017-11-17 16:48:27.111098+0800 精通Objective-C[37036:2409143] Hot dogs

     entry3 retain count = 1

     2017-11-17 16:48:27.111325+0800 精通Objective-C[37036:2409143] Initial orderEntry object with ID A-4

     2017-11-17 16:48:27.112664+0800 精通Objective-C[37036:2409143] Initial OrderItem object Potatos

     2017-11-17 16:48:27.112762+0800 精通Objective-C[37036:2409143] Potatos

     2017-11-17 16:48:27.112848+0800 精通Objective-C[37036:2409143] Setting entry3 to nil

     2017-11-17 16:48:27.112929+0800 精通Objective-C[37036:2409143] Setting entrys to nil

     2017-11-17 16:48:27.113455+0800 精通Objective-C[37036:2409143] Dealloc orderEntry object with ID A-3

     2017-11-17 16:48:27.114057+0800 精通Objective-C[37036:2409143] Dealloc OrderItem object Hot dogs

     2017-11-17 16:48:27.115738+0800 精通Objective-C[37036:2409143] Setting entry4 to nil

     2017-11-17 16:48:27.115833+0800 精通Objective-C[37036:2409143] Dealloc orderEntry object with ID A-4

     2017-11-17 16:48:27.115927+0800 精通Objective-C[37036:2409143] Dealloc OrderItem object Potatos

     2017-11-17 16:48:27.116019+0800 精通Objective-C[37036:2409143] Leaving autoreleasepool block

     

     我们实现了两段代码:区别在于后一种的orderItem对象通过entry对象的属性读取器进行了获取;

         当前所学书籍中描述:通过属性读取器读取之后,ARC会自动插入向对象发送retain和autorelease消息的代码,从而避免对象被过早的释放;以此描述,OrderItem对象将在自动释放池末尾被释放;

         但如上我们所做的测试,在进行对象属性读取前后,OrderItem的引用计数并为发生变化,这个可能是ARC在后续版本中进行了修改;关键的是我们理解了ARC为管理对象内存所做的事;

     

       

     6.3 将ARC与苹果公司提供的框架和服务一同使用

     

     苹果公司提供的常用框架和服务:

         应用框架:AppKit、UIKit;

         应用服务:音频&视频、图形&动画、数据管理、网络&因特网、用户应用、次级框架;

         核心服务:启动服务、Carbon Core、Core Foundation、Foundation;

     

     这些软件库,有些是用OC编写的,因而可以直接使用;而大多数软件库的API是使用ANSC C编写的,要在OC程序中直接使用,还需要做一些事;

         ARC能够自动管理OC对象和块对象的内存,苹果提供的基于C语言的API软件库没有与ARC整合;

         因此,当你通过动态的方式为这些基于C语言的API分配内存时,必须手动管理内存;

     

     实际上,ARC不允许在OC对象指针和其他数据类型指针(苹果提供的基于C的API中的)之间进行直接转换;

         为了方便在OC中使用基于C语言的API,苹果提供了多种机制,如 桥接 和 ARC桥接转换;

     

     我们来分别看一下这两种机制;

     

     

     6.4 OC直接桥接

     

     Core Foundation框架是基于C语言的;

     Foundation是基于OC的;

     两者之中的许多数据类型之间苹果为之提供了互通性——这种功能成为“直接桥接”(toll free bridging);

         通过直接桥接你可以在Core Foundation函数中调用和OC消息的接收器中使用数据类型相同的参数;

         你可以通过将一种数据类型转换为另一种数据类型,防止编译器报警;

     

     以下是一些较为常用的直接桥接数据类型,其中包括Core Foundation数据类型和对应的Foundation框架数据类型;

         Core Foundation数据类型                     Foundation框架数据类型

         CFArrayRef                                  NSArray

         CFDataRef                                   NSData

         CFDateRef                                   NSDate

         CFDictionaryRef                             NSDictionary

         CFMutableArrayRef                           NSMutableArray

         CFMutableDataRef                            NSMutableData

         CFMutableDictionaryRef                      NSMutableDictionary

         CFMutableSetRef                             NSMutableSet

         CFMutableStringRef                          NSMutableString

         CFNumberRef                                 BSNumber

         CFReadStreamRef                             NSInputStream

         CFSetRef                                    NSSet

         CFStringReg                                 NSString

         CFWriteStreamRef                            NSOutputStream

     通过直接桥接,编译器能够将数据在Core Foundation和Foundation框架之间进行转换;

         示例:

CFStringRef cstr = CFStringCreateWithCString(nil, "Hello Flower!", kCFStringEncodingASCII);
NSArray * array = [NSArray arrayWithObject:(__bridge id _Nonnull)(cstr)];
NSArray * array1 = [NSArray arrayWithObject:(__bridge NSString *)(cstr)];

NSLog(@"array:%@",array);
NSLog(@"array1:%@",array1);


     这里的__bridge稍后会介绍,先看其他的;

     我们初始化数组的方法需要一个OC对象指针,我们传入的是直接桥接的CFStringRef数据类型,所以参数会被隐式装换为NSString对象;当然你也可以显示转换;

     

     通过上一节,我们已经知道:

         OC编译器不会自动管理Core Foundation框架数据类型对象的生命周期;

         因此,要在使用ARC的OC程序中,使用Core Foundation框架直接桥接数据类型,就必须手动管理这些对象的内存;

         这也是上边ARC环境下出现__bridge的原因;

     

     在编写使用ARC的OC程序时,必须设置直接桥接数据是由ARC管理内存还是手动管理内存;

         通过ARC桥接转换可以做到这一点;

     

     我们先简单介绍一下ARC桥接转换:

         在使用ARC时,通过ARC桥接转换 可以使用 直接桥接数据类型;

         这些操作必须使用一些特殊标记作为前缀:

             __bridge、__bridge_retained和__bridge_transfer;

     

     __bridge:

         使用__bridge标记 可以在不改变所有权的情况下,将对象从Core Foundation框架数据类型转换为Foundation框架数据类型(反之亦然);

         1)ARC下,Foundation框架对象,通过直接桥接将它的数据类型转换为Core Foundation框架下的;此时,

             通过__bridge标记可以使编译器知道这个对象的生命周期仍旧由ARC管理;

         2)Core Foundation框架对象,直接桥接为Foundation框架的数据类型;此时,

             通过__bridge标记可以告诉编译器这个对象的生命周期仍旧是以手动方式管理的(不是ARC);

         注意:使用该标记可以使编译器不报错,但是不会改变对象的所有权,因此在使用它解决内存泄漏和悬挂指针问题时应多加小心;

 

     __bridge_retained:

         使用__bridge_retained标记可以将Foundation框架数据类型对象 转换为Core Foundation框架数据类型对象;

         并从ARC接管对象所有权;这样你就可以手动管理直接桥接数据的生命周期;

     

     __bridge_transfer:

         使用__bridge_transfer标记可以将Core Foundation框架数据类型对象 转换为Foundation框架数据类型对象;

         并且会将对象的所有权交给ARC管理;这样会由ARC管理对象的声明周期;

     

     使用桥接转换标记的语法:

         (桥接转换标记 目的数据类型)变量名

     

     了解了ARC桥接转换的三中标记的含义,相信可以看出之前示例代码中的问题了,在下一节我们详细分析下,我们修改之后的代码如下:   

CFStringRef cstr1 = CFStringCreateWithCString(nil, "Hello Flower!", kCFStringEncodingASCII);
NSArray * array3 = [NSArray arrayWithObject:(__bridge_transfer NSString *)(cstr1)];


     这里__bridge_transfer标记会将桥接对象的所有权交给ARC,这样该对象的生命周期就会由ARC管理;

      

      了解了概念之后,让我们来看看ARC桥接转换的用法;

      

      

     6.5 使用ARC桥接转换

      

      示例1:

      还是上边的代码示例(使用__bridge标记的那一段);

      我们使用Analyze选项分析该程序,就会发现变量cstr中存在潜在的内存泄漏问题;



      (Pic1)

      

      出现这个问题的原因是使用__bridge标记无法改变直接桥接对象的所有权,因此,必须手动管理变量cstr的生命周期;

          这段代码创建了该CFStringRef对象,所以他拥有这个以动态方式创建的对象,并且可以向该对象发送release消息;

          例如:CFRelease(cstr);

      

      这是一种解决方式;

      

      更好的选择:使用__bridge_transfer才是更好的选择;(上一节中__bridge_transfer标记的那一段)

          这样就不会再检查到内存泄漏了;

      

      示例2:

NSNumber * counting = [[NSNumber alloc]initWithFloat:5];
CFNumberRef cscounting = (__bridge CFNumberRef)counting;
CFShow(cscounting);
NSLog(@"greeting:%@",counting);


    

     这段代码编译是没有问题的,但存在潜在的悬挂指针问题;

         因为在执行桥接转换时,ARC会立即向NSNumber对象发送一个release消息;

         解决这个问题,可以这样修改代码;

 
NSNumber * counting1 = [[NSNumber alloc]initWithFloat:5];
CFNumberRef cscounting1 = (__bridge_retained CFNumberRef)counting1;
CFShow(cscounting1);
CFRelease(cscounting1);


    

     使用__bridge_retained标记可以从ARC接管该对象,从而避免ARC向该对象发送release消息;

     亦因此,你必须手动管理这个对象的生命周期;

         现在已经转换为CFStringRef类型;

         在使用结束之后调用CFRelease方法;

     

     可以看到:

     __bridge:并不能修改对象所有权,之前是手动管理/ARC的,使用它标记之后,仍然需要手动管理/ARC;

     __bridge_retained:可以修改对象所有权,而且不是简单的修改,而是接管,适用于手动管理从ARC的接管对象所有权;

     __bridge_transfer:也可以修改对象所有权,而且不是简单的修改,而是将所有权交给ARC,适用于手动管理交给ARC对象所有权;

     

     

     6.6 小结

     

     本章深入研究了ARC内存管理方式;ARC也是苹果推荐的内存管理方式;

     

     要点:

     1)ARC禁止通过手动方式,向对象发送release、autorelease、dealloc消息,以及与对象有关的其他消息;

         当执行下列操作的时候,程序会放弃对象的所有权:

             a.重新分配变量;-编译时,ARC会插入向该对象发送一条release消息的代码;

             b.将nil赋予变量;-编译时,ARC会插入向该对象发送一条release消息的代码;

             c.释放对象的拥有者;-使用集合类实例时会出现释放所有者的情况,集合类实例被释放时,ARC会自动向该集合中的每一个对象发送一条release消息;

     2)苹果公司提供的基于C语言的API软件库没有与ARC整合,因此,当您在程序中以动态的方式为这些API分配内存时,必须手动管理内存;

     3)ARC禁止在OC对象指针和其他类型指针之间进行标准转换;为此提供了相应的机制:直接桥接 和 ARC桥接转换;

         以方便在OC中使用基于C的API;

     4)Core Foundation和Foundation框架中有很多非常有用的直接桥接数据类型,从而使你编写的程序 能够在调用Core Foundation框架和 接收OC消息时 使用相同的数据类型;

         数据类型转换可以消除编译器警告;但在ARC下,无法直接转换 直接桥接的数据类型,而必须使用特殊的ARC桥接转换标记才能进行转换;

     

     注:直接桥接 桥接的是数据类型;

         ARC桥接转换 转换的是对象所有权;

     

     5)通过ARC桥接装换,可以使用ARC的同时,使用直接桥接数据类型;这些转换操作以特殊的转换标记开始(__bridge、__bridge_retained、__bridge_transfer);

     到本章为止,OC基础知识就介绍完了,接下来我们开始介绍OC运行时系统;

     

代码实现:

C6OrderEntry

#import <Foundation/Foundation.h>

#import "C6OrderItem.h"
#import "C6Address.h"

@interface C6OrderEntry : NSObject{
C6Address * shippingAddress;
}

@property (readonly) NSString * orderId;
@property (readonly) C6OrderItem * item;

-(id)initWithId:(NSString *)oid name:(NSString *)order;

@end

#import "C6OrderEntry.h"

@implementation C6OrderEntry

-(id)initWithId:(NSString *)oid name:(NSString *)order{
if (self = [super init]) {
NSLog(@"Initial orderEntry object with ID %@",oid);

_orderId = oid;
shippingAddress = [[C6Address alloc] init];
_item = [[C6OrderItem alloc]initWithName:order];
}
return self;
}
-(void)dealloc{
NSLog(@"Dealloc orderEntry object with ID %@",self.orderId);
}
@end


C6OrderItem
#import <Foundation/Foundation.h>

@interface C6OrderItem : NSObject

@property (readonly) NSString * name;

-(id)initWithName:(NSString *)itemName;

@end

#import "C6OrderItem.h"

@implementation C6OrderItem
-(id)initWithName:(NSString *)itemName{
if (self = [super init]) {
NSLog(@"Initial OrderItem object %@",itemName);
_name = itemName;//对于自动补全属性来说,实例变量的标准命名约定为在属性名称中加下划线前缀
}
return self;
}
-(void)dealloc{
NSLog(@"Dealloc OrderItem object %@",self.name);
}

@end


C6Address
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息