文章目录

  • 1. iOS内存分配区域
    • 1.1 栈区
    • 1.2 堆区
    • 1.3 常量区
    • 1.4 全局区/静态区
      • 1.4.1 static静态变量
      • 1.4.2 extern全局变量
    • 1.5 代码区
  • 2. iOS的编译链接
    • 2.1 预处理
    • 2.2 编译
    • 2.3 汇编
    • 2.4 链接
  • 3. 引用计数和MRC
    • 3.1 内存管理的思考方式(四个基本法则)
      • 3.1.1 自己生成的对象,自己持有
      • 3.1.2 非自己生成的对象,自己也能持有
      • 3.1.3 不再需要自己持有的对象就将其释放
      • 3.1.4 非自己持有的对象无法释放
    • 3.2 内存管理基本表格
    • 3.3 autorelease
      • 使用场景
    • 3.4 retainCount
  • 4. ARC自动引用计数
    • 4.1 所有权修饰符
      • 4.1.1 __strong修饰符
      • 4.1.2 __weak修饰符
      • 4.1.3 __unsafe_unretained修饰符
      • 4.1.4 __autoreleasing修饰符
  • 5.相关问题
    • 5.1 ARC在编译和运行期间做了什么?
    • 5.2 一些规则
    • 5.3 属性关键字和所有权修饰符

1. iOS内存分配区域

常说的五大内存分配区域,指的是栈区、堆区、常量区、全局区、代码区。

1.1 栈区

是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数等,是一块连续的内存区域,遵循先进后出(FILO)原则。一般在运行时分配。它的分配由高地址空间向低地址空间分配。

优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效。
缺点:栈的内存大小有限制,数据不灵活。

例如:下图,创建两个变量,存放在栈区,地址是递减4。

1.2 堆区

堆区是由程序员手动管理。 主要用来存放动态分配的对象类型数据。是不连续的内存区域。在MRC环境下,由程序员手动释放,在ARC环境下,由编译器自动释放。一般在运行时分配。它的分配是从低地址空间向高地址空间分配。
优点:灵活方便,数据适应面广泛。
缺点:需手动管理,速度慢、容易产生内存碎片。

例如:下图,创建两个对象,存放在堆区,地址递增。

1.3 常量区

常量区存放的就是字符串常量和基本类型常量。在编译时分配,程序结束后回收。

1.4 全局区/静态区

全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在.data段,未初始化的全局变量和静态变量在相邻的.bss区域,在编译时分配,程序结束后由系统释放。

1.4.1 static静态变量

  1. 全局静态变量
    全局变量和全局静态变量的生命周期是一样的,都是在堆中的静态区,在整个工程执行期间内一直存在。
    特点如下:
    1)存储区:静态存储区没变(静态存储区在整个程序运行期间都存在);
    2)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。

好处:
1)不会被其他文件所访问,修改;
2)其他文件中可以使用相同名字的变量,不会发生冲突。

  1. 局部静态变量
    特点如下:
    1)存储区:有栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它;
    2)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

1.4.2 extern全局变量

==只是用来获取全局变量(包括静态全局变量)的值,不能用于定义变量。==先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。

全局静态变量与全局变量 其实本质上是没有区别的,只是存在修饰区别,一个static让其只能内部使用,一个extern让其可以外部使用。

1.5 代码区

代码区是在编译时分配,主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的。

2. iOS的编译链接

在一个程序从代码到执行程序过程里,存在四个阶段:预编译、编译、汇编、静态链接。

2.1 预处理

预处理也被称作预编译(Prepressing),是将main.m文件编译成mian.i文件,指令如下:

clang -E main.m -o main.i

处理源代码文件中以#开头的预编译指令。

2.2 编译

编译过程就是把上面的main.i文件进行:词法分析语法分析静态分析,优化生成相应的汇编代码,最终生成main.s文件。,指令如下:

clang -S main.i -o main.s
名称 处理过程
词法分析 把源代码的字符序列分割成一个个token(关键字、表示符、字面量、特殊符号),比如把标识符放到符号表里面。
语法分析 生成抽象语法树 AST,此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如“*”是乘号还是对指针取内容;表达式不合法、括号不匹配等,都会报错。
静态分析 分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。
中间语法生成 CodeGen根据AST自上向下逐步翻译成LLVM IR(连接着编译器前端和编译器后端),并且对在编译期就可以确定的表达式进行优化,比如代码里面的a=1+3,可以优化成a=4。(假如开启了bitcode)
目标代码生成与优化 根据中间语法生成依赖具体机器的汇编语言;并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那么就给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元里面,那就等到链接的时候才能确定地址。

2.3 汇编

这个过程就是把上面得到的main.s文件里面的汇编指令翻译成机器指令,最终生成等到main.o文件;指令如下:

clang -c main.s -o main.o

2.4 链接

这个过程就是将main.o编译成对应的Mach-O文件,也就是我们常说的可执行文件,指令如下:

clang main.o -o main

链接的本质就是把一个或多个目标文件和需要的库(静态库/动态库,如果需要的话)组合成一个文件(Mach-O可执行文件)。

3. 引用计数和MRC

在OC中,使用引用计数来进行内存管理。每个对象都有一个与其相对应的引用计数器,当持有一个对象,这个对象的引用计数就会递增;当这个对象的某个持有被释放,这个对象的引用计数就会递减。当这个对象的引用计数变为0,那么这个对象就会被系统回收。

当一个对象使用完没有释放,此时其引用计数永远大于1。该对象就会一直占用其分配在堆内存的空间,就会导致内存泄露。内存泄露到一定程度有可能导致内存溢出,进而导致程序崩溃。

3.1 内存管理的思考方式(四个基本法则)

3.1.1 自己生成的对象,自己持有

alloc\new\copy\mutableCopy 方法名开头来创建的对象意味着自己生成的对象只有自己持有。

id obj = [[NSObject alloc] init];

3.1.2 非自己生成的对象,自己也能持有

用 alloc / new / copy / mutableCopy 以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。(比如 NSMutableArray 类的 array方法),但是我们可以通过retain来手动持有对象。

//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];

3.1.3 不再需要自己持有的对象就将其释放

不再需要自己持有的对象就将其使用release释放。

  1. 释放自己生成并持有的对象
//自己持有对象
id obj = [[NSObject alloc] init];
//释放对象
//指向对象的指针仍然被保留在变量obj中,貌似可以访问,但对象一经释放绝对不可访问
[obj release];
  1. 释放非自己生成但持有的对象
id obj = [NSMutableArray array];
//持有该对象
[obj retain];[obj release];

注意:(以下代码在MRC环境下运行)

运行后应该出现的情况是程序崩溃,因为在array创建后引用计数变为1,之后使用release是引用计数减1,引用计数变为0,然后将NSMutableArray类对象A内存块释放,在打印其地址时应该是程序崩溃,但是运行后程序不一定崩溃,是因为array指针依然存在并且指向那段内存,而内存还没有被占用,所以可以访问到,有时又会报错,相当于碰运气…

3.1.4 非自己持有的对象无法释放

如果不是自己持有的对象一定不能进行释放,倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。

  1. 释放两次

例如:自己生成并持有,在释放完不再需要的对象之后再次释放(释放两次)

//自己生成并持有对象
id obj = [[NSObject alloc] init];
//释放对象
[obj release];
//释放对象
[obj release];
  1. 本就不持有

但是以上情况现在都不会崩溃,可能苹果修复了。。。

3.2 内存管理基本表格

对象操作 Objective-C方法 引用计数
生成并持有对象 alloc/new/copy/mutableCopy方法 +1
持有对象 retain +1
释放对象 release -1
废弃对象 dealloc 对象所占内存解除分配 并放回“可用内存池中”

3.3 autorelease

autorelease即“自动释放”,类似于C语言中的局部变量。C语言的局部变量:程序执行时,若某局部变量超出其作用域,该局部变量自动废弃。autorelease像C语言的局部变量那样对待对象实例。当超出其作用域时,对象实例的release实例方法被调用,释放该变量。

前面介绍到了释放对象使用release。也可以使用autorelease,二者的区别是什么呢?

调用 autorelease 方法,就会把该对象放到离自己最近的自动释放池中(栈顶的释放池,多重自动释放池嵌套是以栈的形式存取的),即:使对象的持有权转移给了自动释放池(即注册到了自动释放池中),调用方拿到了对象,但这个对象还不被调用方所持有。当自动释放池销毁时,其中的所有的对象都会调用一次release操作。

其实也就是autorelease 方法不会改变调用者的引用计数,它只是改变了对象释放时机,不再让程序员负责释放这个对象,而是交给自动释放池去处理 。

autorelease 方法相当于把调用者注册到 autoreleasepool 中,ARC环境下不能显式地调用 autorelease 方法和显式地创建 NSAutoreleasePool 对象,但可以使用@autoreleasepool { }块代替(并不代表块中所有内容都被注册到了自动释放池中)。

对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。

使用场景

  1. 循环中创建了许多临时对象,在循环里使用自动释放池,用来减少高内存占用。
  2. 开启子线程的时候要自己创建自己的释放池,否则可能会发生内存泄漏。

3.4 retainCount

我们在MRC中,有时可能会想要打印引用计数,但retainCount方法并不是很有用,由于对象可能会处于自动释放池中,这会导致打印的引用计数并不精准,而且其他程序库也很有可能自行保留或释放对象,这都会扰乱引用计数的具体值。

4. ARC自动引用计数

4.1 所有权修饰符

ARC有效时,id类型和对象类型必须附加所有权修饰符(默认为__strong),修饰符有四种:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

4.1.1 __strong修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符,表示强引用。
不论调用哪种方法,强引用修饰的变量会持有该对象,如果已经持有则引用计数不会增加。

持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

{//因为变量obj为强引用,所以持有对象id __strong obj = [[NSObject alloc] init];
}
//超出作用域后强引用失效,对象所有者不存在,被自动释放

附有__strong修饰符的变量也可以互相赋值

id obj = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
id obj3 = nil;
obj3 = obj2;
obj2 = obj;

赋值的本质是强引用的转变。

附有__strong修饰符同__weak修饰符,__autorelease修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil,即:

id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;
//等同于
id __strong obj1 = nil;
id __weak obj2 = nil;
id __autoreleasing obj3 = nil;

使用__strong可能会导致循环引用问题:
例如:

@interface MyObject : NSObject {id __strong object;
}
- (void)setObject:(id __strong) obj;
@end@implementation MyObject
- (instancetype)init {self = [super init];return self;
}- (void)setObject:(id __strong) obj {_object = obj;
}
@end

主函数

#import <Foundation/Foundation.h>
#import "MyObject.h"
int main(int argc, const char * argv[]) {{id test1 = [[MyObject alloc] init];//对象Aid test2 = [[MyObject alloc] init];//对象B[test1 setObject:test2];[test2 setObject:test1];}return 0;
}

我们可以看出以上代码中,持有对象A的是test1和test2的object;持有对象B的是test2和test1的object。test1和test2超出作用域后,强引用失效,自动释放对象,但是test1的object和test2的object分别持有着对象B和对象A,所有对象B和对象A并没有释放,这样就造成了内存泄漏。

4.1.2 __weak修饰符

在介绍__strong修饰符时,我们说到一个循环引用的问题,现在就可以利用__weak修饰符来解决这个问题。

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

但是不能像上面这么解决,原因是创建的对象如果弱引用的话,就没有持有该对象的变量了,就会被释放。所以应该像下面这么写:

id __strong sObj = [[NSObject alloc] init];
id __weak wObj = sObj;

让sObj持有它。

所以上面循环引用的代码就可以写成:

@interface MyObject : NSObject {id __weak object;
}
- (void)setObject:(id __strong) obj;
@end@implementation MyObject
- (instancetype)init {self = [super init];return self;
}- (void)setObject:(id __strong) obj {object = obj;
}
@end

这样修改后,test1持有对象A,test2持有对象B,而test1的object和test2的object并不持有对象A和对象B,超出作用域后,就没有变量再持有对象A和对象B,对象A和对象B就会被释放,就解决了循环引用的问题。

__weak还有一个作用,就是在持有某对象的弱引用,如果对象被废弃,弱引用将自动失效且为nil。

 id __weak obj;{id obj0 = [[NSObject alloc] init];obj = obj0;NSLog(@"%@", obj);}NSLog(@"%@", obj);

在《Objective-C高级编程》中写到:附有__weak修饰符的变量,即是使用注册到自动释放池中的对象,但是我在测试时,并不是这样:

 id obj = [[NSObject alloc] init];id __weak wObj = obj;NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);_objc_autoreleasePoolPrint();

难道书中写错了? 我们改一种使用__weak修饰变量的方法:

这种情况下就和书中的结论不谋而合了。
每使用一次__weak对象,运行时系统都会将其指向的原始对象先retain,之后保存到自动释放池中。因此如果大量调用__weak对象,则会重复进行此工作。不仅耗费无意义的性能(重复存储同一对象),还会使内存在短时间内大量增长。

那NSLog为什么和书中的结论不一样呢?这里还有点不解。

4.1.3 __unsafe_unretained修饰符

正如其名,__unsafe_unretained修饰符是不安全的,且附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象

同样,附有__unsafe_unretained修饰符不能直接使用。

与__weak修饰符不同的是:附有__weak修饰符的弱引用变量在对象释放时,会自动置为nil,但是__unsafe__unretained修饰符不会,所以一旦对象释放,则会成为悬垂指针,程序崩溃,所以__unnsafe_unretained修饰符的变量一定要在赋值的对象存在的情况下使用。

4.1.4 __autoreleasing修饰符

上面已经讲过ARC无效时autorelease的使用,在ARC有效时,可以写成这样:

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


@autoreleasepool块即相当于上文的NSAutoreleasePool类生成、持有及废弃。
附有__autoreleasing修饰符相当于变量调用了autorelease方法。
以下为使用__weak修饰符的例子,虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到AutoreleasePool的对象

id __weak wObj = sObj;
NSLog(@"%@", [wObj class]);
//等同于
id __weak wObj = sObj;
id __autoreleasing tmp = wObj;
NSLog(@"%@", [tmp class]);

因为弱引用不持有,在访问对象的过程中对象有可能被废弃,要保证正常访问就需要将其注册到自动释放池中

一般情况下,id的指针或对象的指针会默认附加上__autoreleasing修饰符,即:

(NSError **)error
//等同于
(NSError * __autoreleasing *)error

然而,下面的源代码会有编译错误

NSError *error = nil;
NSError **pError = &error;


原因是所有权不同,在赋值时,所有权必须一致

NSError *error = nil;
NSError * __strong *pError = &error;

这样就对了。

5.相关问题

5.1 ARC在编译和运行期间做了什么?

1.在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
2.ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。

5.2 一些规则

  1. 只能在ARC无效时使用retain/release/retainCount/autorelease等方法,ARC有效时也不能使用NSAllocateObject/NSDeallocateObject函数。
  2. 必须遵守内存管理的方法名规则,即:
    • 以alloc/new/copy/mutableCopy开始命名的方法在返回对象时,必须返回给调用方所应当持有的对象。
    • 以init开始命名的方法在返回对象时,返回的对象应为id类型或该方法声明类的对象类型,亦或是该类的超类或子类。
  3. 不要显示调用dealloc,因为dealloc无法释放不属于该对象的一些东西,比如:通知的观察者和KVO的观察者等。
  4. 使用@autoreleasepool块代替NSAutoreleasePool
  5. 不能使用区域NSZone。
  6. 对象性变量不能作为C语言结构体的成员。
  7. 显式转换id和void*。

5.3 属性关键字和所有权修饰符

【iOS开发】—— iOS内存管理相关推荐

  1. iOS开发ARC内存管理技术要点

    本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...

  2. iOS开发ARC内存管理

    本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging ARC的本质 ARC是编译器(时)特性,而不是运行时特性,更不是垃 ...

  3. (0048)iOS开发之内存管理探究

    注意网上很多人支招在ARC模式下可以利用_objc_rootRetainCount()或者CFGetRetainCount()取得 retainCount都是不准确的,特别是在对象拷贝操作之后你会发现 ...

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

    iOS/OS X内存管理(一):基本概念与原理 发表于21小时前| 1585次阅读| 来源CSDN| 8 条评论| 作者刘耀柱 移动开发iOSObjective-C内存管理内存泄露局部变量开发经验 a ...

  5. iOS/OS X内存管理(二):借助工具解决内存问题

    上一篇博客<iOS/OS X内存管理(一):基本概念与原理>主要讲了iOS/OS X内存管理中引用计数和内存管理规则,以及引入ARC新的内存管理机制之后如何选择ownership qual ...

  6. iOS夯实:内存管理

    最近的学习计划是将iOS的机制原理好好重新打磨学习一下,总结和加入自己的思考. 有不正确的地方,多多指正. 基本信息 Objective-C 提供了两种内存管理方式. MRR (manual reta ...

  7. iOS开发经验总结—内存管理

    From: http://ihuby.info/2011/07/18/ios%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93-%E5%86% ...

  8. iOS OC08,09_内存管理

    //管理内存有三种方式,//1.是垃圾回收,java常见的管理内存的方法,系统来检測对象是否被使用,是否被释放//2.MRC手动管理引用计数,iOS管理内存的方式,程序猿通过手动的方式来管理对象是否被 ...

  9. iOS开发--- iOS编程浅析

    1.简介 IOS是由苹果公司为iPhone.iPod touch和iPad等设备开发的操作系统. 2.知识点 1.IOS系统 iPhone OS(现在叫iOS)是iPhone, iPod touch ...

  10. iOS开发--iOS及Mac开源项目和学习资料

    文/零距离仰望星空(简书作者) 原文链接:http://www.jianshu.com/p/f6cdbc8192ba 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". ...

最新文章

  1. git 还原版本方法
  2. opencv Mat 函数--CheckVector
  3. mysql 多表查询 优化_Mysql 多表联合查询效率分析及优化
  4. 查看Linux进程CPU过高具体的线程堆栈(不中断程序)
  5. 雷军晒3亿估值,意欲“收编”台湾硬件创业者?
  6. Python MySQL更新表
  7. springmvc拦截器配置
  8. 数组算法 中部删除数据 1202
  9. Visual Studio Code 编辑器使用
  10. 转载:常用CSS缩写语法总结
  11. python保存mat文件_python读取文件——python读取和保存mat文件
  12. 面向全场景模块化设计,京东智联云的服务器部署有多灵活?
  13. Refresh page
  14. 使用SVM分类器进行图像多分类
  15. 基于tushare和python的证券市场价格分析
  16. IE8 证书错误,导航已阻止,解决方法(selenium)
  17. 密码加密md5和加盐处理
  18. 连续竞价java_撮合引擎开发:开篇
  19. hdu5442 后缀数组
  20. linux 4g 拨号,4G模块Linux PPP拨号说明

热门文章

  1. 企业微信hook之实战开发
  2. 华为鲲鹏认证openeuler系统忘记root密码时如何破解root密码
  3. 面霸是怎样练成的?“2023”带你过关斩将,手撕面试官
  4. Excel去除字符中的空格(trim)
  5. 2012年春节祝福短信集锦
  6. 蓝牙耳机连接ubuntu
  7. 马工程_管理学C_考前重点
  8. 全球手机验证码发放+短视频去水印等组合微信小程序源码
  9. 【操作系统】内存管理设计性实验报告
  10. 企业入门实战(一)基于Redhat7.6环境虚拟机的封装