iOS怎么捏泥人-alloc初探

iOS開發剛開始做的是什么?從初步、比較普遍的結果來看,可能是做一個穩定的好用的App給用戶,這App可以是一個小世界,通過用戶點擊行為等產生一系列的響應,進而成為工具、游戲等。
寫到這里,我們以捏泥巴類比,今天我也想探索下蘋果生態系統下的App是怎么來的,蘋果是依據什么原則去捏對象,進而形成App的呢?

一、 先來一個常見的現象

我們來看下一段代碼:

//YPerson的.h聲明文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN

@interface YPerson : NSObject

@property (nonatomic, assign) int age;

@property (nonatomic, assign) double height;

@property (nonatomic, copy) NSString *name;

@end

NS_ASSUME_NONNULL_END

第二段代碼:

#import "ViewController.h"
#import "YPerson.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSMutableArray *array = [NSMutableArray array];
    
    YPerson *p = [[YPerson alloc] init];
    
    YPerson *p2 = [[YPerson alloc] init];

}
@end

二、方法調用底層初步思考(該用什么工具、從哪里入手):

1. 問題:@selector(array)和@selector(alloc)是否殊途同歸?

以上體現了一個常見的YPerson類的創建方式,即Alloc、init方式,以及對象Array的表面上看非alloc的創建方式,這里先把結果,實際上@selector(array)這個方法最終也是調用的alloc,這是怎么知道的呢?

2.探究工具:符號斷點與混編

這是我們通過這里方法似乎有很多,只講一種我覺得方便的,先斷點調試到目標代碼,如下圖所示:


右鍵調試附件1

紅色標記斷點后,因為OC的底層封裝是以動態庫的形式給到開發者的,點擊查詢調試,找不到@selector(array)的實現,進而無法找到array方法的調用鏈,即到這里就沒了:


右鍵調試附件2

怎么辦呢,這里講一種比較習慣用的即符號斷點和混編的混用:
走到斷點后,插入symbol(方法名)符號斷點如下:


符號斷點添加圖例

符號斷點創建好后,點擊繼續往下走箭頭,可以看到混編內容如下:


混編內容
3.結論:都是走了objc_alloc方法

可以看到,@selector(array)緊接著是走了objc_alloc方法的,同樣的方法調試@selector(alloc),也可以發現是走了objc_alloc方法,的確是一樣的鏈路。

PS:OC有一個非常特性-封裝,很多我們只能看到聲明,看不到實現,這好急啊,那就把墻砸了吧,依次點擊查詢調試是可以看到完整的調用鏈路的,如下提供源代碼鏈接:
objc4-818.2源碼下載

三、alloc干了什么,整體調用鏈路什么樣的:

前面都是鋪墊,這里來到了正主,即捏對象中的關鍵一步,alloc都干了些什么呢?先把結果公布,如下圖所示:


[class alloc] 調用鏈路.png

下載源碼對初始化行斷點調試,接著對alloc符號斷點調試,進而objc_alloc斷點調試,進入系統庫斷點后,依次點擊查看調試我們發現如下的調用鏈:

①.objc_alloc
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
②.callAlloc
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__    
    #warning 咱們只研究OBJC2
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif

    // No shortcuts available.
    if (allocWithZone) {
    #warning 基本棄用
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
③._objc_rootAllocWithZone
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}
④._class_createInstanceFromZone
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    #PersonalMark 核心方法1:instanceSize,計算需要多大的內存(捏個對象,估計身高體重,考量需要多少泥巴) 
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    #PersonalMark 核心方法2:calloc,根據計算出的內存size來給對象劃分內存,開辟內存空間(已經根據第一步捏出來大致的東西了,需要有放置的地方,有存才能有取)
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
    #PersonalMark 核心方法3:initInstanceIsa,將創建的對象分類,和對象的class綁定(確定捏的對象的類別,戰士、法師啥的)
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

到這里,結合流程圖及代碼片段,我們已經對Alloc方法在實際的調用鏈路用了較清晰的認知,那么為什么是這樣的呢?去掉繼承、封裝的干擾,那么整體的思路就是什么呢,我想捏個東西搞事情,我得去計算這個東西的用料及成本,并最終捏個雛形。這個工序我認為有很多思路,蘋果的思路非常棒,將它封裝工廠化,所有的對象創建都可以走這套流程,非常方便、規范;

前面也講了,大部分是封裝,核心的東西是計算并捏出來,這個該怎么做呢?

四、Alloc核心代碼挖掘(泥人內存計算、開辟內存、綁定Class)

①.instanceSize(計算對象所需內存)
    inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        #mark --- 快速計算
            return cache.fastInstanceSize(extraBytes);
        }
  
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        #mark --- 最小16字節
        if (size < 16) size = 16;
        return size;
    }

  size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

static inline size_t align16(size_t x) {
    #mark 16字節對齊
    return (x + size_t(15)) & ~size_t(15);
}

以上是計算所需內存的代碼,這保證了創建的size是16的整數倍,那么這里為什么要對齊,并且是16字節對齊呢? 首先從對齊的角度來說,要保證在cpu在按塊讀取的時候,每次讀取一樣大小的塊,更方便快捷,所以要對齊; 那么為什么要16字節對齊呢不是8字節或是32字節對齊呢?可以這么理解,一個對象都含有一個isa至少8字節,如果8字節對齊,對象之間會更緊湊,訪問錯誤發生的概率就會變大,而如果以32字節對齊,又會在一定程度上浪費內存,所以這里采取了16字節對齊。

②.calloc(開辟內存,用準備好的泥巴捏個雛形對象)
void *
calloc(size_t num_items, size_t size)
{
    return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

calloc的封裝不深挖了,看方法執行后的結果吧,附圖:


開辟內存前后對象地址打印.png

id obj這句代碼給予obj一個臟地址后(爛泥巴),通過calloc分配了具體的內存,obj有了最終的地址{這個地址和(YPerson*) $4 = 后跟的地址一樣的},即我們最終打印對象的地址

③.initInstanceIsa(將對象和類綁定,isa指向明確,泥人的出廠值設定)
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

這句代碼執行后的效果,打印地址如下圖所示:

指針和類綁定后打印結果.png

我們可以看到這個obj已經是我們可以正常調試的對象了,包含了類名、指向的內存地址。

講到這里,已經為對象的創建在底層做了什么講了該大概,那么這里還有些疑問,為何斷點先執行objc_alloc沒直接走alloc?

五、補充(llvm編譯階段對alloc的hook)

看一段非常特別的源碼貼圖:

更改alloc執行.png

有事、待補充

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容