Category的本質(zhì)<一>

Category的本質(zhì)<二>load,initialize方法
Category的本質(zhì)<三>關(guān)聯(lián)對(duì)象

一 寫在開頭

Category大家應(yīng)該用過(guò)漓拾,它主要是用在為對(duì)象的不同類型的功能分塊人断,比如說(shuō)人這個(gè)對(duì)象肴捉,我們可以為其創(chuàng)建三個(gè)分類痢站,分別對(duì)應(yīng)學(xué)習(xí)吻贿,工作鳍怨,休息呻右。
下面創(chuàng)建了一個(gè)Person類和兩個(gè)Person類的分類。分別是Person+Test和Person+Eat京景。這三個(gè)類中各有一個(gè)方法窿冯。

//Person類
- (void)run;
- (void)run{
    
    NSLog(@"run");
}
//Person+Test分類
- (void)test;
- (void)test{
    
    NSLog(@"test");
}
//Person+Eat分類
- (void)eat;
- (void)eat{
    
    NSLog(@"eat");
}

當(dāng)我們需要使用這些分類的時(shí)候只需要引入這些分類的頭文件即可:

#import "Person+Test.h"
#import "Person+Eat.h"

Person *person = [[Person alloc] init];
 [person run];
 [person test];
 [person eat];

我們都知道,函數(shù)調(diào)用的本質(zhì)是消息機(jī)制确徙。[person run]的本質(zhì)就是objc_mgs(person, @selector(run)),這個(gè)很好理解醒串,由于對(duì)象方法是存放在類對(duì)象中的,所以向person對(duì)象發(fā)送消息就是通過(guò)person對(duì)象的isa指針找到其類對(duì)象鄙皇,然后在類對(duì)象中找到這個(gè)對(duì)象方法芜赌。
[person test][person run]都是調(diào)用分類的對(duì)象方法,本質(zhì)應(yīng)該一樣伴逸。[person test]的本質(zhì)就是objc_mgs(person缠沈, @selector(test)),給實(shí)例對(duì)象發(fā)送消息错蝴,person對(duì)象通過(guò)自己的isa指針找到類對(duì)象洲愤,然后在自己的類對(duì)象中查找這個(gè)實(shí)例方法,那么問(wèn)題來(lái)了顷锰,person類對(duì)象中有沒(méi)有存儲(chǔ)分類中的這個(gè)對(duì)象方法呢柬赐?Person+Test這個(gè)分類會(huì)不會(huì)有自己的分類的類對(duì)象,將分類的對(duì)象方法存儲(chǔ)在這個(gè)類對(duì)象中呢官紫?

我們要清楚的一點(diǎn)是每個(gè)類只有一個(gè)類對(duì)象肛宋,不管這個(gè)類有沒(méi)有分類州藕。所以分類中的對(duì)象方法也就存儲(chǔ)在Person類的類對(duì)象中。后面我們會(huì)通過(guò)源碼證實(shí)這一點(diǎn)酝陈。

二 底層結(jié)構(gòu)

我們?cè)诘谝徊糠种v了床玻,分類中的對(duì)象方法和類方法最終會(huì)合并到類中,分類中的對(duì)象方法合并到類的類對(duì)象中沉帮,分類中的類方法合并到類的元類對(duì)象中锈死。那么這個(gè)合并是什么時(shí)候發(fā)生的呢?是在編譯器編譯器就幫我們合并好了嗎穆壕?實(shí)際上是在運(yùn)行期馅精,進(jìn)行的合并。
下面我們通過(guò)將Objective-c的代碼轉(zhuǎn)化為c++的源碼窺探一下Category的底層結(jié)構(gòu)粱檀。我們?cè)诿钚羞M(jìn)入到存放Person+Test.m這個(gè)文件的文件夾中洲敢,然后在命令行輸入clang -rewrite-objc Person+Test.m,這樣Person+Test.m這個(gè)文件就被轉(zhuǎn)化為了c++的源碼Person+Test.cpp。
我們打開這個(gè).cpp文件茄蚯,由于這個(gè)文件非常長(zhǎng)压彭,所以我們直接拖到最下面,找到_category_t這個(gè)結(jié)構(gòu)體渗常。這個(gè)結(jié)構(gòu)體就是每一個(gè)分類的結(jié)構(gòu):

struct _category_t {
    const char *name; //類名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; //對(duì)象方法列表
    const struct _method_list_t *class_methods; //實(shí)例方法列表
    const struct _protocol_list_t *protocols;  //協(xié)議列表
    const struct _prop_list_t *properties;  //屬性列表
};

我們接著往下找到這個(gè)結(jié)構(gòu)體的初始化:

static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,
    0,
    0,
};

通過(guò)結(jié)構(gòu)體名稱_OBJC_$_CATEGORY_Person_$_Test我們可以知道這是Person+Test這個(gè)分類的初始化壮不。類名對(duì)應(yīng)的是"Person",對(duì)象方法列表這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的是&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,類方法列表這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的是&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,其余的初始化都是空。
然后我們找到&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test這個(gè)結(jié)構(gòu)體:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_Test_test}}
};

可以看到這個(gè)結(jié)構(gòu)體中包含一個(gè)對(duì)象方法test皱碘,這正是Person+Test這個(gè)分類中的對(duì)象方法询一。
然后我們?cè)僬业?code>&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test這個(gè)結(jié)構(gòu)體:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test2", "v16@0:8", (void *)_C_Person_Test_test2}}
};

同樣可以看到這個(gè)結(jié)構(gòu)體,它包含一個(gè)類方法test2癌椿,這個(gè)同樣是Person+Test中的類方法健蕊。

三 利用runtime進(jìn)行合并

由于整個(gè)合并的過(guò)程是通過(guò)runtime進(jìn)行實(shí)現(xiàn)的,所以我們要了解這個(gè)過(guò)程就要通過(guò)查看runtime源碼去了解踢俄。下面是查看runtime源碼的過(guò)程:

  • 1.找到objc-os.mm這個(gè)文件缩功,這個(gè)文件是runtime的入口文件。
  • 2.在objc-os.mm中找到_objc_init(void)這個(gè)方法都办,這個(gè)方法是運(yùn)行時(shí)的初始化嫡锌。
  • 3.在_objc_init(void)中會(huì)調(diào)用_dyld_objc_notify_register(&map_images, load_images, unmap_image);,這個(gè)函數(shù)會(huì)傳入map_images這個(gè)參數(shù),我們點(diǎn)進(jìn)這個(gè)參數(shù)琳钉。
  • 4.點(diǎn)擊進(jìn)去map_images我們發(fā)現(xiàn)其中調(diào)用了map_images_nolock(count, paths, mhdrs);這個(gè)函數(shù)势木,我們點(diǎn)進(jìn)這個(gè)函數(shù)。
  • 5.map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[])這個(gè)函數(shù)非常長(zhǎng)歌懒,我們直接拉到這個(gè)函數(shù)最下面啦桌,找到_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);這個(gè)函數(shù),點(diǎn)擊進(jìn)去歼培。
  • 6.void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)這個(gè)方法大概就是讀取模塊的意思了震蒋。
    這個(gè)函數(shù)也是非常長(zhǎng),我們大概在中間位置找到了這樣一行注釋
// Discover categories.

這個(gè)基本上就是我們要找的處理Category的模塊了躲庄。
我們?cè)谶@行注釋下面找到這幾行代碼:

if (cls->isRealized()) {
       remethodizeClass(cls);
       classExists = YES;
                }

 if (cls->ISA()->isRealized()) {
       remethodizeClass(cls->ISA());  //class的ISA指針指向的是元類對(duì)象
               }

這個(gè)代碼里面有一個(gè)關(guān)鍵函數(shù)remethodizeClass,通過(guò)函數(shù)名我們大概猜測(cè)這個(gè)方法是重新組織類中的方法查剖,如果傳入的是類,則重新組織對(duì)象方法噪窘,如果傳入的是元類笋庄,則重新組織類方法。

  • 7.然后我們點(diǎn)進(jìn)這個(gè)方法里面查看:
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

我們看到這段代碼的核心是調(diào)用了attachCategories(cls, cats, true /*flush caches*/);這個(gè)方法倔监。這個(gè)方法中傳入了一個(gè)類cls和所有的分類cats直砂。

  • 8.我們點(diǎn)進(jìn)attachCategories(cls, cats, true /*flush caches*/);這個(gè)方法。這個(gè)方法基本上就是核心方法了浩习。
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    //方法數(shù)組
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //屬性數(shù)組
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //協(xié)議數(shù)組
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        //取出某個(gè)分類
        auto& entry = cats->list[i];
//確定是對(duì)象方法還是類方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
//得到類對(duì)象里面的數(shù)據(jù)
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //將所有分類的對(duì)象方法静暂,附加到類對(duì)象的方法列表中
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
//將所有分類的協(xié)議,附加到類對(duì)象的協(xié)議列表中
    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}
  • bool isMeta = cls->isMetaClass();判斷是類還是元類谱秽。
  • 創(chuàng)建總的方法數(shù)組洽蛀,屬性數(shù)組,協(xié)議數(shù)組
//方法數(shù)組
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //屬性數(shù)組
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //協(xié)議數(shù)組
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

這里mlists疟赊,proplists郊供,protolists都是用兩個(gè)修飾的,說(shuō)明是申請(qǐng)了一個(gè)二維數(shù)組近哟。這三個(gè)二維數(shù)組里面的一級(jí)對(duì)象分別是方法列表驮审,屬性列表,以及協(xié)議列表吉执。由于每一個(gè)分類Category都有一個(gè)方法列表疯淫,一個(gè)屬性列表,一個(gè)協(xié)議列表戳玫,方法列表中裝著這個(gè)分類的方法峡竣,屬性列表中裝著這個(gè)分類的屬性。所以mlists也就是裝著所有分類的所有方法量九。

  • 給前面創(chuàng)建的數(shù)組賦值
 while (i--) {
        //取出某個(gè)分類
        auto& entry = cats->list[i];
//確定是對(duì)象方法還是類方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

這段代碼就很清楚了适掰,通過(guò)一個(gè)while循環(huán)遍歷所有的分類,然后獲取該分類的所有方法荠列,賦值給前面創(chuàng)建的大數(shù)組类浪。

  • rw = cls->data();得到類對(duì)象里面的所有數(shù)據(jù)。
  • rw->methods.attachLists(mlists, mcount);將所有分類的方法肌似,附加到類的方法列表中费就。
  • 9.我們點(diǎn)進(jìn)這個(gè)方法里面看看具體的實(shí)現(xiàn):
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

傳進(jìn)來(lái)的這個(gè)addedLists參數(shù)就是前面得到的這個(gè)類的所有分類的對(duì)象方法或者類方法,而addedCount就是addedLists這個(gè)數(shù)組的個(gè)數(shù)川队。假設(shè)這個(gè)類有兩個(gè)分類力细,且每個(gè)分類有兩個(gè)方法睬澡,那么addedLists的結(jié)構(gòu)大概就應(yīng)該是這樣的:
[
[method, method]
[method, method]
]
addedCount = 2
我們看一下這個(gè)類的方法列表之前的結(jié)構(gòu):


7F4EE0B0-BD7D-4162-AD28-76209E034096.png

所以oldCount = 1

setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;

這一句是重新分配內(nèi)存,由于要把分類的方法合并進(jìn)來(lái)眠蚂,所以以前分配的內(nèi)存就不夠了煞聪,重新分配后的內(nèi)存:


82E1D1CD-096B-4AA8-9B23-96D05CAF7AD3.png
memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));

memmove這個(gè)函數(shù)是把第二個(gè)位置的對(duì)象移動(dòng)到第一個(gè)位置。這里也就是把這個(gè)類本來(lái)的方法列表移動(dòng)到第三個(gè)位置逝慧。

memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));

memcpy這個(gè)函數(shù)是把第二個(gè)位置的對(duì)象拷貝到第一個(gè)位置昔脯,也就是把a(bǔ)ddedLists拷貝到第一個(gè)位置,拷貝之后的內(nèi)存應(yīng)該是這樣的:


E788A916-FD1B-4E98-9363-7F36AF16C403.png

至此就把分類中的方法列表合并到了類的方法列表中笛臣。
通過(guò)上面的合并過(guò)程我們也明白了云稚,當(dāng)分類和類中有同樣的方法時(shí),類中的方法并沒(méi)有被覆蓋沈堡,只是分類的方法被放在了類的方法前面静陈,導(dǎo)致先找到了分類的方法,所以分類的方法就被執(zhí)行了诞丽。

四 總結(jié)

1.通過(guò)runtime加載某個(gè)類的所有Category數(shù)據(jù)窿给。
2.把所有Category的方法,屬性率拒,協(xié)議數(shù)據(jù)合并到一個(gè)大數(shù)組中崩泡,后參與編譯的Category數(shù)據(jù),會(huì)存放在數(shù)組的前面猬膨。
3.將合并后的分類數(shù)據(jù)(方法角撞,屬性,協(xié)議)勃痴,插入到類原來(lái)數(shù)據(jù)的前面谒所。
Category的本質(zhì)<二>load,initialize方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沛申,一起剝皮案震驚了整個(gè)濱河市劣领,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铁材,老刑警劉巖尖淘,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異著觉,居然都是意外死亡村生,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門饼丘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)趁桃,“玉大人,你說(shuō)我怎么就攤上這事∥啦。” “怎么了油啤?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蟀苛。 經(jīng)常有香客問(wèn)我益咬,道長(zhǎng),這世上最難降的妖魔是什么屹逛? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮汛骂,結(jié)果婚禮上罕模,老公的妹妹穿的比我還像新娘。我一直安慰自己帘瞭,他們只是感情好淑掌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蝶念,像睡著了一般抛腕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媒殉,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天担敌,我揣著相機(jī)與錄音,去河邊找鬼廷蓉。 笑死全封,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的桃犬。 我是一名探鬼主播刹悴,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼攒暇!你這毒婦竟也來(lái)了土匀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤形用,失蹤者是張志新(化名)和其女友劉穎就轧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體田度,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钓丰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了每币。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片携丁。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梦鉴,到底是詐尸還是另有隱情李茫,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布肥橙,位于F島的核電站魄宏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏存筏。R本人自食惡果不足惜宠互,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椭坚。 院中可真熱鬧予跌,春花似錦、人聲如沸善茎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)垂涯。三九已至烁焙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耕赘,已是汗流浹背骄蝇。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留操骡,地道東北人乞榨。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像当娱,于是被迫代替她去往敵國(guó)和親吃既。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,372評(píng)論 8 265
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉跨细,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評(píng)論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,097評(píng)論 1 32
  • iOS底層原理總結(jié) - Category的本質(zhì) 面試題 Category的實(shí)現(xiàn)原理鹦倚,以及Category為什么只能...
    xx_cc閱讀 30,452評(píng)論 36 199
  • 其實(shí) 秋天一直深不可測(cè) 因?yàn)榍餂鲞€在 誰(shuí)都想駐進(jìn)彼此內(nèi)心的島嶼 那雙飛的燕子飛得很低 還有走在我身旁的人 把思念...
    _老貓_閱讀 431評(píng)論 0 0