iOS/OS X内存管理(一):基本概念与原理美学原理

在Objective-C的内存管理中,其实就是引用计数(reference count)的治本。内存管理就是在先后必要时程序员分配一段内存空间,而当使用完之后将它释放。即便程序员对内存财富使用不当,有时不仅会导致内存能源浪费,甚至会招致程序crach。大家将会从引用计数和内存管理规则等基本概念开始,然后讲述有何内存管理措施,最终注意有哪些常见内存难题。

memory management from apple document

基本概念

引用计数(Reference Count)

为了诠释引用计数,我们做3个类比:员工在办公使用灯的情景。

引用Pro Multithreading and Memory Management for iOS and OS X的图

  • 先是私家进去办公室时,他索要接纳灯,于是开灯,引用计数为1
  • 当另1个人进去办公室时,他也亟需灯,引用计数为2;每当多一位进去办公室时,引用计数加1
  • 当有1人相差办公室时,引用计数减1,当引用计数为0时,约等于终极一位相距办公时,他不再需求运用灯,关灯离开办公。

内存管理规则

从地点员工在办公使用灯的例证,我们相比较一下灯的动作Objective-C对象的动作有啥相似之处:

灯的动作 Objective-C对象的动作
开灯 创建一个对象并获取它的所有权(ownership)
使用灯 获取对象的所有权
不使用灯 放弃对象的所有权
关灯 释放对象

因为我们是透过引用计数来治本灯,那么大家也足以通过引用计数来保管使用Objective-C对象。

引用Pro Multithreading and Memory Management for iOS and OS X的图

而Objective-C对象的动作对应当哪些方法以及那么些艺术对引用计数有何震慑?

Objective-C对象的动作 Objective-C对象的方法
1. 创建一个对象并获取它的所有权 alloc/new/copy/mutableCopy (RC = 1)
2. 获取对象的所有权 retain (RC + 1)
3. 放弃对象的所有权 release (RC – 1)
4. 释放对象 dealloc (RC = 0 ,此时会调用该方法)

当你alloc三个对象objc,此时普拉多C=1;在有个别地点你又retain本条目的objc,此时LX570C加1,相当于锐界C=2;由于调用alloc/retain三遍,对应必要调用release五回来刑满释放对象objc,所以您须求release对象objc五回,此时CRUISERC=0;而当奥德赛C=0时,系统会自行调用dealloc艺术释放对象。

Autorelease Pool

在支付中,我们平时都会选用到部分变量,局地变量一个表征就是当它超过成效域时,就会自动释放。而autorelease
pool
跟局地变量类似,当执行代码超越autorelease
pool块时,全体放在autorelease
pool的靶子都会自行调用release。它的做事规律如下:

  • 创制一个NSAutoreleasePool对象
  • 在autorelease pool块的靶子调用autorelease方法
  • 释放NSAutoreleasePool对象

引用Pro Multithreading and Memory Management for iOS and OS X的图

iOS 5/OS X Lion前的(等下会介绍引入AEnclaveC的写法)实例代码如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];

[pool drain];

/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */

出于放在autorelease
pool的靶子并不会立马释放,若是有多量图形数据放在那里的话,将会招致内存不足。

for (int i = 0; i < numberOfImages; i++)
{
      /*   处理图片,例如加载
       *   太多autoreleased objects存在
       *   由于NSAutoreleasePool对象没有被释放
       *   在某个时刻,会导致内存不足 
       */
}

A卡宴C管理措施

iOS/OS X内存管理方法有二种:手动引用计数(马努al Reference
Counting)和机动引用计数(Automatic Reference Counting)。从OS X Lion和iOS
5伊始,不再要求程序员手动调用retainrelease方法来管理Objective-C对象的内存,而是引入一种新的内存管理机制Automatic
Reference
Counting(ARC)
,简单的话,它让编译器来代表程序员来机关进入retainrelease方法来具有和废弃对象的全数权。

在AKugaC内存管理机制中,id和其余对象类型变量必须是以下八个ownership
qualifiers
里面多个来修饰:

  • __strong(暗中同意,如果不点名其余,编译器就默许加入)
  • __weak
  • __unsafe_unretained
  • __autoreleasing

由此在管理Objective-C对象内存的时候,你不能不采取其中三个,上边会用一些列子来各个表达它们的意思以及哪些抉择它们。

__strong ownership qualifier

倘使本人想成立三个字符串,使用完今后将它释放调用,使用MQX56C管理内存的写法应该是这么:

{
    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1
    NSLog(@"%@", text);
    [text release];                      //@"Hello, world"对象的RC=0
}

而一旦是应用ASportageC方式的话,就text目的无需调用release方法,而是当text变量当先成效域时,编译器来机关进入[text release]主意来刑释解教内存

{
    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1
    NSLog(@"%@", text);
}
/*
 *  当text超过作用域时,@"Hello, world"对象会自动释放,RC=0
 */

而当你将text赋值给其余变量anotherText时,MRC需要retain时而来持有全体权,当textanotherText应用完将来,各种调用release艺术来刑释解教。

{
    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1
    NSLog(@"%@", text);

    NSString *anotherText = text;        //@"Hello, world"对象的RC=1
    [anotherText retain];                //@"Hello, world"对象的RC=2
    NSLog(@"%@", anotherText);

    [text release];                      //@"Hello, world"对象的RC=1
    [anotherText release];               //@"Hello, world"对象的RC=0
}

而使用A汉兰达C的话,并不须要调用retainrelease主意来全部跟释放对象。

{
    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];   //@"Hello, world"对象的RC=1
    NSLog(@"%@", text);

    NSString *anotherText = text;        //@"Hello, world"对象的RC=2
    NSLog(@"%@", anotherText);
}
/*
 *  当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法, @"Hello, world"对象的RC=0
 */

除了当__strong变量超越功效域时,编译器会自动进入release语句来刑释解教内存,假如你将__strong变量重新赋给它其余值,那么编译器也会自行进入release语句来刑满释放变量指向此前的靶子。例如:

{
    NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];    //@"Hello, world"对象的RC=1
    NSString *anotherText = text;        //@"Hello, world"对象的RC=2
    NSString *anotherText = [[NSString alloc] initWithFormat:@"Sam Lau"];  // 由于anotherText对象引用另一个对象@"Sam Lau",那么就会自动调用[anotherText release]方法,使得@"Hello, world"对象的RC=1, @"Sam Lau"对象的RC=1
}
/*
 *  当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法,
 *  @"Hello, world"对象的RC=0和@"Sam Lau"对象的RC=0
 */

假设变量var被__strong修饰,当变量var指向有些对象objc,那么变量var持有某些对象objc的全数权

前方早已提过内存管理的四条规则

Objective-C对象的动作 Objective-C对象的方法
1. 创建一个对象并获取它的所有权 alloc/new/copy/mutableCopy (RC = 1)
2. 获取对象的所有权 retain (RC + 1)
3. 放弃对象的所有权 release (RC – 1)
4. 释放对象 dealloc (RC = 0 ,此时会调用该方法)

大家总括一下编译器是按以下措施来兑现的:

  • 对于规则1和规则2,是经过__strong变量来兑现,
  • 对此规则3的话,当变量当先它的功用域或被赋值或成员变量被丢掉时就能促成
  • 对此规则4,当奥德赛C=0时,系统就会自行调用

__weak ownership qualifier

实际编译器依据__strong修饰符来管理对象内存。然而__strong并无法解决引用循环(Reference
Cycle)难点:对象A持有对象B,反过来,对象B持有对象A;那样会促成无法放出内存造成内存败露问题。

引用Pro Multithreading and Memory Management for iOS and OS X的图

举二个简约的事例,有多少个类Test有个属性objc,有多少个目的test1和test2的性质objc相互引用test1和test2:

@interface Test : NSObject

@property (strong, nonatomic) id objc;

@end

{
    Test *test1 = [Test new];        /* 对象a */
    /* test1有一个强引用到对象a */

    Test *test2 = [Test new];        /* 对象b */
    /* test2有一个强引用到对象b */

    test1.objc = test2;              /* 对象a的成员变量objc有一个强引用到对象b */
    test2.objc = test1;              /* 对象b的成员变量objc有一个强引用到对象a */
}
/*   当变量test1超过它作用域时,它指向a对象会自动release
 *   当变量test2超过它作用域时,它指向b对象会自动release
 *   
 *   此时,b对象的objc成员变量仍持有一个强引用到对象a
 *   此时,a对象的objc成员变量仍持有一个强引用到对象b
 *   于是发生内存泄露
 */

哪些缓解?于是大家引用壹个__weakownership
qualifier,被它修饰的变量都不富有对象的全部权,而且当变量指向的目的的纳瓦拉C为0时,变量设置为nil。例如:

__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
NSLog(@"%@", text);

由于text变量被__weak修饰,text并不拥有@"Sam Lau"目的的全体权,@"Sam Lau"目的一创设就马上被释放,并且编译器给出警告⚠️,所以打印结果为(null)

从而,针对刚才的引用循环问题,只须要将Test类的质量objc设置weak修饰符,那么就能化解。

@interface Test : NSObject

@property (weak, nonatomic) id objc;

@end

{
    Test *test1 = [Test new];        /* 对象a */
    /* test1有一个强引用到对象a */

    Test *test2 = [Test new];        /* 对象b */
    /* test2有一个强引用到对象b */

    test1.objc = test2;              /* 对象a的成员变量objc不持有对象b */
    test2.objc = test1;              /* 对象b的成员变量objc不持有对象a */
}
/*   当变量test1超过它作用域时,它指向a对象会自动release
 *   当变量test2超过它作用域时,它指向b对象会自动release
 */

__unsafe_unretained ownership qualifier

__unsafe_unretained ownership
qualifier,正如名字所示,它是不安全的。它跟__weak貌似,被它修饰的变量都不享有对象的所有权,但当变量指向的对象的宝马X5C为0时,变量并不安装为nil,而是继续保留对象的地点;那样的话,对象有大概早已放出,但两次三番走访,就会造成专擅访问(Invalid
Access)
。例子如下:

__unsafe_unretained id obj0 = nil;

{
    id obj1 = [[NSObject alloc] init];     // 对象A
    /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */

    obj0 = obj1;
    /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */

    NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */

NSLog(@"B: %@", obj0);
/* 由于obj0是__unsafe_unretained,当它指向的对象RC=0时,它会继续保存对象的地址,所以两个地址相同 */

打印结果是内存地址相同

如果将__unsafe_unretained改为weak的话,八个打印结果将不相同

__weak id obj0 = nil;

{
    id obj1 = [[NSObject alloc] init];     // 对象A
    /* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */

    obj0 = obj1;
    /* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */

    NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */

NSLog(@"B: %@", obj0);
/* 由于obj0是__weak, 当它指向的对象RC=0时,它会自动设置为nil,所以两个打印结果将不同*/

__autoreleasing ownership qualifier

引入ARAV4C之后,让大家看看autorelease
pool有何样变化。没有A中华VC之前的写法如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];

[pool drain];

/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */

引入A奇骏C之后,写法比从前特别简洁:

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

对待以前的创设、使用和假释NSAutoreleasePool目的,今后您只须要将代码放在@autoreleasepool块即可。你也不需要调用autorelease艺术了,只须要用__autoreleasing修饰变量即可。

引用Pro Multithreading and Memory Management for iOS and OS X的图

可是大家很少或基本上不采取autorelease
pool。当大家运用XCode成立工程后,有一个app的入口文件main.m接纳了它:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

Property(属性)

有了A帕杰罗C之后,新的property
modifier也被引入到Objective-C类的property,例如:

@property (strong, nonatomic) NSString *text;

上面有张表来显示property modifier与ownership qualifier的相应关系

Property modifier Ownership qualifier
strong __strong
retain __strong
copy __strong
weak __weak
assign __unsafe_unretained
unsafe_unretained __unsafe_unretained

总结

要想通晓iOS/OS X的内存管理,首先要深入精通引用计数(Reference
Count)那么些概念以及内存管理的平整;在没引入A福特ExplorerC此前,大家都以通过retainrelease形式来手动管理内存,但引入AEvoqueC之后,大家得以器重编译器来支持自动调用retainrelease艺术来简化内存管理和滑降出错的恐怕性。尽管__strong修饰符可以履行大多数内存管理,但它不可以化解引用循环(Reference
Cycle)难点,于是又引入另二个修饰符__weak。被__strong修饰的变量都有着对象的全部权,而被__weak修饰的变量并不具有对象全部权。下篇大家介绍使用工具怎么样缓解广大内存难题:悬挂指针和内存走漏

参考资料