面试题答案
一键面试Objective-C内存管理原理
- 引用计数的实现细节:
- 在Objective - C中,每个对象都有一个与之关联的引用计数(Reference Count,RC)。当一个对象被创建时,它的引用计数通常初始化为1。例如,使用
[[NSObject alloc] init]
创建对象,引用计数为1。 - 当有新的变量指向该对象时,引用计数会增加。比如通过
retain
方法(ARC下编译器会自动处理类似操作),在ARC之前,代码NSObject *obj1 = [[NSObject alloc] init]; NSObject *obj2 = [obj1 retain];
,此时对象的引用计数增加为2。 - 当一个指向对象的变量不再使用该对象(比如变量超出作用域或被赋值为
nil
),对象的引用计数会减少。在ARC之前,使用release
方法减少引用计数,如[obj2 release];
,引用计数减为1 。当对象的引用计数降为0时,系统会自动调用对象的dealloc
方法,释放对象所占用的内存。
- 在Objective - C中,每个对象都有一个与之关联的引用计数(Reference Count,RC)。当一个对象被创建时,它的引用计数通常初始化为1。例如,使用
- 对象的内存布局:
- Objective - C对象在内存中由一个isa指针和成员变量组成。isa指针指向对象的类,通过这个指针,对象可以找到它的类定义,从而获取方法列表等元数据。例如,对于一个自定义类
MyClass
的对象,内存中首先是isa指针,然后是MyClass
中定义的成员变量,按照声明顺序排列。成员变量可以是基本数据类型,也可以是指向其他对象的指针。
- Objective - C对象在内存中由一个isa指针和成员变量组成。isa指针指向对象的类,通过这个指针,对象可以找到它的类定义,从而获取方法列表等元数据。例如,对于一个自定义类
内存泄漏定位与解决
- 定位内存泄漏:
- 使用 Instruments工具:Instruments是Xcode自带的性能分析工具,其中的Leaks工具可以检测内存泄漏。运行应用程序时,Leaks工具会监控内存分配和释放情况,当发现有对象的内存已经分配但无法被释放(即引用计数不会降为0)时,会标记为内存泄漏,并提供泄漏对象的相关信息,如类名、内存地址等,帮助开发者定位泄漏发生的代码位置。
- 代码审查:仔细检查代码中对象的创建、引用和释放逻辑。特别是在ARC之前的代码中,查找是否有
alloc
、retain
后没有对应的release
或autorelease
。例如,在一个循环中反复创建对象但没有释放,就容易导致内存泄漏。在ARC环境下,也要注意循环引用的情况,比如两个对象相互强引用,导致双方引用计数都不会降为0。
- 解决内存泄漏:
- 打破循环引用:如果是因为循环引用导致内存泄漏,在ARC环境下,可以通过将其中一个强引用改为弱引用(
weak
)或无主引用(unowned
)来打破循环。例如,在一个视图控制器和其内部的一个自定义视图类中,如果视图控制器持有自定义视图的强引用,而自定义视图又持有视图控制器的强引用,可以将自定义视图对视图控制器的引用改为weak
引用。这样,当视图控制器被释放时,自定义视图对它的引用不会阻止其释放,从而避免内存泄漏。 - 正确管理对象生命周期:确保对象在不再需要时能够正确释放。在ARC之前,手动调用
release
方法要确保在合适的时机。在ARC环境下,虽然编译器自动管理内存,但也要注意对象的作用域。例如,不要在不必要的情况下延长对象的生命周期,比如在一个方法内部创建了一个临时对象,但在方法返回后仍然持有该对象的引用,导致对象无法被释放。
- 打破循环引用:如果是因为循环引用导致内存泄漏,在ARC环境下,可以通过将其中一个强引用改为弱引用(
优化内存使用的策略及理由
- 延迟加载:
- 策略:对于一些不马上需要使用的对象,采用延迟加载的方式。例如,在一个视图控制器中,如果有一个复杂的图表视图,用户可能在某个特定操作后才会看到,那么可以在视图控制器的
viewDidLoad
方法中不创建该图表视图对象,而是在用户触发相关操作时再创建。 - 理由:这样可以在应用程序启动或运行初期减少内存占用,提高应用程序的响应速度。因为在应用启动时,只加载必要的对象,而将一些非必要对象的创建推迟到真正需要时,避免了一开始就占用大量内存。
- 策略:对于一些不马上需要使用的对象,采用延迟加载的方式。例如,在一个视图控制器中,如果有一个复杂的图表视图,用户可能在某个特定操作后才会看到,那么可以在视图控制器的
- 对象复用:
- 策略:对于一些频繁创建和销毁的对象,可以进行复用。比如在UITableView中,单元格(UITableViewCell)就是典型的复用例子。通过
dequeueReusableCellWithIdentifier:
方法,从复用队列中获取可复用的单元格,如果没有则创建新的单元格。 - 理由:减少了对象创建和销毁的开销,提高了内存使用效率。创建和销毁对象不仅涉及内存的分配和释放,还可能涉及初始化和清理等操作,复用对象可以避免这些不必要的开销,从而优化内存使用和提高性能。
- 策略:对于一些频繁创建和销毁的对象,可以进行复用。比如在UITableView中,单元格(UITableViewCell)就是典型的复用例子。通过