iOS面试题(3)----内存管理

来源:互联网 发布:dnf最新版数据芯片搬砖 编辑:程序博客网 时间:2024/05/01 19:06

1.堆和栈的区别
管理方式:
对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生memory leak。

申请大小:

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统 预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地 址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

碎片问题:
对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。
对于栈来讲,则不会存在这个 问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

分配方式:
堆都是动态分配的,没有静态分配的堆。
栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的 效率比较高。
堆则是C/C++函数库提供的,它的机制是很复杂的。

2.内存管理原则是什么?
1. 谁创建,谁释放(类似于“谁污染,谁治理”)。如果你通过alloc、new或copy来创建一个对象,那么你必须调用release或autorelease。换句话说,不是你创建的,就不用你去释放。
例如,你在一个函数中alloc生成了一个对象,且这个对象只在这个函数中被使用,那么你必须在这个函数中调用release或autorelease。如果你在一个class的某个方法中alloc一个成员对象,且没有调用autorelease,那么你需要在这个类的dealloc方法中调用release;如果调用了autorelease,那么在dealloc方法中什么都不需要做。
2. 除了alloc、new或copy之外的方法创建的对象都被声明了autorelease。
3. 谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的,你都要调用release。有时候你的代码中明明没有retain,可是系统会在默认实现中加入retain。

3.谈一谈自动释放池

自动释放池(Autorelease pool)是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池来回收统一释放.自动释放池本事销毁的时候,池子里面所有的对象都会做一次release操作.
自动释放池并不包含实际的对象本身,仅仅是对池释放的时候要释放的对象的引用,通过像当前的自动释放池发送一条autorelease消息,就可以将一个对象添加到其中,以便以后释放。
Cocoa应用程序中的每个线程都会维护一个自己的NSAutoreleasePool对象的堆栈。当一个线程终止时,它会自动地释放所有与自身相关的自动释放池。在基于Application Kit的应用程序中,自动释放池会在程序的主线程中被自动创建和销毁,所以,您的代码通常无需处理它们。但是,如果您在Application Kit的主线程之外发起Cocoa调用,则您需要创建自己的自动释放池。如果您正在编写一个Foundation应用程序,或者如果您拆分了一个线程,则是属于这种情况。

4.什么是强指针,什么是弱指针?
强指针在当进行对象地址的赋值操作时会保留新值并释放旧值,当其退出指针作用域时释放。而弱指针则类似于手动内存管理时的指针,它仅仅是一个指针而已,不做额外的保留和释放,在对象被回收时会自动置为nil。在程序中使用__strong关键字指定强指针,使用__weak关键字指定弱指针,默认为强指针。

if (someCondition){    NSMutableArray* stringArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];    id firstObj = [stringArray objectAtIndex:0];    [stringArray removeObjectAtIndex:0];    NSLog(@"%@", firstObj);}

if语句块的第一行代码创建了一个数组,其中包含三个字符串对象,然后将其地址赋值给stringArray。第二行将数组的首元素地址赋值给firstObj,即指向字符串@”A”。第三行将数组首元素移出数组。第四行打印firstObj的描述信息。最后退出if语句块。

在未开启ARC时,由于元素在被移出数组时会被数组释放,所以在调用NSLog函数时@”A”已经被回收,firstObj指向的是一个无效的地址(野指针、僵尸),这会导致程序崩溃。

而当开启了ARC时,上述代码就是正确的。当执行到第二行时,stringArray和firstObj均为强指针(默认为强指针),分别保留数组和@”A”,此时@”A”的所有者为数组和firstObj。到第三行,@”A”的所有者为firstObj,未被回收,所以NSLog语句没有问题。最后退出if语句块时,stringArray和firstObj退出作用域,分别释放并回收数组和@”A”。

当然,你可以在定义stringArray和firstObj时指定__strong关键字:

[plain] view plaincopy__strong NSMutableArray* stringArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];__strong id firstObj = [stringArray objectAtIndex:0];

不过由于默认为强指针,所以__strong关键字是可以忽略的。

由于强指针会一直保留着对象,所以当你确实需要将其释放时,需要手动将强指针赋值为nil,否则对象一直不会被回收,会导致系统内存资源不足。

再来说说弱指针。请看如下代码:

[plain] view plaincopyNSString* strongPtr = [[NSString alloc] initWithCString:"A" encoding:NSUTF8StringEncoding];__weak NSString* weakPtr = strongPtr;NSLog(@"%@", weakPtr);strongPtr = nil;NSLog(@"%@", weakPtr);double delayInSeconds = 3.0;dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));dispatch_after(popTime, dispatch_get_main_queue(), ^(void){    NSLog(@"After 3 seconds: %@", weakPtr);});

由于firstObj为弱指针,前两个NSLog输出为A,而最后一个则为(null)。这说明当对象被回收时,所有指向该对象的弱指针均会被置为nil,但这需要时间。

读到这里,你应该知道下面的代码有什么问题了吧:

[plain] view plaincopy__weak NSArray* array = [[NSArray alloc] initWithObjects:@"Puzhi Li", nil];NSLog(@"%@", array);

当启用了ARC之后,(基本上)不会出现内存泄漏、使用野指针、访问僵尸对象的情况 。

5.多次调用对象的autorelease方法会导致什么问题?
多次将地址存到自动释放池中,导致野指针异常

0 0