以前一篇文章《iOS關聯對象》詳細介紹瞭如何經過關聯對象添加屬性,本篇文章將介紹如何經過runtime的class_addProperty
或class_addIvar
動態添加屬性,而且帶領你們看看這兩個方法底層是如何實現的。git
class_addProperty
添加屬性對於已經存在的類咱們用class_addProperty
方法來添加屬性,而對於動態建立的類咱們經過class_addIvar
添加屬性, 它會改變一個已有類的內存佈局,通常是經過objc_allocateClassPair
動態建立一個class,才能調用class_addIvar
建立Ivar,最後經過objc_registerClassPair
註冊class。github
對於已經存在的類,
class_addIvar
是不可以添加屬性的數組
首先咱們聲明瞭一個Person
類less
@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
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
上邊代碼演示瞭如何動態添加屬性,接下來讓咱們看看蘋果底層是如何實現的。指針
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;
...
}
複製代碼
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_addProperty
和class_addIvar
兩個方法。runtime讓oc成爲了一門動態語言,只有咱們想不到的,沒有runtime作不到的。