OC的分類方法調用原理

在使用OC開發中,咱們常常使用分類爲一些不方便修改的類,添加分類,達到爲類添加"屬性"和方法的目的,下面是爲LBPerson類添加分類的代碼:數組

#import "LBPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson (Animation)

- (void)run;
@end

NS_ASSUME_NONNULL_END

#import "LBPerson+Animation.h"

@implementation LBPerson (Animation)

- (void)run {
    NSLog(@"LBPerson+Animation.h");
}
@end

若是分類中的方法和原類中方法名稱相同,會優先調用分類中的方法。

LBPerson中代碼以下:源碼分析

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson : NSObject
- (void)run;
@end

NS_ASSUME_NONNULL_END
#import "LBPerson.h"

@implementation LBPerson

- (void)run {
    NSLog(@"LBPerson run");
}
@end

LBPerson+Animation中代碼以下:ui

#import "LBPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson (Animation)

- (void)run;
@end

NS_ASSUME_NONNULL_END
#import "LBPerson+Animation.h"

@implementation LBPerson (Animation)

- (void)run {
    NSLog(@"Animation run");
}
@end

在main方法中調用person對象的run方法代碼以及打印以下:spa

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LBPerson *person = [[LBPerson alloc] init];
        [person run];
    }
    return 0;
}

// 輸出結果
Animation run

若是兩個分類中含有相同的方法,則會按照編譯的順序,編譯靠後的分類會被調用

下面就從runtime的源碼分析爲何會按照上述規則進行調用:code

// cls 本類
// cats_list 存儲分類數組
// cats_count 存儲分類的數量
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
       在啓動期間只有少數類擁有超過64個分類
     * This uses a little stack, and avoids malloc.
     * 使用少許棧避免分類
     * Categories must be added in the proper order, which is back
       分類確定會按照合適的順序被添加,這個順序就是從後向前
     * to front. To do that with the chunking, we iterate cats_list
       爲了作這個組塊,咱們迭代分類數組從前到後
     * from front to back, build up the local buffers backwards,
       建造一個向後的本地緩衝
     * and call attachLists on the chunks. attachLists prepends the
       調用附加數組在多個組塊上
     * lists, so the final result is in the expected order.
       附加數組頻道list上,因此最終的結果就是按照期待的順序
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ]; // 類方法數組
    property_list_t *proplists[ATTACH_BUFSIZ]; // 對象方法數組
    protocol_list_t *protolists[ATTACH_BUFSIZ]; //協議數組

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS); // 是不是元類
    auto rwe = cls->data()->extAllocIfNeeded(); // 找到本類的rw 而且判斷是否須要額外分配
    // 遍歷分類數組
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i]; // 具體的分類
        // 取出類方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                // 添加到原本的方法列表中
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 取出對象方法
        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }
        // 取出協議
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

決定方法調用順序就是下面源碼:orm

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]));
        }
    }

上述源碼註釋純屬我的的理解對象

相關文章
相關標籤/搜索