内存管理
iOS程序的内存布局
-
代码段:编译之后的代码
-
数据段
- 字符串常量:比如
NSString *str = @"123" - 已初始化数据:已初始化的全局变量、静态变量等
- 未初始化数据:未初始化的全局变量、静态变量等
- 字符串常量:比如
-
栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
-
堆:通过 alloc、malloc、calloc 等动态分配的空间,分配的内存空间地址越来越大
int a = 10;
int b;
int main(int argc, char * argv[]) {
@autoreleasepool {
static int c = 20;
static int d;
int e;
int f = 20;
NSString *str = @"123";
NSObject *obj = [[NSObject alloc] init];
NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n",
&a, &b, &c, &d, &e, &f, str, obj);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
/*
字符串常量
str=0x10dfa0068
已初始化的全局变量、静态变量
&a =0x10dfa0db8
&c =0x10dfa0dbc
未初始化的全局变量、静态变量
&d =0x10dfa0e80
&b =0x10dfa0e84
堆
obj=0x608000012210
栈
&f =0x7ffee1c60fe0
&e =0x7ffee1c60fe4
*/
Tagged Pointer
-
从 64bit 开始,iOS引入了 Tagged Pointer 技术,用于优化 NSNumber、NSDate、NSString 等小对象的存储。
-
在没有使用 Tagged Pointer 之前, NSNumber 等对象需要动态分配内存、维护引用计数等,NSNumber 指针存储的是堆中 NSNumber 对象的地址值。
-
使用 Tagged Pointer 之后,NSNumber 指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中。
-
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。
-
objc_msgSend 能识别 Tagged Pointer,比如 NSNumber 的 intValue 方法,直接从指针提取数据,节省了以前的调用开销。
-
如何判断一个指针是否为 Tagged Pointer?
- iOS平台,最高有效位是1(第64bit)
- Mac平台,最低有效位是1
Objc 源码判断一个指针是否是 Tagged Pointer:
#if TARGET_OS_OSX && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
思考以下2段代码能发生什么事?有什么区别?
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
第一段代码会崩溃,报 EXC_BAD_ACCESS 坏内存访问,因为属性在修改的时候,内部是要先调用 release,然后在调用 copy 或者 retain,如果多个线程同时去修改,就可能会同时执行 release 方法,如果这时候该属性已经被释放掉了,再调用 release 就会导致坏内存访问。
第二段代码不会崩溃的原因是它是 Tagged Pointer,给它赋值时相当于直接修改它的指针的值。
OC对象的内存管理
OC内存管理遵循”谁创建,谁释放,谁引用,谁管理“的机制。
在ObjC中内存的管理是依赖对象引用计数器来进行的:在ObjC中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”。
-
当程序调用方法名以
alloc、new、copy、mutableCopy开头的方法来创建对象时,该对象的引用计数加1。 -
当调用这个对象的
retain方法之后它的引用计数加1, -
当调用这个对象的
release方法之后它的引用计数减1, -
如果一个对象的引用计数为0,则系统会自动调用这个对象的
dealloc方法来销毁这个对象。
在MRC下,注意最后一定要调用父类的 dealloc 方法,两个目的:
- 一是:父类可能有其他引用对象需要释放;
- 二是:当前对象真正的释放操作是在 super 的 dealloc 中完成的
我们可以通过 dealloc 方法来查看是否一个对象已经被回收,如果没有被回收则有可能造成内存泄露。
在MRC下,我们需要在适当的位置插入 release 和 autorelease,保证对象在使用完后会被释放。
如果一个对象被释放之后,那么最后引用它的变量我们需要手动设置为 nil,否则可能造成野指针错误,而且需要注意在 ObjC 中给空对象发送消息是不会引起错误的。
MRC下的内存管理
@property的参数分为三类,也就是说参数最多可以有三个,中间用逗号分隔,每类参数可以从上表三类参数中任选一个。
如果不进行设置或者只设置其中一类参数,程序会使用三类中的各个默认参数。
MRC下默认参数:(atomic, readwrite, assign)
原子性
-
atomic 用于保证属性 setter、getter 的原子性操作,相当于在 getter 和 setter 内部加了线程同步的锁
-
可以参考源码 objc4 的
objc-accessors.mm -
它并不能保证使用属性的过程是线程安全的,因为我们可以绕过 setter 方法来进行赋值
atomic 在iOS中基本不会使用,因为太耗性能,一般用于MacOS。
内存管理属性
假设我们定义一个属性a,这里列出三种方式的生成代码:
-
assign
- 直接赋值,引用计数不改变,适用于简单数据类型,例如:NSIngeter、CGFloat、int、char等。
- (void)setA:(int)a { _a = a; } -
retain
- 指针的拷贝,使用的是原来的内存空间。
- 对象的引用计数加1。
- 此属性只能用于 Objective-C 对象类型,而不能用于 Core Foundation 对象。(原因很明显,retain 会增加对象的引用计数,而基本数据类型或者 Core Foundation 对象都没有引用计数)。
- (void)setA:(Car *)a { if(_a != a){ [_a release]; _a = [a retain]; } } -
copy
- 对象的拷贝,新申请一块内存空间,并把原始内容复制到那片空间。
- 新对象的引用计数为1。
- 此属性只对那些实行了 NSCopying 协议的对象类型有效。
- 通常用于字符串对象、Block、NSArray、NSDictionary
- (void)setA:(NSString *)a { if(_a != a){ [_a release]; _a = [a copy]; } }
深浅拷贝
不管是集合类对象,还是非集合类对象,接收到 copy 和 mutableCopy 消息时,都遵循以下准则:
- copy 返回 immutable 对象;所以,如果对 copy 返回值使用 mutable 对象接收就会 crash;
- mutableCopy 返回 mutable 对象;
系统非集合类对象指的是 NSString,NSNumber ... 之类的对象
不管是集合对象,还是非集合对象。
- immutable 对象
- copy,是指针复制,
- mutableCopy,是内容复制
- mutable 对象
- copy 和 mutableCopy 都是内容复制
但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制,被称为单层深复制。
因此,网上有人对浅复制、深复制、单层深复制做了概念区分:
- 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
- 单层深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
- 完全深复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
如何实现集合的完全深复制呢?
集合的深复制有两种方法。
-
第一可以用
initWithArray:copyItems:将第二个参数设置为YES即可:NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。
-
第二个方法是将集合进行归档(archive),然后解档(unarchive),如:
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
ARC下内存管理
ARC下默认的属性参数
-
基本数据类型为:
(atomic, readwrite, assign) -
对象类型为:
(atomic, readwrite, strong)
所有权修饰符
在ARC中,id类型和对象类型必须附加所有权修饰符,所有权修饰符有四种:
-
__strong- id类型和对象类型默认的所有权修饰符
__strong修饰的指针指向的对象,会在赋值时调用被指向对象的 retain 方法,导致其引用计数加1__strong修饰的指针变量指向对象时,表示对其强引用(owner),只要还有某个__strong修饰的指针变量指向该对象,该对象就无法释放;当指针指向新值,或者指针不再存在时,相关联的对象就会释放(也就是说现在已经没有指针对其进行强引用了,所以才能释放)- 使用
__strong修饰符可能会导致循环引用
-
__weak- 提供弱引用(reference),而不是 owner,即引用计数不会加1。
- 当
__weak修饰的指针变量指向的对象被释放后,这个指针变量会被置为nil,不会产生野指针 - 使用
__weak修饰符来解决循环引用
-
__unsafe_unretained- 提供弱引用(reference),而不是 owner,即引用计数不会加1。
__unsafe_unretained修饰指针变量指向的对象被释放后,指针不会置空,而是变成一个野指针,若该指针指向的内存后来被重新写入了数据,那么此时如果访问这个对象的话,程序就会 Crash,抛出 BAD_ACCESS 的异常。
-
__autoreleasing- 在ARC环境下,将对象赋值给附有
__autoreleasing修饰符的指针变量等价于在MRC环境下调用对象的 autorelease 方法,即将对象注册到 autoreleasepool。 - 但是,显式地附加
__autoreleasing修饰符同显式地使用__strong修饰符一样罕见,因为编译器会帮我们添加,所以这个修饰符很少用到。 - 作为
alloc/new/copy/mutableCopy方法返回值取得的对象是自己生成并持有的,其他情况下便是取得非自己生成的对象并持有,这些非alloc/new/copy/mutableCopy方法返回的对象,编译器会自动将他们注册到 autoreleasepool。
- 在ARC环境下,将对象赋值给附有
内存管理属性
| 属性声明 | 所有权修饰符 |
|---|---|
| assign | __unsafe_unretained |
| copy | __strong |
| retain | __strong |
| strong | __strong |
| unsafe_unretained | __unsafe_unretained |
| weak | __weak |
-
assign
- 只可以用来修饰基本数据类型,该方式会对象直接赋值而不会进行retain操作。
- 如果用assign修饰对象,当对象释放后(因为不存在强引用,离开作用域对象内存可能被回收),指针的地址还是存在的,也就是说指针并没有被置为nil,下次再访问该对象就会造成野指针异常。对象是分配在堆上的,堆上的内存由程序员手动释放。
- assign修饰基本数据类型或OC数据类型,因为基本数据类型是分配在栈上的,由系统分配和释放,所以不会造成野指针。
-
copy
- 是owner,不是reference(引用),对复制出来的对象强引用。
- 新的对象引用计数为1,与原始对象引用计数无关,且原始对象引用计数不会改变。
- 使用copy创建的新对象也是强引用,使用完成后需要负责释放该对象。
- copy特性可以减少对象对上下文的依赖。新对象、原始对象中任一对象的值改变不会影响另一对象的值。
- 要想设置该对象的特性为copy,该对象必须遵守
NSCopying协议,Foundation类默认实现了NSCopying协议,所以只需要为自定义的类实现该协议即可。
-
strong
- 创建一个强引用的指针,引用对象引用计数加1。
-
retain
- 创建一个强引用的指针,引用对象引用计数加1。
- iOS引入ARC后,用strong替代了retain
- 与 strong 的区别主要体现再修饰 block的时候,strong 修饰 block,相当于 copy,此时 block 是放在堆上的,生命周期不回随函数作用域结束而结束;但是 retain 修饰的 block 是存放在栈上,block 会有被提前释放的风险。
-
weak
- 只是reference,不是owner。即引用计数不会加1。
- 当 weak 指向的对象被释放后,这个指针变量会被置为 nil。
引用计数的存储
在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在 SideTable 结构体中
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; // 一个存放着对象引用计数的散列表
weak_table_t weak_table; // 弱引用表,也是散列表
};
我们通过源码看看是如何取出对象的引用计数的:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
// 如果是TaggedPointer类型,直接返回它自己
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { // 如果是优化过的isa指针
// 从isa中取出引用计数
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
// 如果has_sidetable_rc为1,则证明isa已经存不下了,
// 超出的引用计数被存储在了sidetable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
// 拿到sidetable
SideTable& table = SideTables()[this];
// 从sidetable的refcnts中取出对象的引用计数
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
dealloc
当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是
-
dealloc
-
_objc_rootDealloc
-
rootDealloc
inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this); } else { object_dispose((id)this); } } -
object_dispose
id object_dispose(id obj) { if (!obj) return nil; objc_destructInstance(obj); free(obj); return nil; } -
objc_destructInstance、free
void *objc_destructInstance(id obj) { if (obj) { // Read all of the flags at once for performance. bool cxx = obj->hasCxxDtor(); bool assoc = obj->hasAssociatedObjects(); // This order is important. if (cxx) object_cxxDestruct(obj); // 清楚当前的成员变量 if (assoc) _object_remove_assocations(obj); obj->clearDeallocating(); // 将指向当前对象的弱指针置为nil } return obj; } -
clearDeallocating
-
从weak表中获取废弃对象的地址为键值的记录
-
将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
-
将weak表中该记录删除
-
从引用计数表中删除废弃对象的地址为键值的记录
-
weak指针的实现原理
对对象的弱引用会被存储在 SideTable 中的 weak_table 里,如果对象被释放掉,那么在调用对象的 dealloc 方法的时候,就会把 weak_table 里面的内容清空。
自动释放池
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
-
自动释放池的主要底层数据结构是:
__AtAutoreleasePool、AutoreleasePoolPage -
调用了 autorelease 的对象最终都是通过 AutoreleasePoolPage 对象来管理的
-
源码分析
-
clang重写
@autoreleasepool,底层相当于:@autoreleasepool { // atautoreleasepoolobj = objc_autoreleasePoolPush(); MJPerson *person = [[[MJPerson alloc] init] autorelease]; // objc_autoreleasePoolPop(atautoreleasepoolobj); } -
objc4源码:NSObject.mm
-
objc_autoreleasePoolPush
void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. // 每个autorelease pool开始都创建一个新的page dest = autoreleaseNewPage(POOL_BOUNDARY); } else { // page已经存在,快速添加 dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; } id *autoreleaseNewPage(id obj) { AutoreleasePoolPage *page = hotPage(); // 如果已经存在一个页,就当这个页已经存满了,创建一个新的页,添加到child // 然后在新的页里添加obj if (page) return autoreleaseFullPage(obj, page); // 否则,创建一个新页 else return autoreleaseNoPage(obj); } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // 如果存在一个页,且这个页还没满,直接添加obj if (page && !page->full()) { return page->add(obj); } else if (page) { // 否则,就创建一个新的页,添加到child return autoreleaseFullPage(obj, page); } else { // 若不存在当前页,创建一个新页 return autoreleaseNoPage(obj); } } id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. assert(page == hotPage()); assert(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); } id *autoreleaseNoPage(id obj) { // "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet assert(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { // We are pushing a second pool over the empty placeholder pool // or pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", pthread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) { // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); } // We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } // Push the requested object or pool. return page->add(obj); } -
objc_autoreleasePoolPop
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. if (hotPage()) { // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. pop(coldPage()->begin()); } else { // Pool was never used. Clear the placeholder. setHotPage(nil); } return; } page = pageForPointer(token); stop = (id *)token; if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. return badPop(token); } } if (PrintPoolHiwat) printHiwat(); // 核心在这里,释放直到遇到stop page->releaseUntil(stop); // memory: delete empty children if (DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging AutoreleasePoolPage *parent = page->parent; page->kill(); setHotPage(parent); } else if (DebugMissingPools && page->empty() && !page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } } void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage // 循环调用next,释放资源,直到遇到stop while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it // 如果当前页空了还没有遇到stop,那么找到当前页的父页,设置父页为当前页,继续释放 while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj != POOL_BOUNDARY) { objc_release(obj); } } setHotPage(this); }
-
-
AutoreleasePoolPage
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
- 每个 AutoreleasePoolPage 对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过双向链表的形式连接在一起
-
调用 push 方法会将一个 POOL_BOUNDARY 入栈,并且返回其存放的内存地址
-
之后每当一个对象调用 autorelease 方法,就会将该对象的地址入栈
-
调用 pop 方法时传入一个 POOL_BOUNDARY 的内存地址,会从最后一个入栈的对象开始发送 release 消息,直到遇到这个 POOL_BOUNDARY
-
id *next指向了下一个能存放 autorelease 对象地址的区域
运行下面代码:
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
@autoreleasepool {
for (int i = 0; i < 6; i++) {
MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
}
@autoreleasepool {
MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
}
}
return 0;
}
结果输出:
objc[6215]: ##############
objc[6215]: AUTORELEASE POOLS for thread 0x1000d1dc0
objc[6215]: 12 releases pending.
objc[6215]: [0x10280c000] ................ PAGE (hot) (cold)
objc[6215]: [0x10280c038] ################ POOL 0x10280c038
objc[6215]: [0x10280c040] 0x1007ab9f0 MJPerson
objc[6215]: [0x10280c048] 0x1007ac7e0 MJPerson
objc[6215]: [0x10280c050] ################ POOL 0x10280c050
objc[6215]: [0x10280c058] 0x1007abbc0 MJPerson
objc[6215]: [0x10280c060] 0x1007ab730 MJPerson
objc[6215]: [0x10280c068] 0x1007aaec0 MJPerson
objc[6215]: [0x10280c070] 0x1007a53d0 MJPerson
objc[6215]: [0x10280c078] 0x1007a4530 MJPerson
objc[6215]: [0x10280c080] 0x1007a4410 MJPerson
objc[6215]: [0x10280c088] ################ POOL 0x10280c088
objc[6215]: [0x10280c090] 0x1007a46a0 MJPerson
objc[6215]: ##############
Runloop与Autorelease
iOS在主线程的 Runloop 中注册了2个 Observer
- 第1个Observer监听了 kCFRunLoopEntry 事件,会调用 objc_autoreleasePoolPush()
- 第2个Observer
- 监听了 kCFRunLoopBeforeWaiting 事件,会调用 objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
- 监听了 kCFRunLoopBeforeExit 事件,会调用 objc_autoreleasePoolPop()
自动内存释放简单总结
- autorelease 方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中;
- 自动释放池实质是当自动释放池销毁后调用对象的 release 方法,不一定就能销毁对象(例如如果一个对象的引用计数器 >1 则此时就无法销毁);
- 由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池;
- ObjC类库中的静态方法一般都不需要手动释放,内部已经调用了 autorelease 方法;
面试题
-
使用CADisplayLink、NSTimer有什么注意点?
-
介绍下内存的几大区域
-
讲一下你对 iOS 内存管理的理解
-
ARC 都帮我们做了什么?
-
ARC是LLVM编译器和Runtime系统相互协作的一个结果。
-
LLVM帮我们自动添加了retain、release和autoRelease。
-
Runtime帮我们在运行时做一些支持的操作,比如清空weak指针。
-
-
weak指针的实现原理
-
autorelease 对象在什么时机会被调用 release
- 如果 autorelease 对象被一个
@autoreleasepool包含,就会在大括号结束的地方调用 release。 - 它可能是在当前对象所属的某次 runloop 循环,在 runloop 休眠之前调用了 release。
- 如果 autorelease 对象被一个
-
方法里有局部对象, 出了方法后会立即释放吗
- 如果局部对象后面跟的是 autorelease,不会立即释放,而是等到当前 runloop 休眠之前释放
- 如果局部对象后面跟的是 release,会立即释放