iOS 内存管理

iOS进阶

这篇文章简单的根据几个常见的面试题,引导出了iOS在ARC模式下仍需要注意的一些内存管理方面的小知识,本文中提到的一些知识点需要仔细的考虑。

1、存储空间的类别和管理方式

1
2
3
4
5
6
7
8
9
10
11

1、栈区(stack)——由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap)——一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

3、全局区(静态区)(static)——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

4、文字常量区——常量字符串就是放在这里的。程序结束后由系统释放。

5、程序代码区——存放函数体的二进制代码。

1、NSString *str = @”111”

首先str是一个指针,这种指针变量本身肯定是在栈空间,而@”111”是一个常量
字符串,注意iOS中NSString所指向的NSString是一个常量,分配在常量区,
你可能会问,常量存储区不是不能改变吗?我们可以验证下

1
2
3
4
5
6
NSString *str1 = @"121";
NSLog(@"%p",str1);
// 两个打印结果相同,可以证明str1指向一个常量
NSLog(@"%p",@"121");
// 而通过subString 等方法返回的是一个新的常量,NSNumber同理

关于NSString的分析 可以参考这篇文章

3、OC和C在数组表示中的区别

NSArray

2、使用copy和strong修饰数组的区别

代码:

@property (nonatomic, strong) NSString *str1;

@property (nonatomic, copy) NSString *str2;

结果:

string

3、自动释放池的使用

autorelaesepool

附赠 :

1
2
3
4
5
6
7
8

测试代码耗时的方法:
NSLog(@"开始");
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
[self answer1];
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"外部 %f", end - start);

2、const关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const char *p;
char const *p;
char * const p;
const char * const p;

参考答案:

const char *p定义了一个指向不可变的字符串的字符指针,可以这么看:const char *为类型,p是变量。
char const *p与上一个是一样的。
char * const p定义了一个指向字符串的指针,该指针值不可改变,即不可改变指向。这么看:char *是类型,const是修饰变量p,也就是说p是一个常量
const char * const p定义了一个指向不可变的字符串的字符指针,且该指针也不可改变指向。这一个就很容易看出来了。两个const分别修饰,因此都是不可变的。


int age = 10;
int money = 200;

// const修饰的是*p1
int const * p1 = &age;


// const修饰的是p2
int * const p2 = &age;

*p1 = 999; // 错误
p1 = &money; // 正确
*p2 = 999; // 正确
p2 = &money; // 错误

// OC字符串常量的定义
NSString * const url = @"http://baidu.com";
url = @"http://apple.com"; // 错误


4、static 关键字

1
2
3
4
5
6
7
8
9
10
11
static的作用
-修饰`局部变量` :修改的是`生命周期`

1> 被static修饰的局部变量,在整个程序运行过程中,都只有一份内存
2> 被static修饰的局部变量,并没有改变作用域

修饰`全局变量` : 修改的是`作用域`

1> 没有被static修饰的全局变量,能被项目中的任何文件访问
2> 被static修饰的全局变量,只能被本文件访问(定义这个变量的文件)

3、iOS内存管理(来自标哥)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
描述一下iOS的内存管理,在开发中对于内存的使用和优化包含哪些方面。我们在开发中应该注意哪些问题。

内存管理准则:谁强引用过,谁就在不再使用时使引用计数减一。

对于内存的使用和优化常见的有以下方面:

重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用。

尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能。

不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多。

选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。

gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。

延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。

数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。

处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉
重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。

避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要。

使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存。

正确选择图片加载方式:详情阅读细读UIImage加载方式

4、数组名称与数组首元素地址


1
2
3
4
5
6
int array[3] = {11, 22, 33};
// array可看做是一个指针:指向array[0](某个数组元素),指向是int类型的数据(4个字节的数据)
// &array[0]可看做是一个指针:指向array[0](某个数组元素),指向是int类型的数据(4个字节的数据)
// &array可看做是一个指针:指向array(整个数组),指向int[3]类型的数据(12个字节的数据)

// &array[0] === array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int array[2][3] = {
{1, 11, 111},
{2, 22, 222}
};
// array[0] : 指向array[0][0]元素(4个字节的空间)
// array[1] : 指向array[1][0]元素(4个字节的空间)
// &array[0][0] : 指向array[0][0]元素(4个字节的空间)
// &array[1][0] : 指向array[1][0]元素(4个字节的空间)
// array : 指向array[0](数组{1, 11, 111},12个字节的空间)
// &array : 指向array(整个数组,24个字节的空间)

// array[0] === &array[0][0]
// array[1] === &array[1][0]
// array === &array[0]