iOS動態添加屬性

以前一篇文章《iOS關聯對象》詳細介紹瞭如何經過關聯對象添加屬性,本篇文章將介紹如何經過runtime的class_addPropertyclass_addIvar動態添加屬性,而且帶領你們看看這兩個方法底層是如何實現的。git

class_addProperty添加屬性

對於已經存在的類咱們用class_addProperty方法來添加屬性,而對於動態建立的類咱們經過class_addIvar添加屬性, 它會改變一個已有類的內存佈局,通常是經過objc_allocateClassPair動態建立一個class,才能調用class_addIvar建立Ivar,最後經過objc_registerClassPair註冊class。github

對於已經存在的類,class_addIvar是不可以添加屬性的數組

首先咱們聲明瞭一個Personless

@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@end
複製代碼

而後咱們經過class_addProperty動態添加屬性佈局

id getter(id object,SEL _cmd1){
    NSString *key = NSStringFromSelector(_cmd1);
    return objc_getAssociatedObject(object, (__bridge const void * _Nonnull)(key));
}
void setter(id object,SEL _cmd1,id newValue){
    NSString *key = NSStringFromSelector(_cmd1);
    key = [[key substringWithRange:NSMakeRange(3, key.length-4)] lowercaseString];
    objc_setAssociatedObject(object, (__bridge const void * _Nonnull)(key), newValue, OBJC_ASSOCIATION_RETAIN);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
        objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
        objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
        objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", @"sex"] UTF8String] };  //variable name
        objc_property_attribute_t attrs[] = { type, ownership0, ownership,backingivar};//這個數組必定要按照此順序才行
        BOOL add = class_addProperty([Person class], "sex", attrs, 4);
        if (add) {
            NSLog(@"添加成功\n");
        }else{
            NSLog(@"添加失敗\n");
        }

        class_addMethod([Person class], NSSelectorFromString(@"sex"), (IMP)getter, "@@:");
        class_addMethod([Person class], NSSelectorFromString(@"setSex:"), (IMP)setter, "v@:@");

        unsigned int count;
        objc_property_t *properties =class_copyPropertyList([Person class], &count);
        for (int i = 0; i < count; i++) {
            objc_property_t property = properties[i];
            NSLog(@"名字:%s--屬性:%s\n",property_getName(property),property_getAttributes(property));
        }

        Person *person = [Person new];
        person.name = @"FlyOceanFish";
        [person setValue:@"男" forKey:@"sex"];
        NSLog(@"name:%@",person.name);
        NSLog(@"sex:%@",[person valueForKey:@"sex"]);
    }
    return 0;
}
複製代碼

Demo地址ui

這裏要注意幾點:this

  • attrs屬性設置的數組必定是此順序 此屬性設置是參照原有屬性的打印格式進行設置的,name是原先有的,sex是後來動態添加的,以下:

2019-01-02 14:39:39.370405+0800 MyProperty[1354:159207] 名字:sex--屬性:T@"NSString",C,N,V_sexatom

2019-01-02 14:39:39.370425+0800 MyProperty[1354:159207] 名字:name--屬性:T@"NSString",C,N,V_namespa

  • 添加完屬性咱們要添加上對應的set、get方法,由於咱們是經過kvo的方式設值和取值的,它會調用set、get方法,若是沒有的話,會報錯。

底層代碼實現

上邊代碼演示瞭如何動態添加屬性,接下來讓咱們看看蘋果底層是如何實現的。指針

class_addProperty

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
        rwlock_writer_t lock(runtimeLock);
        try_free(prop->attributes);
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
    }
    else {
        rwlock_writer_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;
    }
}
複製代碼

經過代碼咱們能夠看到若是添加一個已經存在的屬性是添加不成功的; 添加一個新屬性,是實例化了一個property_list_t對象,最終調用了cls的data方法,返回了class_rw_t指針,最終添加在屬性properties的一個數組中。

還有一個結構體的名字是class_ro_t,與class_rw_t是配合使用的,你們有興趣能夠自行去研究。ro即read only;rw即read write。看到這裏應該能猜個八九不離十

class對象結構體

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() {
        return bits.data();
    }
    ...
  }
複製代碼

class_rw_t結構體

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    ...
  }
複製代碼

class_addIvar

BOOL

{
    if (!cls) return NO;

    if (!type) type = "";
    if (name  &&  0 == strcmp(name, "")) name = nil;

    rwlock_writer_t lock(runtimeLock);

    assert(cls->isRealized());

    // No class variables
    if (cls->isMetaClass()) {
        return NO;
    }

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

    uint32_t offset = cls->unalignedInstanceSize();
    uint32_t alignMask = (1<<alignment)-1;
    offset = (offset + alignMask) & ~alignMask;

    ivar_t& ivar = newlist->get(newlist->count++);
#if __x86_64__
    // Deliberately over-allocate the ivar offset variable.
    // Use calloc() to clear all 64 bits. See the note in struct ivar_t.
    ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1);
#else
    ivar.offset = (int32_t *)malloc(sizeof(int32_t));
#endif
    *ivar.offset = offset;
    ivar.name = name ? strdupIfMutable(name) : nil;
    ivar.type = strdupIfMutable(type);
    ivar.alignment_raw = alignment;
    ivar.size = (uint32_t)size;

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

    // Ivar layout updated in registerClass.

    return YES;
}
複製代碼

經過以上代碼咱們能夠看到經過此方法添加的屬性是實例化了ivar_t對象,而且存儲在了class_ro_t對象中了。因此跟咱們上邊說的改變了類的內存佈局一致。

總結

咱們經過一個demo實現了動態添加屬性,經過底層源碼解析讓你們完全認識了class_addPropertyclass_addIvar兩個方法。runtime讓oc成爲了一門動態語言,只有咱們想不到的,沒有runtime作不到的。

個人博客

FlyOceanFish

相關文章
相關標籤/搜索