内存管理的一些概念
1.1 为什么要使用内存管理?
严格的内存管理,能够是我们的应用程在性能上有很大的提高
如果忽略内存管理,可能导致应用占用内存过高,导致程序崩溃
1.2 OC的内存管理主要有三种方式:
ARC(自动内存计数)
手动内存计数
内存池
1.3 OC中内存管理的基本思想:
保证任何时候指向对象的指针个数和对象的引用计数相同,多一个指针指向这个对象这个对象的引用计数就加1,少一个指针指向这个对象这个对象的引用计数就减1。没有指针指向这个对象对象就被释放了。
1.4 苹果官方基础内存管理规则:
自动内存管理
ARC
的认识和理解? ARC
是iOS 5推出的新功能。编译器在代码里适当的地方自动插入 retain
/ release
完成内存管理(引用计数)。引用计数器
简单易用的标题
2.1 自动释放池底层怎么实现?
(以栈的方式实现的)(系统自动创建,系统自动释放)栈里面的(先进后出) 内存里面有栈,栈里面有自动释放池。 自动释放池以栈的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶。当一个对象收到发送autorelease消息时,它被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,它们从栈中被删除,并且会给池子里面所有的对象都会做一次release操作。
2.2 什么是自动释放池?
答:自动释放池是用来存储多个对象类型的指针变量
2.3 自动释放池对池内对象的作用?
被存入到自动释放池内的对象,当自动释放池被销毁时,会对池内的对象全部做一次release操作
2.4 对象如何放入到自动释放池中?
当你确定要将对象放入到池中的时候,只需要调用对象的 autorelease
对象方法就可以把对象放入到自动释放池中
2.5 多次调用对象的autorelease方法会导致什么问题? 答:多次将地址存到自动释放池中,导致野指针异常
2.6 自动释放池作用
将对象与自动释放池建立关系,池子内调用 autorelease
方法,在自动释放池销毁时销毁对象,延迟 release
销毁时间
2.7 自动释放池,什么时候创建?
程序刚启动的时候,也会创建一个自动释放池
产生事件以后,运行循环开始处理事件,就会创建自动释放池
2.8 什么时候销毁的?
程序运行结束之前销毁
事件处理结束以后,会销毁自动释放池
还有在池子满的时候,也会销毁
2.9 自动释放池使用注意:
不要把大量循环操作放在释放池下,因为这会导致大量循环内的对象没有被回收,这种情况下应该手动写 release
代码。尽量避免对大内存对象使用 autorelease
,否则会延迟大内存的回收。
2.10 autorelease的对象是在什么时候被release的?
答:autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的 Autoreleasepool中,当该pool被释放时,该pool中的所有Object会被调用Release。对于每一个Runloop,系统会隐式创建一个Autoreleasepool,这样所有的releasepool会构成一个象CallStack一样的一个栈式结构,在每一个 Runloop结束时,当前栈顶的Autoreleasepool会被销毁,这样这个pool里的每个Object(就是autorelease的对象)会被release。那什么是一个Runloop呢?一个UI事件,Timer call,delegate call, 都会是一个新的Runloop。
2.11 If we don’t create any autorelease pool in our application then is there any autorelease pool already provided to us? 系统会默认会不定时地创建和销毁自动释放池
2.12 When you will create an autorelease pool in your application? 当不需要精确地控制对象的释放时间时,可以手动创建自动释放池
@property内存管理策略的选择
读写属性:readwrite
、readonly
setter语意:assign
、retain
/ copy
原子性(多线程管理):atomic
、 nonatomic
强弱引用:strong
、 weak
3.1 读写属性:
readwrite
:同时生成 set
和 get
方法(默认)
readonly
:只会生成 get
方法
3.2 控制set方法的内存管理:
retain
:release
旧值,retain
新值。希望获得源对象的所有权时,对其他 NSObject
和其子类(用于 OC
对象)
copy
:release
旧值,copy
新值。希望获得源对象的副本而不改变源对象内容时(一般用于 NSString
,block
)
assign
:直接赋值,不做任何内存管理(默认属性),控制需不需生成 set
方法。对基础数据类型 (NSInteger
,CGFloat
)和C数据类型(int
, float
, double
, char
, 等等)
3.3 原子性(多线程管理):
@synchronized
(变量)来对该变量进行加锁(加锁的目的常常是为了同步或保证原子操作)。3.4 强指针(strong)、弱指针(weak)
strong
strong
系统一般不会自动释放,在 oc
中,对象默认为强指针。作用域销毁时销毁引用。在实际开放中一般属性对象一般 strong
来修饰(NSArray
,NSDictionary
),在使用懒加载定义控件的时候,一般也用strong。
weak
所引用对象的计数器不会加一,当对象被释放时指针会被自动赋值为 nil
,系统会立刻释放对象。__unsafe_unretained
弱引用 当对象被释放时指针不会被自动赋值为 ni
在ARC时属性的修饰符是可以用 assign
的(相当于 __unsafe_unretained
)
在ARC时属性的修饰符是可以用 retain
的 (相当于 __strong
)sb
或者 xib
给控件拖线的时候,为什么拖出来的先属性都是用 weak 修饰呢?
由于在向 xib
或者 sb
里面添加控件的时候,添加的子视图是添加到了跟视图 View
上面,而 控制器 Controller
对其根视图 View
默认是强引用的,当我们的子控件添加到 view
上面的时候,self.view addSubView:
这个方法会对添加的控件进行强引用,如果在用 strong
对添加的子控件进行修饰的话,相当于有两条强指针对子控件进行强引用, 为了避免这种情况,所以用 weak
修饰。assign
还是用 weak
?assign
: 如果由于某些原因代理对象被释放了,代理指针就变成了野指针。
weak
: 如果由于某些原因代理对象被释放了,代理指针就变成了空指针,更安全(weak
不能修饰基本数据类型,只能修饰对象)。内存分析
什么情况下会发生内存泄漏和内存溢出?
内存泄漏:堆里不再使用的对象没有被销毁,依然占据着内存。 内存溢出:一次内存泄露危害可以忽略,但内存泄露多了,内存迟早会被占光,最终会导致内存溢出!当程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如数据长度比较小的数据类型 存储了数据长度比较大的数据。
关于图片占用内存管理
4.1 图片加载占用内存对比
imageName:
加载图片:imageWithContentsOfFile:
加载图片imageName:
来加载(按钮图标/主页里面图片)imageWithContentsOfFile:
来加载(版本新特性/相册)4.2 图片在沙盒中的存在形式
内存管理问题
5.1 单个对象内存管理的问题
5.2 多个对象内存管理的问题
内存相关的一些数据结构的对比
6.1 简述内存分区情况
6.2 手机的存储空间分为内存(RAM)和闪存(Flash)两种
6.3 堆和栈的区别?
面试题
Foundation
对象( OC
对象) : 只要方法中包含了 alloc\new\copy\mutableCopy\retain
等关键字,那么这些方法产生的对象, 就必须在不再使用的时候调用1次 release
或者1次 autorelease
。
CoreFoundation
对象( C
对象) : 只要函数中包含了 create\new\copy\retain
等关键字, 那么这些方法产生的对象, 就必须在不再使用的时候调用1次 CFRelease
或者其他 release
函数。CoreFoundation
对象( C
对象) : 只要函数中包含了 create\new\copy\retain
等关键字, 那么这些方法产生的对象, 就必须在不再使用的时候调用1次 CFRelease
或者其他 release
函数。// block的内存默认在栈里面(系统自动管理)
void (^test)() = ^{
};
// 如果对block进行了Copy操作, block的内存会迁移到堆里面(需要通过代码管理内存)
Block_copy(test);
// 在不需要使用block的时候, 应该做1次release操作
Block_release(test);
[test release];
B *b = [[B alloc]init];
[self.view addSubview:b.view];
@property (nonatomic, retain) NSString *name;
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
@property(nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name copy];
}
}
- (void)dealloc {
self.name = nil;
// 上边这句相当于下边两句
[_name release];
_name = nil;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1
Person *p = [[Person alloc] init];
p.age = 20;
// 0 (p指向的内存已经是坏内存, 称person对象为僵尸对象)
// p称为野指针, 野指针: 指向僵尸对象(坏内存)的指针
[p release];
// p称为空指针
p = nil;
p.age = 40;
// [0 setAge:40];
// message sent to deallocated instance 0x100201950
// 给空指针发消息不会报错
[p release];
}
return 0;
}
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10; // 栈
int b = 20; // 栈
// c : 栈
// Car对象(计数器==1) : 堆
Car *c = [[Car alloc] init];
}
// 当autoreleasepool执行完后后, 栈里面的变量a\b\c都会被回收
// 但是堆里面的Car对象还会留在内存中, 因为它是计数器依然是1
return 0;
}
NSMutableArray* ary = [[NSMutableArray array] retain];
NSString *str = [NSString stringWithFormat:@"test"]; // 1
[str retain]; // 2
[ary addObject:str]; // 3
NSLog(@"%d", [str retainCount]);
[str retain]; // 4
[str release]; // 3
[str release]; // 2
NSLog(@"%d", [str retainCount]);
[ary removeAllObjects]; // 1
NSLog(@"%d", [str retainCount]);