OC對(duì)象的內(nèi)存管理
在iOS中卖哎,使用引用計(jì)數(shù)來管理OC對(duì)象的內(nèi)存
一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對(duì)象就會(huì)銷毀三椿,釋放其占用的內(nèi)存空間
調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1
- (void)setDog:(MJDog *)dog
{
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc葫辐、new搜锰、copy、mutableCopy方法返回了一個(gè)對(duì)象耿战,在不需要這個(gè)對(duì)象時(shí)蛋叼,要調(diào)用release或者autorelease來釋放它
想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1剂陡;不想再擁有某個(gè)對(duì)象狈涮,就讓它的引用計(jì)數(shù)-1
可以通過以下私有函數(shù)來查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);
copy和mutableCopy區(qū)別
- copy 產(chǎn)生不可變對(duì)象
- mutableCopy產(chǎn)生可變對(duì)象
注意點(diǎn)
可變數(shù)組copy后會(huì)變成不可變數(shù)組 , 添加元素 會(huì)報(bào)錯(cuò) 變成了不可變對(duì)象
引用計(jì)數(shù)的存儲(chǔ)
在64bit中,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過的isa指針中鸭栖,也可能存儲(chǔ)在SideTable類中
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
refcnts是一個(gè)存放著對(duì)象引用計(jì)數(shù)的散列表
RetainCount
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;//指針
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {//非指針類型 歌馍,是否優(yōu)化過
uintptr_t rc = 1 + bits.extra_rc;//引用計(jì)數(shù)
if (bits.has_sidetable_rc) {// 1不是存儲(chǔ)在isa中,而是存儲(chǔ)在sideTable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
dealloc
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
__weak指針原理
runtime維護(hù)著一張弱引用表,用于存儲(chǔ)指向某個(gè)對(duì)象的所有Weak指針晕鹊,弱引用以散列表的方式存儲(chǔ)到弱引用表里松却,釋放時(shí)調(diào)?用clearDeallocating函數(shù),通過對(duì)象地址值作為key & 掩碼獲取索引 ,取出當(dāng)前對(duì)象的弱引用表 溅话,將里面的弱引用都清除掉晓锻,并將所有Weak指針的值設(shè)為nil
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this]; //取出弱引用表
table.lock();
if (isa.weakly_referenced) {//弱引用 移除
weak_clear_no_lock(&table.weak_table, (id)this);
}
//將引用計(jì)數(shù)表里的 東西也擦除掉
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
weak_entry_remove(weak_table, entry);
}
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
//地址值 & 掩碼 獲取 一個(gè)索引
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
ARC幫我們做了什么
利用LLVM 編譯器自動(dòng)幫我們添加retain,release
利用Runtime 監(jiān)聽對(duì)象銷毀的時(shí)候,找到弱引用并清除
自動(dòng)釋放池
/*
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 構(gòu)造函數(shù)飞几,在創(chuàng)建結(jié)構(gòu)體的時(shí)候調(diào)用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析構(gòu)函數(shù)砚哆,在結(jié)構(gòu)體銷毀的時(shí)候調(diào)用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
atautoreleasepoolobj = objc_autoreleasePoolPush();
MJPerson *person = [[[MJPerson alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
*/
- 自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage
- 調(diào)用了autorelease的對(duì)象最終都是通過AutoreleasePoolPage對(duì)象來管理的
AutoreleasePoolPage 結(jié)構(gòu)
- 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存屑墨,除了用來存放它內(nèi)部的成員變量躁锁,剩下的空間用來存放autorelease對(duì)象的地址
- 所有的AutoreleasePoolPage對(duì)象通過雙向鏈表的形式連接在一起
- 調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址
- 調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址绪钥,會(huì)從最后一個(gè)入棧的對(duì)象開始發(fā)送release消息灿里,直到遇到這個(gè)POOL_BOUNDARY
autorelease什么時(shí)候釋放
iOS在主線程的Runloop中注冊(cè)了2個(gè)Observer
- 第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
- 第2個(gè)Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件程腹,會(huì)調(diào)用objc_autoreleasePoolPop()匣吊、objc_autoreleasePoolPush()
監(jiān)聽了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 這個(gè)Person什么時(shí)候調(diào)用release,是由RunLoop來控制的
// 它可能是在某次RunLoop循環(huán)中色鸳,RunLoop休眠之前調(diào)用了release
// MJPerson *person = [[[MJPerson alloc] init] autorelease];
MJPerson *person = [[MJPerson alloc] init];
NSLog(@"%s", __func__);
}
/*
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 2
kCFRunLoopBeforeSources = (1UL << 2), 4
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
/*
kCFRunLoopEntry push
<CFRunLoopObserver 0x60000013f220 [0x1031c8c80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
kCFRunLoopBeforeWaiting | kCFRunLoopExit
kCFRunLoopBeforeWaiting pop社痛、push
kCFRunLoopExit pop
<CFRunLoopObserver 0x60000013f0e0 [0x1031c8c80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x103376df2), context = <CFArray 0x60000025aa00 [0x1031c8c80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fd0bf802048>\n)}}
*/
@end
方法里有局部變量 ,出了 方法會(huì)立即釋放嗎
分情況討論,如果生成的是autorelease 代碼 不會(huì)立馬釋放命雀,會(huì)等到runloop休眠的時(shí)候會(huì)釋放
如果是生成release 會(huì)立馬釋放
使用CADisplayLink蒜哀、NSTimer有什么注意點(diǎn)?
CADisplayLink吏砂、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用撵儿,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用
解決方案
- 使用block
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- 使用中間對(duì)象
@interface MJProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation MJProxy
+ (instancetype)proxyWithTarget:(id)target
{
MJProxy *proxy = [[MJProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation MJProxy
NSProxy 專門用來消息轉(zhuǎn)發(fā)狐血,少了去父類里找方法的過程,比NSObject效率要高
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy對(duì)象不需要調(diào)用init淀歇,因?yàn)樗緛砭蜎]有init方法
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
Tagged Pointer
從64bit開始,iOS引入了Tagged Pointer技術(shù)匈织,用于優(yōu)化NSNumber浪默、NSDate、NSString等小對(duì)象的存儲(chǔ)
在沒有使用Tagged Pointer之前缀匕, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存纳决、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值
使用Tagged Pointer之后乡小,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data阔加,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中
當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)
objc_msgSend能識(shí)別Tagged Pointer劲件,比如NSNumber的intValue方法掸哑,直接從指針提取數(shù)據(jù)约急,節(jié)省了以前的調(diào)用開銷
如何判斷一個(gè)指針是否為Tagged Pointer零远?
iOS平臺(tái),最高有效位是1(第64bit)
Mac平臺(tái)厌蔽,最低有效位是1
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"];
// 解鎖
});
}
//沒有采用 Tagged Point 技術(shù)牵辣,多線程重復(fù)釋放崩潰
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"];
});
}
//采用 Tagged Point 技術(shù),不會(huì)訪問setter方法
/**
- (void)setName:(NSString *)name
{
if (_name != name) {
[_name release]; //多線程重復(fù)釋放
_name = [name retain];
}
}
*/