回答-阿里、字節(jié):一套高效的iOS面試題①(結(jié)構(gòu)模型)

最近工作比較閑,想鞏固一下自己的iOS開發(fā)基礎(chǔ)知識(shí),就回答一下阿里、字節(jié):一套高效的iOS面試題,歡迎各位同行批評(píng)斧正!

runtime是iOS開發(fā)最核心的知識(shí)了,如果下面的問(wèn)題都解決了,那么對(duì) objc-runtime 的理解已經(jīng)很深了。
runtime已經(jīng)開源了,這有一份別人調(diào)試好可運(yùn)行的源碼objc-runtime,也可以去官網(wǎng)找objc4,以下回答用到的源碼版本是objc4-756.2。

結(jié)構(gòu)模型

1、介紹下runtime的內(nèi)存模型(isa、對(duì)象、類、metaclass、結(jié)構(gòu)體的存儲(chǔ)信息等)

isa :

1、所有繼承于NSObject類的對(duì)象,在內(nèi)存布局中,第一個(gè)變量都是isa。

2、在arm64之前,實(shí)例對(duì)象的isa指向類對(duì)象,類對(duì)象的isa指向元類對(duì)象。

3、在arm64之后,isa經(jīng)過(guò)了優(yōu)化,采取了共用體的結(jié)構(gòu),將一個(gè)64位的內(nèi)存數(shù)據(jù)分開存儲(chǔ)了很多的信息,其中的33位才是存儲(chǔ)類對(duì)象、元類對(duì)象的地址值的,可以通過(guò)一個(gè)位運(yùn)算取出instance的isa包含的class的地址,取出class的isa包含的meta-class的地址。

union isa_t {
    Class cls;
#if defined(ISA_BITFIELD)
    struct {
      uintptr_t nonpointer        : 1; //0,代表普通的指針,存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址,1,代表優(yōu)化過(guò),使用位域存儲(chǔ)更多的信息                                    
      uintptr_t has_assoc         : 1; //是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象,如果沒有,釋放時(shí)會(huì)更快
      uintptr_t has_cxx_dtor      : 1; //是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放時(shí)會(huì)更快
      uintptr_t shiftcls          : 33; //存儲(chǔ)著Class、Meta-Class對(duì)象的內(nèi)存地址信息
      uintptr_t magic             : 6;  //用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化
      uintptr_t weakly_referenced : 1;  //是否有被弱引用指向過(guò),如果沒有,釋放時(shí)會(huì)更快
      uintptr_t deallocating      : 1;  //對(duì)象是否正在釋放
      uintptr_t has_sidetable_rc  : 1;  //里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
      uintptr_t extra_rc          : 19  //引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中,如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中
    };
#endif
};

對(duì)象:

@interface OffcnPerson : NSObject {
    @public
    int _age;
    int _no;
}

  OffcnPerson *person = [[OffcnPerson alloc] init];
  person->_age = 25;
  person->_no = 77123;

以上面的person對(duì)象為例,它的內(nèi)存結(jié)構(gòu)包含它父類的變量isa指針,以及person對(duì)象自身的變量_age、_no。

實(shí)際分配的大小應(yīng)該是所有變量加起來(lái),內(nèi)存對(duì)齊后的大小,對(duì)象的底層是使用結(jié)構(gòu)體存儲(chǔ)變量的。

結(jié)構(gòu)體的大小必須是結(jié)構(gòu)體中占用內(nèi)存最大的成員變量的倍數(shù) ,上面的person對(duì)象占用內(nèi)存變量是isa指針,占用8個(gè)字節(jié),所以person內(nèi)存最小得是16個(gè)字節(jié)。如果再增加一個(gè)int _height,變量的總大小是20個(gè)字節(jié),實(shí)際上會(huì)分配24個(gè)字節(jié)。

類:

class對(duì)象在內(nèi)存中存儲(chǔ)的信息主要包括

isa指針、superclass指針、類的屬性信息(@property)、類的對(duì)象方法信息( instance method)、類的協(xié)議信息(Protocol)、類的成員變量信息 (ivar)。

metaclass:

meta-class對(duì)象和class對(duì)象的內(nèi)存結(jié)構(gòu)是一樣的,在內(nèi)存中存儲(chǔ)的信息主要包括

isa指針、superclass指針、類的屬性信息(@property)、類的對(duì)象方法信息( instance method)。

2、為什么要設(shè)計(jì)metaclass

1、如果對(duì)象方法、類方法都儲(chǔ)存在類對(duì)象的結(jié)構(gòu)體中,需要在objc_class的結(jié)構(gòu)中增加一個(gè)數(shù)組存儲(chǔ)類對(duì)象方法列表。

窺探struct objc_class的結(jié)構(gòu).png

2、需要在調(diào)用objc_msgSend的時(shí)候就需要額外追加一個(gè)參數(shù)去分辨該次調(diào)用的是對(duì)象方法還是類方法,而我們現(xiàn)在的objc_msgSend()只接收了(id self, SEL _cmd, ...)這三種參數(shù),第一個(gè)self就是消息的接收者,第二個(gè)就是方法,后續(xù)的...就是各式各樣的參數(shù)。

如果不加參數(shù),對(duì)象方法和類方法同名時(shí)就不知道調(diào)用的是哪一個(gè)了。

如果在objc_msgSend中再添加一個(gè)參數(shù)標(biāo)識(shí)是對(duì)象方法還是類方法,就需要在消息發(fā)送機(jī)制中進(jìn)行對(duì)象類型和方法類型判斷,影響消息發(fā)送的效率。

3、把對(duì)象方法和類方法耦合在一起不符合設(shè)計(jì)模式中的單一職責(zé)原則,通過(guò)增加一個(gè)和類對(duì)象具有相同結(jié)構(gòu)的metaclass后,實(shí)例對(duì)象就干存儲(chǔ)屬性值的事,類對(duì)象存儲(chǔ)實(shí)例方法列表,元類對(duì)象存儲(chǔ)類方法列表。

instance的isa指向class,當(dāng)調(diào)用對(duì)象方法時(shí),通過(guò)instance的isa找到class,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用,class的isa指向meta-class,當(dāng)調(diào)用類方法時(shí),通過(guò)class的isa找到meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用。

3、class_copyIvarList&class_copyPropertyList區(qū)別

class_copyPropertyList返回的僅僅是對(duì)象類的屬性(@property聲明的屬性),而class_copyIvarList返回類的所有屬性(等同于getter+setter+變量)和變量(包括在@interface大括號(hào)中聲明的變量)。

@interface OffcnStudent ()
{
    int _age;
    int _no;
}

@property (nonatomic,   copy) NSString *name;
@property (nonatomic,   copy) NSString *department;

@end

  
- (void)invokeClass_copyPropertyList {
    unsigned int count =0;
    objc_property_t *properties = class_copyPropertyList(OffcnStudent.class, &count);
    for (int i =0; i<count;i++) {
        objc_property_t property = properties[i];
        //獲取屬性的名字
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        NSLog(@"propertyName:%@",propertyName);
    }
}
//打印
/*
propertyName:name
propertyName:department
*/

- (void)invokeClass_copyIvarList {
    unsigned int count =0;
    Ivar *ivars = class_copyIvarList(OffcnStudent.class, &count);
    for (int i =0; i<count;i++) {
        //獲取屬性的名字
        NSString *ivarName = [[NSString alloc] initWithCString:ivar_getName(ivars[i]) encoding:NSUTF8StringEncoding];
        NSLog(@"ivarName:%@",ivarName);
    }
}
//打印
/*
ivarName:_age
ivarName:_no
ivarName:_name
ivarName:_department
*/

4、class_rw_tclass_ro_t 的區(qū)別

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}
//由此可以看出bits是采用位域的方式存儲(chǔ)數(shù)據(jù)的,其中的某一些內(nèi)存空間存放的是class_rw_t

class_ro_t存放的是編譯期間就確定的;而class_rw_t是在runtime時(shí)才確定。

調(diào)用realizeClassWithoutSwift方法時(shí),如果當(dāng)前的class沒有實(shí)現(xiàn)初始化,會(huì)對(duì)rw進(jìn)行內(nèi)存分配、將class_ro_t的內(nèi)容拷貝過(guò)去,把rw設(shè)置給class的data、設(shè)置nextSiblingClass和 firstSubclass屬性、然后再將當(dāng)前類的分類的方法拷貝到rw的methods方法列表中。

當(dāng)然實(shí)際訪問(wèn)類的方法、屬性等也都是訪問(wèn)的class_rw_t中的內(nèi)容。

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;

     //初始化rw,把ro設(shè)置給rw
    // Normal class. Allocate writeable class data.
    rw = objc::zalloc<class_rw_t>();
    rw->set_ro(ro);
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    cls->setData(rw); //把rw設(shè)置給class的data
    
    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);


    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);
    
    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls); //設(shè)置nextSiblingClass和 firstSubclass屬性
    } else {
        addRootClass(cls);
    }

    // Attach categories 追加分類
    methodizeClass(cls, previously);

    return cls;
}

5、category如何被加載的,兩個(gè)category的load方法的加載順序,兩個(gè)category的同名方法的加載順序

實(shí)現(xiàn)思路:

1、每個(gè)分類中有多個(gè)方法,根據(jù)分類中方法的總大小 + 原來(lái)類對(duì)象中方法列表的大小,realloc重新分配數(shù)組內(nèi)存空間

2、往后挪動(dòng)原來(lái)類對(duì)象方法(類方法)列表的數(shù)據(jù),挪動(dòng)的大小為分類中方法的總大小

3、根據(jù)編譯順序把分類中的方法倒序添加的數(shù)組中,依次加入到新分配數(shù)組的前面

4、根據(jù)objc_msgsend去調(diào)用方法,先找類對(duì)象的方法列表,再通過(guò)superclass找到父類的方法列表進(jìn)行調(diào)用

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    bool isMeta = cls->isMetaClass();

    // 為方法列表的二維數(shù)組分配內(nèi)存,大小為分類方法的總大小
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int i = cats->count;
    while (i--) {
        //從分類方法的末尾取出方法
        auto& entry = cats->list[i];
        //根據(jù)當(dāng)前cls是類對(duì)象還是元類對(duì)象,對(duì)應(yīng)取出對(duì)象方法和類方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist; //依次把分類中的方法加入方法列表中
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
}

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;
        //把類中的方法向后挪(分類的方法總數(shù))個(gè)位置
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        //把分類中的方法列表拷貝到方法數(shù)組的前面
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

1、category如何被加載的?

從上面的兩段代碼可以看出,分類中的方法會(huì)被添加到方法列表的前面。

分類中的方法是按照倒序的方法添加到方法的列表前面的。

2、兩個(gè)category的load方法的加載順序?

1、首先要搞清楚load方法什么時(shí)候調(diào)用?load方法是在程序啟動(dòng)runtime加載類、分類的時(shí)候就會(huì)調(diào)用。

2、先調(diào)用類的load方法,再調(diào)用分類的load方法,load方法的調(diào)用不是通過(guò)消息發(fā)送機(jī)制,而是找到函數(shù)地址直接調(diào)用。

3、分類中的load方法是按照編譯的先后順序加載調(diào)用的,具體可以看下面call_category_loads中的實(shí)現(xiàn)。

4、先調(diào)用父類,再調(diào)用子類,先編譯先調(diào)用,具體可以看下面schedule_class_load的實(shí)現(xiàn)。

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more(重復(fù)的調(diào)用類的+load方法)
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE(然后調(diào)用分類的+load)
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

static void call_class_loads(void)
{
    int i;
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
      //找到load方法的地址直接調(diào)用
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.(按照正序遍歷分類中的load方法,然后調(diào)用)
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }
    }
}

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

3、兩個(gè)category的同名方法的加載順序?

由于分類的方法是倒序添加到方法列表的前面的,所以后編譯的先調(diào)用。

在Build Phases 的Compile Sources中可以調(diào)整編譯的順序。

6、category & extension區(qū)別,能給NSObject添加Extension嗎,結(jié)果如何?

1、category & extension區(qū)別?

categoryextension的主要區(qū)別是extension相當(dāng)于把變量屬性的訪問(wèn)權(quán)限改為私有了,編譯后就己經(jīng)合并到底層的C++代碼中了,category是在運(yùn)行時(shí)才合并到類的方法列表中。

2、能給NSObject添加Extension嗎,結(jié)果如何?

不能給NSObject添加Extension。Extension里面的變量和屬性都是私有的,需要在.m文件中添加,現(xiàn)在拿不到NSObject的源文件,所以無(wú)法給NSObject添加Extension。

7、OC的消息轉(zhuǎn)發(fā)機(jī)制和其他語(yǔ)言的消息機(jī)制優(yōu)劣對(duì)比?

源代碼 -> 編譯鏈接 -> 運(yùn)行。

對(duì)于C語(yǔ)言來(lái)說(shuō),編譯完之后生成的二進(jìn)制文件就是當(dāng)初源代碼那個(gè)樣子。C語(yǔ)言就是當(dāng)初寫的是什么,編譯的就是什么,運(yùn)行的結(jié)果也就是什么。運(yùn)行結(jié)果和當(dāng)初的編譯時(shí)保持一致的。

OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語(yǔ)言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行。OC能夠在程序的運(yùn)行中修改之前編譯好的一些東西,可以在運(yùn)行的過(guò)程中添加一些方法。

OC的動(dòng)態(tài)性就是由Runtime來(lái)支撐和實(shí)現(xiàn)的,Runtime是一套C語(yǔ)言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)

平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用。

8、在方法調(diào)用的時(shí)候,方法查詢-> 動(dòng)態(tài)解析-> 消息轉(zhuǎn)發(fā) 之前做了什么

OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)。

1、方法查詢(消息發(fā)送)

1、在方法查詢之前會(huì)判斷receiver(方法調(diào)用者)是否為nil,如果為nil就直接退出。

2、receiver通過(guò)isa指針找到receiverClass,從receiverClass的cache方法列表中查找selector方法名,找到方法后調(diào)用,結(jié)束查找。

3、在cache方法列表沒找到方法,就去receiverClass的class_rw_t中查找方法,找到方法后調(diào)用,結(jié)束查找。并將該方法緩存到receiverClass的cache方法列表中。

4、在receiverClass的class_rw_t沒找到方法,就從receiverClass的superClass的cache方法列表查找,找到方法后調(diào)用,結(jié)束查找。并將該方法緩存到receiverClass的cache方法列表中。

5、在receiverClass的superClass的cache中沒找到方法,就去receiverClass的superClass的class_rw_t中查找,找到方法后調(diào)用,結(jié)束查找。并將該方法緩存到receiverClass的cache方法列表中。

6、找不到就去receiverClass的superClass的superClass中重復(fù)以上過(guò)程,如果上層已沒有superClass就進(jìn)入下一階段,動(dòng)態(tài)方法解析。

2、動(dòng)態(tài)方法解析

1、判斷是否曾經(jīng)有動(dòng)態(tài)解析,如果有動(dòng)態(tài)解析,直接走第3步中的消息轉(zhuǎn)發(fā)

2、如果沒有動(dòng)態(tài)方法解析過(guò),可以通過(guò)+resolveInstanceMethod:、+resolveClassMethod:來(lái)動(dòng)態(tài)添加方法實(shí)現(xiàn)。

3、添加過(guò)方法后,標(biāo)記為已經(jīng)動(dòng)態(tài)解析,然后重新走消息發(fā)送的流程,“從receiverClass的cache中查找方法”這一步開始執(zhí)行。

void other(id self,SEL _cmd) {
    
}

+(BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test)) {
       // 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
        class_addMethod(self, sel, (IMP)other, "v@:");
      // 返回YES代表有動(dòng)態(tài)添加方法
      return YES;
    }
    return [super resolveClassMethod:sel];
}

3、消息轉(zhuǎn)發(fā)

1、調(diào)用forwardingTargetForSelector:方法,返回值不為nil,調(diào)用objc_msgSend

2、返回值為nil,調(diào)用methodSignatureForSelector:方法,返回值不為nil,調(diào)用forwardInvocation:方法

3、返回值為nil,調(diào)用doesNotRecognizeSelector:方法

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[MJCat alloc] init], aSelector)
        return [[MJCat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

 //方法簽名:返回值類型、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封裝了一個(gè)方法調(diào)用,包括:方法調(diào)用者、方法名、方法參數(shù)
//    anInvocation.target 方法調(diào)用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    anInvocation.target = [[MJCat alloc] init];
    [anInvocation invoke];

    [anInvocation invokeWithTarget:[[MJCat alloc] init]];
}

9、IMP、SEL、Method的區(qū)別和使用場(chǎng)景?

1、IMP代表函數(shù)的具體實(shí)現(xiàn)

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

2、SEL代表函數(shù)名,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似

可以通過(guò)@selector()和sel_registerName()獲得

可以通過(guò)sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串

不同類中相同名字的方法,所對(duì)應(yīng)的方法選擇器是相同的

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

method_t是對(duì)函數(shù)的封裝。

typedef struct method_t *Method;
struct method_t {
    SEL name;     //函數(shù)名
    const char *types;  //編碼 (返回值類型、參數(shù)類型)
    MethodListIMP imp; //指向函數(shù)的指針 (函數(shù)地址)
};

iOS中提供了一個(gè)叫做@encode的指令,可以將具體的類型表示成字符串編碼,具體可以查看Type Encoding

10、load、initialize方法的區(qū)別什么?在繼承關(guān)系中他們有什么區(qū)別

調(diào)用方式不同:

load是根據(jù)函數(shù)地址直接調(diào)用,initialize是通過(guò)objc_msgSend調(diào)用。

調(diào)用時(shí)機(jī)不同:

load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次)

initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會(huì)initialize一次(父類的initialize方法可能會(huì)被調(diào)用多次)。

load、initialize的調(diào)用順序?

load

1> 先調(diào)用類的load
a) 先編譯的類,優(yōu)先調(diào)用load
b) 調(diào)用子類的load之前,會(huì)先調(diào)用父類的load

2> 再調(diào)用分類的load
a) 先編譯的分類,優(yōu)先調(diào)用load

initialize

1> 先初始化父類
2> 再初始化子類(可能最終調(diào)用的是父類的initialize方法)

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

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

  • [TOC] runtime相關(guān)問(wèn)題 面試題出自掘金的一篇文章《阿里、字節(jié):一套高效的iOS面試題》該面試題解答gi...
    NinthDay閱讀 21,571評(píng)論 15 194
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,662評(píng)論 1 32
  • 設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型的...
    CoderBigBear閱讀 1,267評(píng)論 0 2
  • 設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡(jiǎn)要敘述? 設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型的...
    卑微的戲子閱讀 683評(píng)論 0 1
  • 1.關(guān)于方法的執(zhí)行順序問(wèn)題,代碼示例如下: dispatch_after 第二個(gè)參數(shù)為0,可以理解為在當(dāng)前時(shí)刻向主...
    oc123閱讀 1,033評(píng)論 0 0

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