Objective-C高级编程笔记一(自动引用计数)

作者 : 开心源码 本文共8795个字,预计阅读时间需要22分钟 发布时间: 2022-05-12 共193人阅读

示例代码下载

手动引用计数

MRC内存管理的思考方式

  • 自己生成的对象自己持有
  • 不是自己生成的对象,自己也能持有
  • 不在需要自己持有的对象时释放
  • 不是自己持有的对象无法释放

对象操作与Objective-C方法的对应

对象操作Objective-C方法
生成并持有对象alloc/new/copy/mutableCopy等方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法

实现一个MRCObject类:

@implementation MRCObject- (void)dealloc {    NSLog(@"%@(%@)销毁了", NSStringFromClass(self.class), self);        [super dealloc];}+ (instancetype)object {    MRCObject *obj = [self allocObject];    [obj autorelease];    return obj;}+ (instancetype)allocObject {    MRCObject *obj = [[MRCObject alloc] init];    NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);        return obj;}@end

自己生成并持有对象:

MRCObject *obj = [MRCObject allocObject];

不是自己生成的对象也能持有:

MRCObject *obj = [MRCObject object];[obj retain];

不在需要自己持有的对象时释放:

MRCObject *obj = [self allocObject];[obj release];

无法释放自己没有持有的对象:

MRCObject *obj = [self allocObject];[obj release];[obj release];//会奔溃

autorelease

autorelease像c语言的自动变量来对待对象实例,当超出其作用域(相当于变量作用域),对象实例的release方法被调用。与c语言自动变量不同的是,可以autorelease的作用域。

autorelease的使用方法:

  • 生成NSAutoreleasePool对象
  • 调用已分配对象实例的autorelease方法
  • 废弃NSAutoreleasePool对象

在应用程序中,因为主线程的NSRunloop对NSAutoreleasePool对象进行生成、持有和废弃解决。因而开发者不肯定非得使用NSAutoreleasePool对象来进行开发工作。如下图:

image

在大量产生autorelease对象时,只需不废弃NSAutoreleasePool对象,autorelease对象就不会被释放,因而会产生内存不足的现象。如下两段代码:

    for (int index = 0; index < 1000; index++) {        NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];        [image autorelease];    }
    for (int index = 0; index < 1000; index++) {        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];        NSString *path = [[NSBundle mainBundle] pathForResource:@"1553667540126" ofType:@"jpeg"];        UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];        [image autorelease];        [pool drain];    }

ARC

ARC规则

ARC有效时,id类型和对象类型同c语言其余类型不同,必需增加所有权修饰符。共如下4种所有权修饰符:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __outoreleasing修饰符

import “ARCObject.h”

实现一个ARCObject类:

@interface ARCObject (){    __strong id _strongObj;    __weak id _weakObj;}@end@implementation ARCObject- (void)dealloc {    NSLog(@"%@(%@)销毁了", NSStringFromClass(self.class), self);}+ (instancetype)allocObject {    ARCObject *obj = [[ARCObject alloc] init];    NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);    return obj;}- (void)setStrongObject:(id)obj {    _strongObj = obj;}- (void)setWeakObject:(id)obj {    _weakObj = obj;}@end

__strong修饰符

__strong修饰符是所有id类型和对象类型默认的所有权修饰符,表示对对象的强引用,在超出其作用域或者被重新赋值时被废弃。

{    ARCObject *obj = [ARCObject allocObject];    NSLog(@"作用域最后一行%@", obj);}NSLog(@"作用域已经结束");
ARCObject *obj = [ARCObject allocObject];NSLog(@"重新赋值前%@", obj);obj = [ARCObject allocObject];NSLog(@"重新赋值前后%@", obj);

__strong、__weak、__outoreleasing修饰符的自动变量默认初始化为nil。

__weak修饰符

__weak修饰符与__strong修饰符相反,提供弱引用,弱引用不持有对象实例。

循环引用容易发生内存泄漏,内存泄漏就是应当废弃的对象在超出其生存周期后仍然存在。可以使用__weak修饰符来避免。

ARCObject *aObj = [ARCObject allocObject];ARCObject *bObj = [ARCObject allocObject];[aObj setStrongObject:bObj];[bObj setStrongObject:aObj];
ARCObject *obj = [ARCObject allocObject];[obj setStrongObject:obj];
ARCObject *aObj = [ARCObject allocObject];ARCObject *bObj = [ARCObject allocObject];ARCObject *cObj = [ARCObject allocObject];[aObj setWeakObject:bObj];[bObj setWeakObject:aObj];[cObj setWeakObject:cObj];

__weak修饰符有一个优点就是:在持有某对象的弱引用时,假如该对象被废弃,则该对象弱引用自动失效且被置为nil。

__unsafe_unretained修饰符

__unsafe_unretained修饰符,正如其名一样是不安全的所有权修饰符。虽然ARC的内存管理是编译器的工作,但是这一点需要注意特别注意,__unsafe_unretained修饰符的变量不属于编译器内存管理的对象。

__unsafe_unretained修饰符和__weak修饰符的变量一样不会持有对象,但是__unsafe_unretained修饰符的变量在销毁时并不会自动置为nil,在其地址被覆盖后就会由于反问垂悬指正而造成奔溃。因而__unsafe_unretained修饰符变量赋值给__strong修饰符变量时要确保对象的真实存在。由于__weak修饰符是在iOS5中实现的,__unsafe_unretained修饰符存在的意义就是在iOS4中代替__weak修饰符的作用。

ARCObject __unsafe_unretained *obj = nil;{    ARCObject *obj1 = [ARCObject allocObject];    obj = obj1;}NSLog(@"%@(%@)", NSStringFromClass(obj.class), obj);

__outoreleasing修饰符

ARC有效时不能使用outorelease方法,也不能使用NSAutoreleasePool类。这样就导致outorelease无法直接使用,但实际上outorelease功能是起作用的。使用@outoreleasepool{}块代码来代替NSAutoreleasePool类对象的生成持有以及废弃。通过赋值给__outoreleasing修饰符的变量来代替调用outorelease方法,也就是说对象被注册到autoreleasepool中。

@autoreleasepool {    ARCObject __autoreleasing *obj1 = [ARCObject allocObject];    NSLog(@"autoreleasepool块最后一行%@", obj1);}NSLog(@"autoreleasepool块已经结束");

ARC有效时,cocoa中因为编译器会检查方法名能否以alloc/new/copy/mutableCopy开始,假如不是则自动将返回值的对象注册到outoreleasepool中。所以非显示的使用__outoreleasing修饰符也是可以的。

NSMutableArray __weak *array = nil;NSLog(@"作用域块开始前%@", array);{    NSMutableArray *arr = [NSMutableArray arrayWithObject:@(1)];    array = arr;    NSLog(@"作用域块最后一行%@", array);}NSLog(@"作用域块已经结束%@", array);

打印结果:

2019-03-28 11:56:52.316360+0800 ProfessionalExample[82984:16680615] 作用域块开始前(null)2019-03-28 11:56:52.316538+0800 ProfessionalExample[82984:16680615] 作用域块最后一行(    1)2019-03-28 11:56:52.316627+0800 ProfessionalExample[82984:16680615] 作用域块已经结束(    1)

id的指针和对象的指针在没有显式指定修饰符时会被附加上__outoreleasing修饰符。

- (BOOL)performOperationWithError:(ARCObject **)obj {    *obj = [ARCObject object];    return NO;}

调用方法则为如下所示,自动转化为__autoreleasing修饰符:

[self performOperationWithError:<#(ARCObject *__autoreleasing *)#>];

id的指针和对象的指针变量必需指明所有权修饰符,并且赋值的所有权修饰符必需一致:

NSObject **pObj;//编报错,没有所有权修饰符
NSObject *obj = [[NSObject alloc] init];NSObject *__autoreleasing*pObj = &obj;//编译报错,会更改所有权属性

纠正一个比较普遍的错误认知,for循环中并不是循环结束才释放循环内的局部变量,并不是所有产生大量对象的for循环中都需要加NSAutoreleasePool,而是产生大量autorelease对象时才需要增加。如下示例代码:

    for (int index = 0; index < 2; index++) {        if (index == 0) {            NSLog(@"-------------begin");            ARCObject *obj = [[ARCObject alloc] init];            NSLog(@"%@(%@)生成了", NSStringFromClass(obj.class), obj);        }        if (index == 1) {            NSLog(@"-------------end");        }    }

下面是这段代码的打印内容:

2019-03-28 15:27:19.179194+0800 ProfessionalExample[85692:16955598] -------------begin2019-03-28 15:27:19.179366+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)生成了2019-03-28 15:27:19.179449+0800 ProfessionalExample[85692:16955598] ARCObject(<ARCObject: 0x600001ded3a0>)销毁了2019-03-28 15:27:19.179521+0800 ProfessionalExample[85692:16955598] -------------end

ARC编码规则

  • 不能使用retain/release/retainCount/autorelease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 须遵守内存管理的方法命名规则
  • 不能显式调用dealloc方法
  • 使用@autoreleasepool{}代替NSAutoreleasePool
  • 不能使用NSZone
  • 对象变量不能作为c语言结构体的成员
  • 显式转换id和void *

内存管理的方法命名规则

以alloc/new/copy/mutableCopy开头的方法返回对象时,必需返回给调用方应当持有的对象。这在ARC有效时是一样的,不同的是以init开头的方法必需是实例方法且需要返回对象,该返回对象并不注册到autoreleasepool上。

对象变量不能作为c语言结构体的成员

要把对象类型变量加入到结构体中,需强制转为void *或者者前面附加__unsafe_unretained修饰符。

显式转换id和void *

可以使用(__bridge)转换void *和OC对象,但是其安全性和赋值给__unsafe_unretained修饰符相近或者者更低。假如管理时不注意赋值对象的所有者就会由于垂悬指针而奔溃或者者内存泄漏。

NSObject *obj = [[NSObject alloc] init];void *p = (__bridge void *)obj;obj = (__bridge NSObject *)p;

__bridge_retained转换可使要赋值的变量持有所赋值的变量。__bridge_transfer则与之相反。

NSObject *obj = [[NSObject alloc] init];void *p = (__bridge_retained void *)obj;obj = (__bridge_transfer NSObject *)p;

NSObject对象与Core Fundation对象之间的相互转换,即免费桥(Toll-Freee-Bridge)转换。CFBridgingRetain函数(等价于__bridge_retained转换),CFBridgingRelease函数(等价于__bridge_transfer)。

NSObject *obj = [[NSObject alloc] init];CFTypeRef ref = CFBridgingRetain(obj);obj = CFBridgingRelease(ref);

属性

属性公告的属性与所有权修饰符对应关系

属性公告的属性所有权修饰符
assign__unsafe_unretained修饰符
copy__strong修饰符(但是赋值的是被赋值过来的)
retain__strong修饰符
unsafe_unretained__unsafe_unretained修饰符
weak__weak修饰符

c数组

c静态数组,各修饰符的使用OC对象一样没有区别。

以__strong为例,其初始化为nil,超过作用域销毁:

{    ARCObject *array[2];    array[0] = [ARCObject allocObject];    NSLog(@"array第一个元素:%@", array[0]);    NSLog(@"array第二个元素:%@", array[1]);    array[1] = nil;    NSLog(@"array第二个元素:%@", array[1]);}NSLog(@"作用域块已经结束");

打印结果:

2019-03-28 19:19:26.697408+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)生成了2019-03-28 19:19:26.697661+0800 ProfessionalExample[88859:17353905] array第一个元素:<ARCObject: 0x6000005f8500>2019-03-28 19:19:26.697761+0800 ProfessionalExample[88859:17353905] array第二个元素:(null)2019-03-28 19:19:26.697845+0800 ProfessionalExample[88859:17353905] array第二个元素:(null)2019-03-28 19:19:26.697930+0800 ProfessionalExample[88859:17353905] ARCObject(<ARCObject: 0x6000005f8500>)销毁了2019-03-28 19:19:26.697995+0800 ProfessionalExample[88859:17353905] 作用域块已经结束

c动态数组,c语言中动态数组公告用指针即id *array(NSObject **array)。需要注意如下几点:

  • _strong/__weak修饰符的OC变量初始化为nil,并不代表其指针初始化为nil。所以分配内存后,需要对其初始化为nil,否则非常危险。calloc函数分配的就是nil初始化后的内存,malloc函数分配内存后必需使用memset将内存填充为0(nil)。
  • 必需置空_strong修饰符的态数数组内的元素,使其强引用失效,元素才能释放。由于动态数组的生命周期有开发者管理,编译器不能确定销毁动态数组内元素的时机。
{    ARCObject *__strong *array;    array = (ARCObject *__strong *)calloc(2, sizeof(ARCObject *));    NSLog(@"array第一个元素:%@", array[0]);    NSLog(@"array第二个元素:%@", array[1]);    array[0] = [ARCObject allocObject];    array[1] = [ARCObject allocObject];    array[0] = nil;    NSLog(@"array第一个元素:%@", array[0]);    NSLog(@"array第二个元素:%@", array[1]);    free(array);}NSLog(@"作用域块已经结束");

打印结果:

2019-03-28 19:29:26.162245+0800 ProfessionalExample[89048:17394552] array第一个元素:(null)2019-03-28 19:29:26.162586+0800 ProfessionalExample[89048:17394552] array第二个元素:(null)2019-03-28 19:29:26.162763+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)生成了2019-03-28 19:29:26.162867+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a395c0>)生成了2019-03-28 19:29:26.162945+0800 ProfessionalExample[89048:17394552] ARCObject(<ARCObject: 0x600001a32b40>)销毁了2019-03-28 19:29:26.163011+0800 ProfessionalExample[89048:17394552] array第一个元素:(null)2019-03-28 19:29:26.163083+0800 ProfessionalExample[89048:17394552] array第二个元素:<ARCObject: 0x600001a395c0>2019-03-28 19:29:26.163160+0800 ProfessionalExample[89048:17394552] 作用域块已经结束

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Objective-C高级编程笔记一(自动引用计数)

发表回复