http://www.sufeinet.com/plugin.php?id=keke_group

苏飞论坛

 找回密码
 马上注册

QQ登录

只需一步,快速开始

分布式系统框架(V2.0) 轻松承载百亿数据,千万流量!讨论专区 - 源码下载 - 官方教程

HttpHelper爬虫框架(V2.7-含.netcore) HttpHelper官方出品,爬虫框架讨论区 - 源码下载 - 在线测试和代码生成

HttpHelper爬虫类(V2.0) 开源的爬虫类,支持多种模式和属性 源码 - 代码生成器 - 讨论区 - 教程- 例子

查看: 5171|回复: 6

[综合] 【iOS新手开发之旅】内存管理

[复制链接]
发表于 2018-12-26 17:34:21 | 显示全部楼层 |阅读模式
本帖最后由 竹林风 于 2018-12-26 17:34 编辑


  文章导航  

【iOS新手开发之旅】   http://www.sufeinet.com/thread-24000-1-1.html



引用计数

为了解释引用计数,做一个类比:员工在办公室使用灯的情景。

1455699399329355.png

  • 当第一个人进入办公室时,他需要使用灯,于是开灯,引用计数为1
  • 当另一个人进入办公室时,他也需要灯,引用计数为2,每当从一个人进入办公室时,引用计数加1
  • 当有一个人离开办公室时,引用计数减1,当引用计数为0时,也就是最后一个人离开办公室时,他不再玩需要灯,关灯离开办公室。


内存管理规则

从上面的例子,可以对比一个灯的动作与Objective-C对象动作的相似之处:

1455699433532824.png

Object-C对象的动作对应有哪些方法以及这些方法对引用计数有什么影响?

1455699479630119.png

当alloc一个对象objc,此时RC=1;在某个地方又retain了这个对象objc,此时RC加1,也就是RC= 2;由于调用alloc/retain一次,对应需要调用release一次来释放对象objc,所以你需要release对象objc两次,此时RC = 0;而当RC = 0时,系统会自动调用dealloc方法释放对象。

Autorelease Pool

在开发中,我们常常会使用到局部变量,局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool跟局部变量类似,当执行代码超过autorelease pool代码块时,所有放在autorealease pool的对象都会自动调用release。它的工作原理如下:

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


ARC的管理方法

iOS/OS X内存管理方法有两种:手动引用计数(Manual Reference Counting)和自动引用计数(Automatic Reference Counting)。从OS X Lion和iOS 5开始,不再需要程序员手动调用retain和release方法来管理Objective-C对象的内存,而是引入一种新的内存管理机制Automatic Reference Counting(ARC),简单来说,它让编译器来代替程序员来自动加入retain和release方法来持有和放弃对象的所有权。

在ARC内存管理机制中,id和其他对象类型变量必须是以下四个ownership qualifiers其中一个来修饰:

  • __strong(默认,如果不指定其他,编译器就默认加入)

  • __weak

  • __unsafe_unretained

  • __autoreleasing


所以在管理Objective-C对象内存的时候,你必须选择其中一个,下面会用一些列子来逐个解释它们的含义以及如何选择它们。

__strong ownership qualifier

如果我想创建一个字符串,使用完之后将它释放调用,使用MRC管理内存的写法应该是这样:

[Objective-C] 纯文本查看 复制代码
{

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

    NSLog(@"%@", text);

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

}

而如果是使用ARC方式的话,就text对象无需调用release方法,而是当text变量超过作用域时,编译器来自动加入[text release]方法来释放内存

[Objective-C] 纯文本查看 复制代码
{

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

    NSLog(@"%@", text);

}

/*

 *  当text超过作用域时,@"Hello, world"对象会自动释放,RC=0

 */

而当你将text赋值给其他变量anotherText时,MRC需要retain一下来持有所有权,当text和anotherText使用完之后,各个调用release方法来释放。

[Objective-C] 纯文本查看 复制代码
{

    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

}

而使用ARC的话,并不需要调用retain和release方法来持有跟释放对象。

[Objective-C] 纯文本查看 复制代码
{

    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语句来释放变量指向之前的对象。例如:

[Objective-C] 纯文本查看 复制代码
{

    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的所有权


前面已经讲过内存管理的四条规则,那么编译器的实现方法:

  • 对于规则1和规则2,是通过__strong变量来实现,

  • 对于规则3来说,当变量超过它的作用域或被赋值或成员变量被丢弃时就能实现

  • 对于规则4,当RC=0时,系统就会自动调用



__weak ownership qualifier

其实编译器根据__strong修饰符来管理对象内存。但是__strong并不能解决引用循环(Reference Cycle)问题:对象A持有对象B,反过来,对象B持有对象A;这样会导致不能释放内存造成内存泄露问题。

举一个简单的例子,有一个类Test有个属性objc,有两个对象test1和test2的属性objc互相引用test1和test2:

[Objective-C] 纯文本查看 复制代码
@interface Test : NSObject

@property (strong, nonatomic) id objc;

@end

[Objective-C] 纯文本查看 复制代码
{

    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,被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。例如:

[Objective-C] 纯文本查看 复制代码
__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];

NSLog(@"%@", text);

由于text变量被__weak修饰,text并不持有@"Sam Lau"对象的所有权,@"Sam Lau"对象一创建就马上被释放,并且编译器给出警告,所以打印结果为(null)。

所以,针对刚才的引用循环问题,只需要将Test类的属性objc设置weak修饰符,那么就能解决。

[Objective-C] 纯文本查看 复制代码
@interface Test : NSObject

@property (weak, nonatomic) id objc;

@end

[Objective-C] 纯文本查看 复制代码
{

    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相似,被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC为0时,变量并不设置为nil,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。例子如下:


[Objective-C] 纯文本查看 复制代码
__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的话,两个打印结果将不同


[Objective-C] 纯文本查看 复制代码
__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


引入ARC之后,让我们看看autorelease pool有哪些变化。没有ARC之前的写法如下:


[Objective-C] 纯文本查看 复制代码
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

 

// put object into pool

id obj = [[NSObject alloc] init];

[obj autorelease];

 

[pool drain];

 

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



引入ARC之后,写法比之前更加简洁:


[Objective-C] 纯文本查看 复制代码
@autoreleasepool {

    id __autoreleasing obj = [[NSObject alloc] init];

}



相比之前的创建、使用和释放NSAutoreleasePool对象,现在你只需要将代码放在@autoreleasepool块即可。你也不需要调用autorelease方法了,只需要用__autoreleasing修饰变量即可。


但是我们很少或基本上不使用autorelease pool。当我们使用XCode创建工程后,有一个app的入口文件main.m使用了它:


[Objective-C] 纯文本查看 复制代码
int main(int argc, char * argv[]) {

    @autoreleasepool {

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

    }

}



Property(属性)

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

[Objective-C] 纯文本查看 复制代码
@property (strong, nonatomic) NSString *text;

下面有张表来展示property modifier与ownership qualifier的对应关系

1455700566330232.png





1. 开通SVIP会员,免费下载本站所有源码,不限次数据,不限时间
2. 加官方QQ群,加官方微信群获取更多资源和帮助
3. 找站长苏飞做网站、商城、CRM、小程序、App、爬虫相关、项目外包等点这里
 楼主| 发表于 2018-12-27 14:08:28 | 显示全部楼层

要想掌握iOS/OS X的内存管理,首先要深入理解引用计数(Reference Count)这个概念以及内存管理的规则;在没引入ARC之前,我们都是通过retain和release方法来手动管理内存,但引入ARC之后,我们可以借助编译器来帮忙自动调用retain和release方法来简化内存管理和减低出错的可能性。虽然__strong修饰符能够执行大多数内存管理,但它不能解决引用循环(Reference Cycle)问题,于是又引入另一个修饰符__weak。被__strong修饰的变量都持有对象的所有权,而被__weak修饰的变量并不持有对象所有权
发表于 2018-12-26 19:11:58 | 显示全部楼层
我只是路过打酱油的。
发表于 2018-12-27 09:38:50 | 显示全部楼层
强烈支持楼主ing……
发表于 2018-12-27 13:44:50 | 显示全部楼层
强烈支持楼主ing……
发表于 2018-12-27 17:14:52 | 显示全部楼层
强烈支持楼主ing……
发表于 2018-12-30 15:47:16 | 显示全部楼层
真是难得给力的帖子啊。
您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

QQ|手机版|小黑屋|手机版|联系我们|关于我们|广告合作|苏飞论坛 ( 豫ICP备18043678号-2)

GMT+8, 2024-11-25 12:01

© 2014-2021

快速回复 返回顶部 返回列表