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

示例代码下载

手动引用计数

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] -------------begin
2019-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] 作用域块已经结束
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容