iOS程序內(nèi)存布局與管理

OC的對象都是指針形式表示,對于對像內(nèi)存管理從四個方面來說(ARC和MRC,區(qū)別大部分是在于編譯器幫我們添加retain和release,可以歸到一起總結(jié))

  1. 使用了TaggedPointer技術(shù)的對象指針,由于把數(shù)據(jù)存在了指針里,在函數(shù)棧調(diào)用結(jié)束時,就會自動釋放,不涉及retain,release操作,即使ARC環(huán)境下,系統(tǒng)也不用加release
  2. 普通對象,使用了strong,copy策略聲明的屬性,編譯器會在函數(shù)棧結(jié)束或者類調(diào)用dealloc的時候,加上release,釋放對象指針,及內(nèi)存。
  3. 類方法(array,[NSString stringWithFormat:]等)創(chuàng)建的對象,框架內(nèi)部會把他加入到自動釋放池,這種方法創(chuàng)建的對象和MRC下autorelease對像都是加到自動釋放池,而自動釋放池設(shè)計到內(nèi)存分頁管理,見下文。
  4. weak類型指針對象,被指向的對象,有一張哈希表,里面存著所有執(zhí)向他的指針,在類dealloc時,會去釋放這些指針。

iOS程序內(nèi)存分為6個區(qū):

  1. 保留區(qū)
  2. 代碼區(qū)(__TEXT):保存編譯之后的代碼
  3. 數(shù)據(jù)段(__DATA):字符串常量:NSString *str = @"String";已初始化的全局變量,靜態(tài)變量等;未初始化的全局變量,靜態(tài)變量。
  4. 堆區(qū)(heap):函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址越來越小。
  5. 棧區(qū)(stack):通過alloc,malloc,calloc等動態(tài)分配的空間,分配的內(nèi)存空間地址越來越大
  6. 內(nèi)核區(qū)

數(shù)據(jù)對象調(diào)用copy和mutableCopy所發(fā)生的拷貝情況表格


圖-01

總結(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)用軌跡。

  1. dealloc
  2. _objc_rootDealloc
  3. rootDealloc
  • 如果是tagged指針,直接return
  • 如果這個對象的isa指針是個普通指針,并且沒有弱引用,沒有關(guān)聯(lián)對象,沒有C++析構(gòu)函數(shù)沒有弱引用列表就直接釋放,
  • 不然就調(diào)用object_dispose
  1. object_dispose
  • 調(diào)用objc_destructInstance
  • free(objc)
  1. 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;
  1. 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)鍵的就是上面這三句代碼

  1. objc_autoreleasePoolPush();
  2. [someNSObject autorelease];
  3. 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方法時,存在三種情況

  1. 當前有AutoreleasePoolPage對象并且page對象沒有放滿,這樣就把對象加入到page里
  2. page對象存在,但是滿了,autoreleaseFullPage(obj, page);會新建個page,新的page里會指向舊的page,
  3. 當前沒有page對象,autoreleaseNoPage(obj)里會創(chuàng)建page,并把對象加入到page里。

objc_autoreleasePoolPop(atautireleasepooloobj

接收objc_autoreleasePoolPush();返回的對象。從最后一頁page開始,一個個把page里的對象release,直到遇到一個POOL_BOUNDARY,代表這個autorelease對象釋放完畢。

多個AutoreleasePoolPage采用雙向鏈表的形式存儲。


AutoreleasePoolPage結(jié)構(gòu)圖

autoreleasePool和runloop的關(guān)系,

iOS在主線程的Runloop注冊了2個Observer,

  1. 第一個Observer
    監(jiān)聽了kCFRunloopEntry:
  • 調(diào)用objc_autoreleasePoolPush
  1. 第二個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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容