iOS面試-類的動(dòng)態(tài)創(chuàng)建

概念

1. 如何動(dòng)態(tài)創(chuàng)建一個(gè)類?

Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
objc_registerClassPair(TPerson);

那么objc_allocateClassPair是如何實(shí)現(xiàn)的呢?

Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;

    // 如果已經(jīng)存在相同名稱的類,則創(chuàng)建失敗,直接返回nil
    if (look_up_class(name, NO, NO)) return nil;

    mutex_locker_t lock(runtimeLock);

    // Fail if the class name is in use.
    // Fail if the superclass isn't kosher.
    if (getClassExceptSomeSwift(name)  ||
        !verifySuperclass(superclass, true/*rootOK*/))
    {
        return nil;
    }

    // Allocate new classes.
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);

    // fixme mangle the name if it looks swift-y?
    objc_initializeClassPair_internal(superclass, name, cls, meta);

    return cls;
}

由代碼可知,objc_allocateClassPair有3個(gè)參數(shù):

  • 需要?jiǎng)?chuàng)建的類的父類
  • 需要?jiǎng)?chuàng)建的類名
  • 需要?jiǎng)?chuàng)建的類的內(nèi)存容量

創(chuàng)建一個(gè)類的時(shí)候,首先我們要判斷是否已經(jīng)存在相同名稱的類,如果存在則創(chuàng)建失敗,直接返回nil;其次還需要判斷其父類是否已經(jīng)實(shí)現(xiàn),如果沒有實(shí)現(xiàn)也會(huì)創(chuàng)建失敗;然后調(diào)用

static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)

進(jìn)行創(chuàng)建類的相關(guān)信息,設(shè)置類和元類的data數(shù)據(jù)如rw/ro,并綁定父類、子類、元類的關(guān)系;最后將該類加入到存放class的表中。

2. 添加成員變量

添加成員變量的實(shí)現(xiàn)如下:

BOOL class_addIvar(Class cls, const char *name, size_t size, 
              uint8_t alignment, const char *type)
{
    ...... 

    // Can only add ivars to in-construction classes.
    if (!(cls->data()->flags & RW_CONSTRUCTING)) {
        return NO;
    }

    // Check for existing ivar with this name, unless it's anonymous.
    // Check for too-big ivar.
    // fixme check for superclass ivar too?
    if ((name  &&  getIvar(cls, name))  ||  size > UINT32_MAX) {
        return NO;
    }

    class_ro_t *ro_w = make_ro_writeable(cls->data());

    // fixme allocate less memory here
    
    ivar_list_t *oldlist, *newlist;
    if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) {
        size_t oldsize = oldlist->byteSize();
        newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1);
        memcpy(newlist, oldlist, oldsize);
        free(oldlist);
    } else {
        newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1);
        newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t);
    }
    
    ......

    ro_w->ivars = newlist;
    cls->setInstanceSize((uint32_t)(offset + size));

    // Ivar layout updated in registerClass.

    return YES;
}

由代碼可知,需要傳入的參數(shù)如下:

  • 需要?jiǎng)?chuàng)建成員變量的類
  • 成員變量的名稱
  • 成員變量的內(nèi)存容量
  • 該成員變量的內(nèi)存對(duì)齊所偏移的量
  • 成員變量的類型

給類添加成員變量時(shí),首先要確定該類是正在構(gòu)造的類;其次需要確定該變量名稱的唯一性,即在作用域內(nèi)的唯一性,且不是內(nèi)存超大類型的變量;然后對(duì)已經(jīng)創(chuàng)建的內(nèi)存進(jìn)行擴(kuò)容,將新的變量寫入到roivars中。

3. 注冊(cè)類

  • 將類的狀態(tài)置為RW_CONSTRUCTED
  • 將類插入存儲(chǔ)類的表中
void objc_registerClassPair(Class cls)
{
    mutex_locker_t lock(runtimeLock);

    checkIsKnownClass(cls);

    ......

    // Clear "under construction" bit, set "done constructing" bit
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

    // Add to named class table.
    addNamedClass(cls, cls->data()->ro->name);
}

這樣,動(dòng)態(tài)創(chuàng)建類就完成了。上面的例子中,我們不止創(chuàng)建了一個(gè)名稱為TPerson的類,并且為該類創(chuàng)建了一個(gè)名稱為nickName類型為NSString的成員變量。我們可以打印驗(yàn)證結(jié)果:

id person = [TPerson alloc];
[person setValue:@"chengXuYuan" forKey:@"nickName"];
NSLog(@"--%@--",[person valueForKey:@"nickName"]);

結(jié)果為:
--chengXuYuan--

題目

1. 以下成員變量是否能夠添加成功?為什么?

Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
objc_registerClassPair(TPerson);
class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");

答案:不能添加成功。因?yàn)槌蓡T變量是存在于classclass_ro_t中,當(dāng)我們調(diào)用objc_registerClassPair注冊(cè)完類之后,類的狀態(tài)就置為RW_CONSTRUCTED,而在調(diào)用class_addIvar添加成員變量的時(shí)候,會(huì)判斷當(dāng)前類的狀態(tài)是否是RW_CONSTRUCTED,如果是則會(huì)添加成員變量失敗。簡而言之,成員變量存在ro中,創(chuàng)建類成功之后ro就是不可變的,所以就不能添加成員變量了。

2. 是否可以添加property?如果可以,如何添加?

答案:可以添加,因?yàn)?code>property是存在rw里的,所以可以在任意時(shí)刻添加。首先我們來看一下添加屬性的API:

BOOL 
class_addProperty(Class cls, const char *name, 
                  const objc_property_attribute_t *attrs, unsigned int n)
{
    return _class_addProperty(cls, name, attrs, n, NO);
}

static bool 
_class_addProperty(Class cls, const char *name, 
                   const objc_property_attribute_t *attrs, unsigned int count, 
                   bool replace)
{
    if (!cls) return NO;
    if (!name) return NO;

    property_t *prop = class_getProperty(cls, name);
    if (prop  &&  !replace) {
        // already exists, refuse to replace
        return NO;
    } 
    else if (prop) {
        // replace existing
        mutex_locker_t lock(runtimeLock);
        try_free(prop->attributes);
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
    }
    else {
        mutex_locker_t lock(runtimeLock);
        
        assert(cls->isRealized());
        
        property_list_t *proplist = (property_list_t *)
            malloc(sizeof(*proplist));
        proplist->count = 1;
        proplist->entsizeAndFlags = sizeof(proplist->first);
        proplist->first.name = strdupIfMutable(name);
        proplist->first.attributes = copyPropertyAttributeString(attrs, count);
        
        cls->data()->properties.attachLists(&proplist, 1);
        
        return YES;
    }
}

由代碼可知,class_addProperty方法需要傳入的參數(shù)如下:

  • 屬性的名稱
  • 屬性的修飾詞數(shù)組
  • 屬性修飾詞數(shù)組的元素個(gè)數(shù)

屬性的修飾詞數(shù)組中的第一個(gè)元素是按照屬性的類型規(guī)定的Code:

Property declaration Code
char Tc
double Td
enum/int/signed Ti
float Tf
long Tl
short Ts
signed TI
id T@
int指針 T^i
void指針 T^v

其中屬性的修飾詞指的就是strong、weak、copy、assign、nonatomic、atomic等修飾詞。

Code Meaning
R read-only
& retain
C copy
N nonatomic
G<name> getter
S<name> setter
D dynamic
W weak

屬性的修飾詞數(shù)組的最后一個(gè)元素則是V_拼接屬性名稱。

綜上所述,假如我們需要給TPerson添加一個(gè)屬性:

@property (nonatomic, copy) NSString *englishName; 

那么我們可以如下實(shí)現(xiàn):

const char *propertyName = "englishName";

//type
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; 

// C = copy
objc_property_attribute_t ownership0 = { "C", "" }; 

// N = nonatomic
objc_property_attribute_t ownership1 = { "N", "" };

//variable name
objc_property_attribute_t backingivar  = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String };  

objc_property_attribute_t attrs[] = {type, ownership0, ownership1, backingivar};

// attrs = {T@"NSString", C, N, V_englishName}
class_addProperty(TPerson, propertyName, attrs, 4);

但是需要注意的是,此時(shí)添加的屬性是無法進(jìn)行賦值和取值操作的,因?yàn)槲覀儾]有實(shí)現(xiàn)其set/get方法。 只有實(shí)現(xiàn)了set/get方法,動(dòng)態(tài)添加的屬性,才是一個(gè)完整的屬性。

3. 為TPerson添加方法

API如下:

BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    
    ......
    
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

由代碼可知,需要的參數(shù)如下:

  • 要添加方法的類
  • 方法編號(hào)(方法名稱)
  • 方法實(shí)現(xiàn)的指針
  • 方法簽名

給類動(dòng)態(tài)的添加方法的時(shí)候,首先通過類名和方法去尋找是否存在method_t,如果存在則添加失敗,否則將當(dāng)前方法綁定到類的data中的methods里。這樣方法就添加成功了。

以下代碼給TPersonenglishName屬性添加了set/get方法:

class_addMethod(TPerson, @selector(setEnglishName:), (IMP)SetterEnglishName, "v@:@");
class_addMethod(TPerson, @selector(englishName), (IMP)GetEnglishName, "@@:");

void SetEnglishName(NSString *value){
    printf("%s/n",__func__);
}

NSString *GetEnglishName(){
    printf("%s/n",__func__);
    return @"HaHaHa";
}

總結(jié)

當(dāng)我們需要?jiǎng)討B(tài)創(chuàng)建類的時(shí)候,一定要注意該類相關(guān)信息的處理。比如添加成員變量必須在注冊(cè)類之前;添加屬性必須為其添加set/get方法才能正常使用。

?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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