内容简介:之前一篇文章《iOS关联对象》详细介绍了如何通过关联对象添加属性,本篇文章将介绍如何通过runtime的对于已经存在的类我们用
之前一篇文章《iOS关联对象》详细介绍了如何通过关联对象添加属性,本篇文章将介绍如何通过runtime的 class_addProperty
或 class_addIvar
动态添加属性,并且带领大家看看这两个方法底层是如何实现的。
class_addProperty
添加属性
对于已经存在的类我们用 class_addProperty
方法来添加属性,而对于动态创建的类我们通过 class_addIvar
添加属性, 它会改变一个已有类的内存布局,一般是通过 objc_allocateClassPair
动态创建一个class,才能调用 class_addIvar
创建Ivar,最后通过 objc_registerClassPair
注册class。
对于已经存在的类,
class_addIvar
是不能够添加属性的
首先我们声明了一个 Person
类
@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(@"添加成功
");
}else{
NSLog(@"添加失败
");
}
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
",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;
}
这里要注意几点:
-
attrs属性设置的数组一定是此顺序 此属性设置是参照原有属性的打印格式进行设置的,
name是原先有的,sex是后来动态添加的,如下:
2019-01-02 14:39:39.370405+0800 MyProperty[1354:159207] 名字:sex--属性:T@"NSString",C,N,V_sex 2019-01-02 14:39:39.370425+0800 MyProperty[1354:159207] 名字:name--属性:T@"NSString",C,N,V_name
-
添加完属性我们要添加上对应的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 = (1count++);
# 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做不到的。
我的博客
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何自动地向知识图谱中添加属性?
- powershell – 如何向自定义对象添加更多属性值
- 在 Django 中如何添加没有微秒的 DateTimeField 属性
- Next.js 6.0.4-canary.9 发布,添加类属性配置
- CSS 属性篇(七):Display属性
- JavaScript对象之数据属性与访问器属性
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Think Python
Allen B. Downey / O'Reilly Media / 2012-8-23 / GBP 29.99
Think Python is an introduction to Python programming for students with no programming experience. It starts with the most basic concepts of programming, and is carefully designed to define all terms ......一起来看看 《Think Python》 这本书的介绍吧!