OC的對象都是指針形式表示,對于對像內(nèi)存管理從四個方面來說(ARC和MRC,區(qū)別大部分是在于編譯器幫我們添加retain和release,可以歸到一起總結(jié))
- 使用了TaggedPointer技術(shù)的對象指針,由于把數(shù)據(jù)存在了指針里,在函數(shù)棧調(diào)用結(jié)束時,就會自動釋放,不涉及retain,release操作,即使ARC環(huán)境下,系統(tǒng)也不用加release
- 普通對象,使用了strong,copy策略聲明的屬性,編譯器會在函數(shù)棧結(jié)束或者類調(diào)用dealloc的時候,加上release,釋放對象指針,及內(nèi)存。
- 類方法(array,[NSString stringWithFormat:]等)創(chuàng)建的對象,框架內(nèi)部會把他加入到自動釋放池,這種方法創(chuàng)建的對象和MRC下autorelease對像都是加到自動釋放池,而自動釋放池設(shè)計到內(nèi)存分頁管理,見下文。
- weak類型指針對象,被指向的對象,有一張哈希表,里面存著所有執(zhí)向他的指針,在類dealloc時,會去釋放這些指針。
iOS程序內(nèi)存分為6個區(qū):
- 保留區(qū)
- 代碼區(qū)(__TEXT):保存編譯之后的代碼
- 數(shù)據(jù)段(__DATA):字符串常量:NSString *str = @"String";已初始化的全局變量,靜態(tài)變量等;未初始化的全局變量,靜態(tài)變量。
- 堆區(qū)(heap):函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址越來越小。
- 棧區(qū)(stack):通過alloc,malloc,calloc等動態(tài)分配的空間,分配的內(nèi)存空間地址越來越大
- 內(nèi)核區(qū)
數(shù)據(jù)對象調(diào)用copy和mutableCopy所發(fā)生的拷貝情況表格

總結(jié):不可變對象調(diào)用copy是淺拷貝,調(diào)用mutableCopy是深拷貝;可變對象調(diào)用copy和mutableCopy都是深拷貝。
Tagged Pointer 技術(shù)
從64bit開始,iOS引入Tagged Pointer技術(shù)用于優(yōu)化NSNumber,NSdate,NSString等小對象的存儲。
在沒有使用Tagged Point對象之前, NSNumber等對象需要動態(tài)分配內(nèi)存,維護引用計數(shù)等,NSNumber指針存儲的是堆中NSNumber對象的地址。
使用Tagged Pointer技術(shù)后,就變成了Tag + Data,也就是將數(shù)據(jù)直接存在了指針中, 但是如果指針存不下實再按原方法存到堆中。
當使用Tagged pointer計數(shù)后的對象時 調(diào)用
objc_msgSend()時,會直接去指針取之而不是去找什么isa,找對象.
把使用了Tagged pointer技術(shù)的對象賦值給一個對象,相當于把地址賦值給新對象。
判斷是否是tagged pointer指針
iOS 平臺:address & (1<<63):指針的最高有效位是否是1
Mac OSX 平臺:address & 1:指針最低的有效位是否是1
MRC相關(guān):
內(nèi)存管理
- 在iOS中,使用引用計數(shù)來管理OC對象的內(nèi)存
- 一個新建的OC對象引用計數(shù)默認是1,當引用計數(shù)為0,OC對象就會自動銷毀釋放其占用的內(nèi)存。
- 調(diào)用retain會讓OC對象的引用計數(shù)+1,調(diào)用release會讓OC對象的引用計數(shù)-1
- 可以通過以下私有函數(shù)來查看自動釋放池的情況
-objc_autoreleasePoolPrint(void);
平常聲明屬性時我們寫的assig,retain,copy的含義相當于在MRC下是,如果我們調(diào)用set方法的屬性沒有對應(yīng)的assig,retain,copy,就會報方法找不到。
//assign
-(void)setProperty:(id)Property
{
_property = property;
}
//retain
-(void)setProperty:(id)Property
{
if(_property!=property)
[_property release];
_property = [property retain];
}
//copy
-(void)setProperty:(id)Property
{
if(_property!=property)
[_property release];
_property =[property copy];
}
調(diào)用類方法創(chuàng)建對象:[NSString stringWithFormat:@"123"];相當于在MRC下如下代碼,不用我們?nèi)ソo對象寫release。
[[NSString stringWithFormat:@"123"]autorelease];
自定義類對象實現(xiàn)copy
需要實現(xiàn)copyWithZone:(NSZone*)zone方法,因為copy也是調(diào)用這個方法實現(xiàn),然后在copyWithZone里把想復(fù)制值的對象屬性賦值一遍。
- (id)copyWithZone:(NSZone*)zone
{
SomeClass* instance = [[SomeClass allocWithZone:zone]init];
instance.property1 = self.property1;
instance.property2 = self.property2;
instance.property3 = self.property3;
return instance;
}
引用計數(shù)的存儲
在64位優(yōu)化過后,isa指針是isa_t共用體類型,里面的extra_rc這個19位就是存儲引用計數(shù),當這19位裝不下時,isa_t 共用體里的has_sidetable_rc就變成1,引用計數(shù)就存到一個SideTable的類的屬性中refcnts
union isa_t
{
class cls;
struct {
uintptr_t nonpointer :1;//0代表普通指針,存Class,Meta-Class對象的內(nèi)存地址
uintptr_t has_assoc. :1;//代表優(yōu)化過,使用位域存儲更多的信息
uintptr_t has_cxx_dtor :1;//是否有C++的析構(gòu)函數(shù),如果沒有釋放更快
uintptr_t shiftcls :33;//存著Class,Meta-Class對象的內(nèi)存地址信息
uintptr_t magic. :6;//調(diào)試時分辯對象是否未完成初始化
uintptr_t weakly_referenced :1;//是否有被弱引用執(zhí)行過,沒有則釋放更快
uintptr_t deallocating :1; //對象是否正在釋放
uintptr_t has_sidetable_rc :1;//extra_rc存不下的時候變?yōu)?,引用計數(shù)存到一個SideTable類的屬性中
uintptr_t extra_rc :19; //存引用計數(shù)
}
}
//存引用計數(shù)的結(jié)構(gòu)體
struct SideTable
{
spinlock_t slock;
RefcountMap refcnts;//存著引用計數(shù),是個散列表
weak_table_t weak_table;
}
weak指針
weak 和unsafe_unretained區(qū)別
weak在所指向的對象銷毀時,系統(tǒng)會將指針置nil,而unsafe_unretained不會自動清空的。
實現(xiàn)原理:
當一個對象要釋放時,會自動調(diào)用dealloc,這個對象有張哈希表SideTable,這里存著指向這個對象的弱引都清掉,調(diào)用軌跡。
- dealloc
- _objc_rootDealloc
- rootDealloc
- 如果是tagged指針,直接return
- 如果這個對象的isa指針是個普通指針,并且沒有弱引用,沒有關(guān)聯(lián)對象,沒有C++析構(gòu)函數(shù)沒有弱引用列表就直接釋放,
- 不然就調(diào)用object_dispose
- object_dispose
- 調(diào)用objc_destructInstance
- free(objc)
- objc_destructInstance
- 查看C++析構(gòu)器返回是否是1,調(diào)用object_cxxDestruct清除成員變量
- 查看是否有關(guān)聯(lián)對象,調(diào)用_object_remove_assocations(obj);清楚關(guān)聯(lián)對象
- 調(diào)用clearDeallocating();//將指向當前對象的弱指針置為nil;
- free
autorelease
工程build Seting -> 搜索Automatic reference Counting設(shè)置NO 啟用MRC模式。
在main.m的文件中加幾句代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc]init]autorelease];
NSLog(@"autorelease will end");
}
NSLog(@"autorelease end");
return 0;
}
用clang編譯器重寫下main文件成main.cpp,看下原理??梢栽谥貙懞蟮膍ain.cpp看到如下源碼
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
除去Person對象聲明,可以看到這么一段東西
{
__AtAutoreleasePool __autoreleasepool;
}
這個就是我們寫的
@autoreleasepool {}
相當于聲明了個__AtAutoreleasePool對象,找到這個類的代碼
struct __AtAutoreleasePool {
__AtAutoreleasePool() { atautoreleasepoolobj=objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
在創(chuàng)建__AtAutoreleasePool對象的時候會調(diào)用構(gòu)造函數(shù),{...}作用域結(jié)束后,回收??臻g__AtAutoreleasePool對象會調(diào)用析構(gòu)所以我們寫的@autoreslease{}就相當于
{
void *atautireleasepooloobj = objc_autoreleasePoolPush();
//中間調(diào)用autorelease的對象會被加入autorelease釋放池
[someNSObject autorelease];
objc_autoreleasePoolPop(atautireleasepooloobj);//釋放所有對象
}
關(guān)鍵的就是上面這三句代碼
- objc_autoreleasePoolPush();
- [someNSObject autorelease];
- objc_autoreleasePoolPop(atautireleasepooloobj)
1. objc_autoreleasePoolPush();
轉(zhuǎn)到objc源碼里,在源碼里正好能找到同名的函數(shù),這個函數(shù)objc_autoreleasePoolPush()調(diào)用就相當于調(diào)用AutoreleasePoolPage的push方法,這個方法在源碼里如下:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//先忽略inline關(guān)鍵字,這是內(nèi)聯(lián)函數(shù),表示下面的函數(shù)直接轉(zhuǎn)成代碼,在匯編里而不是跳到一個函數(shù)地址
static inline void *push() {
id *dest;
//判斷是否有AutoreleasePoolPage對象,如果沒有創(chuàng)建個新的,有的話,把POOL_BOUNDARY壓進棧
if (DebugPoolAllocation) {
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
上面的代碼可以看出來會先去判斷是否有autoreleaseNewPage,DebugPoolAllocation是個全局的,如果我們@autorelease循環(huán)嵌套的話并且當前的AutoreleasePage沒滿的話,第二個@autorelease只會把一個邊界標記壓入AutoreleasePoolPage棧里,如果當前的page滿了才會創(chuàng)建下一頁(注意:不是每個@autorelease都創(chuàng)建AutoreleasePoolPage對象,當前page只有一個,不同@autorelease靠bundary來區(qū)別)。
2. autorelease()
如下所示,
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
一個對象調(diào)用autorelease方法時,存在三種情況
- 當前有AutoreleasePoolPage對象并且page對象沒有放滿,這樣就把對象加入到page里
- page對象存在,但是滿了,autoreleaseFullPage(obj, page);會新建個page,新的page里會指向舊的page,
- 當前沒有page對象,autoreleaseNoPage(obj)里會創(chuàng)建page,并把對象加入到page里。
objc_autoreleasePoolPop(atautireleasepooloobj
接收objc_autoreleasePoolPush();返回的對象。從最后一頁page開始,一個個把page里的對象release,直到遇到一個POOL_BOUNDARY,代表這個autorelease對象釋放完畢。
多個AutoreleasePoolPage采用雙向鏈表的形式存儲。

autoreleasePool和runloop的關(guān)系,
iOS在主線程的Runloop注冊了2個Observer,
- 第一個Observer
監(jiān)聽了kCFRunloopEntry:
- 調(diào)用objc_autoreleasePoolPush
- 第二個Observer
監(jiān)聽了kCFRunloopBeforeWaiting事件:
- 調(diào)用objc_autoreleasePoolpop ,objc_autoreleasePoolPush
監(jiān)聽了kCFRunloopBeforeExit事件 - 調(diào)用objc_autoreleasePoolPop()
可以看到是個結(jié)構(gòu)體,里面有兩個
autorelesase對象什么時候釋放,是在所屬Runloop循環(huán)中休眠之前調(diào)用pop釋放
所以 :
我們的對象如果調(diào)用了autorelease 加入了自動釋放池,則是在Runloop休眠或者退出時,被釋放,而不是在所處作用域(函數(shù)或者{})結(jié)束時立即被釋放。
我們的普通局部對象,則是因為編譯器編譯時會自動加上release,所以出了作用域,要是沒有別的地方強引用著,則釋放掉。
可以通過_objc_autoreleasePoolPrint(void)查看自動釋放池情況
必須得先聲明,extern void _objc_autoreleasePoolPrint(void);但是不用實現(xiàn)。