iOS的操作系统运行的简单原理
当前的主流iPhone实际物理内存一般都不超过1G,要高效利用的这1G空间,达到高性能,ios是怎么做的嗯?iOS的操作系统更是抛弃了不必要的复杂——系统层面不支持App内存页换出,当内存吃紧时,对于可以重新载入的只读数据来说,直接清理掉,而对于可写的数据,只能通过App自己去管理维护。内存紧张时,iOS会向App发起memory warning,不配合释放足够内存者,kill掉!
进程的内存结构
最简单来说分为两大部分:指令+数据。再细分一点,五部分:代码(指令),初始化数据区,未初始化数据区,堆,栈。
** 主要的布局如下图所示:**
![](https://img.haomeiwen.com/i2050812/cb9178f537d01cbf.png)
** 从代码上来分析:**
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); 分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
-
栈区(stack):由编译器自动分配和释放,存放 函数的参数值、局部变量值等,栈是向低地址扩展的数据结构,是一块连续的内存区域。即栈顶的地址和栈的最大容量是系统预先设定好的。
-
堆(heap):一般由程序员分配和释放、若程序员不释放,程序结束后由OS回收。堆是不连续的内存区域,向高地址扩展的数据结构。
-
区别:堆只有动态分配的,栈有静态分配(编译器完成,比如局部变量)和动态分配的(alloc函数,编译器来释放);栈的分配效率比堆高,但堆灵活和内存占用大,由于堆空间的不连续性,会存在碎片问题。
-
全局区和静态区:全局变量和静态变量是放在一块的,内部划分初始化和未初始化两个块区。程序结束后由系统释放。
-
文字常量区:常量字符串放在这里的,程序结束后由系统释放
-
程序代码区:存放函数体的二进制代码
iOS的内存管理机制
** MRC(iOS 5.0 之前)**
MRC,手动引用计数, 手动管理内存的三个原则
-
谁创建,谁释放,如果你通过alloc new copy来创建一个对象,那么你必须调用release或者这autorrelease。换句话说,,不是你创建的,你就不用去释放。
-
除了alloc、new、copy之外的方法创建的对象都被声明了autorelease。
-
谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的,你都要调用release
** ARC(iOS 5.0 之后)**
自 iOS5.0 之后,苹果开始支持ARC(Automatic Reference Counting)自动引用计数,是一个编译器特性,编译器自动的添加完成了内存管理的引用计数代码。arc只针对OC对象类型,OC类型:任何继承于NSObject对象都属于OC的类型。它对基本数据类型不管用。
** @property属性 **
OC中@property属性关键字的使用(assign/weak/strong/copy) ,property的属性默认是:readwrite、strong、 atomic(没有这个关键字)
-
assign
用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型
eg:int、bool ,struct,enum等 -
copy
一般用在NSString*类型、block类型上
- strong
OC对象类型(NSArray、NSDate、NSNumber、模型类)
一个对象只要有强指针引用着,就不会被销毁
- weak
一般应用: IBOutlet UI控件、delegate代理(防止循环引用)
weak型的指针变量指向的对象不存在时,会自动置为nil。
注: 如果还能打印出指针的地址,说明指针并不会马上销毁,那么什么时候回销毁恩?
在每一个运行循环(runloop)的开始,系统会自动创建一个自动释放池autoreleasepoo;在每一个运行循环的结尾,系统会自动销毁这个自动释放池。
当一个自动释放池自身被销毁(dealloc)时,它会给池中每一个对象发送一个release消息,所以进行下一轮消息循环(runLoop)之后才会发现这个指针被销毁了。
注意:使用arc机制后代码中不能再出现这些:
- 代码中不能使用retain,release,autorelease
- 不重载dealloc(可以重写覆盖,不能调用)
- 不能使用NSAutoReleasePool、而需要@autoreleasepool块
IOS 内存泄漏及预防
在使用ARC的时候,可以在一定程度上简化我们的编程操作,但是在使用的过程中也会出现内存泄露的问题
- delegate循环引用造成的内存泄露
把delegate引用声明成了strong
@property (nonatomic, strong) id delegate;
解决办法:改成弱引用
@property (nonatomic, weak) id delegate; 或者 @property (nonatomic, assign) id delegate;
- block的循环引用
Block 循环引用也很常见,假设 self 代表当前 ViewController,那么一般写法是这样
__weak typeof(self)weakSelf = self;
self.completionHandler = ^{
NSLog(@"%@", weakSelf);
};
- NSTimer
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
如果在一个 ViewController 里创建了一个定时器,并且 repeats:值为 YES,一定记得在 pop/dismiss 当前 ViewController 将 timer 设为 invalidate,否则 会造成循环引用,这里要特别需要 注意 的一点是:我们不要在 ViewController 的 dealloc 方法里调用 [timer invalidate]; 因为从来不会调到这里,我们应该在 viewWillDisappear里边调用,像这样:
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]) {
[timer invalidate]; //解除对self的引用
}
}
- CoreFundation/CoreGraphics 相关函数
ARC 针对的 Objective-C 对象来说的,而 CoreFundation/CoreGraphics 这种非Objective-C对象,我们一定要记得自己手动释放,否则会造成内存泄漏
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
只要记住一点:CG/CF 开头的函数,有创建就要有释放,这样可以有效降低内存泄漏的风险。
- 基本数据类型申请的内存空间需要手动free
uint8_t *buffer=malloc(1024)
free(buffer)
如何检测内存泄漏
开发应用程序,内存泄漏不可避免,那么如何检测内存泄漏呢,那就要说道 Xcode工具套件中的 Instruments。针对 内存泄漏,主要是两个 Allocations 和 Leaks
这里有篇教程写的比较详细:http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
我建议大家直接看英文原版吧,也不难。这里主要说明几点:
教程中用的 Instruments 界面跟目前最新的略微有些差别,比如 里边 提到的处于左边的 Mark Heap 按钮,目前改为了 Mark Generation ,在这里:
对于 循环引用 如何进行检测
简单点可以通过在对应的类中复写 dealloc 方法,像这样:
- (void)dealloc{
NSLog(@"=== %@ dealloced! ===", NSStringFromClass([self class]));
}
如果调用这里,说明不存在循环引用。
同样,我们也可以通过 Instruments 来检测,利用 Allocations ,如图:
这里以 ViewController 为例,在搜索框中输入想要查看的类名,然后连续进行 pop/push 操作,看 #Total 那一列的个数有没有连续增加,如果连续增加,那么说明出现循环引用了,注意:此方法只适用于真机。
网友评论