概念
1. 如何动态创建一个类?
Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
objc_registerClassPair(TPerson);
那么objc_allocateClassPair
是如何实现的呢?
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)
{
Class cls, meta;
// 如果已经存在相同名称的类,则创建失败,直接返回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个参数:
- 需要创建的类的父类
- 需要创建的类名
- 需要创建的类的内存容量
创建一个类的时候,首先我们要判断是否已经存在相同名称的类,如果存在则创建失败,直接返回nil
;其次还需要判断其父类是否已经实现,如果没有实现也会创建失败;然后调用
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
进行创建类的相关信息,设置类和元类的data
数据如rw/ro
,并绑定父类、子类、元类的关系;最后将该类加入到存放class
的表中。
2. 添加成员变量
添加成员变量的实现如下:
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;
}
由代码可知,需要传入的参数如下:
- 需要创建成员变量的类
- 成员变量的名称
- 成员变量的内存容量
- 该成员变量的内存对齐所偏移的量
- 成员变量的类型
给类添加成员变量时,首先要确定该类是正在构造的类;其次需要确定该变量名称的唯一性,即在作用域内的唯一性,且不是内存超大类型的变量;然后对已经创建的内存进行扩容,将新的变量写入到ro
的ivars
中。
3. 注册类
- 将类的状态置为
RW_CONSTRUCTED
- 将类插入存储类的表中
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);
}
这样,动态创建类就完成了。上面的例子中,我们不止创建了一个名称为TPerson
的类,并且为该类创建了一个名称为nickName
类型为NSString
的成员变量。我们可以打印验证结果:
id person = [TPerson alloc];
[person setValue:@"chengXuYuan" forKey:@"nickName"];
NSLog(@"--%@--",[person valueForKey:@"nickName"]);
结果为:
--chengXuYuan--
题目
1. 以下成员变量是否能够添加成功?为什么?
Class TPerson = objc_allocateClassPair([NSObject class], "TPerson", 0);
objc_registerClassPair(TPerson);
class_addIvar(TPerson, "nickName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
答案:不能添加成功。因为成员变量是存在于class
的class_ro_t
中,当我们调用objc_registerClassPair
注册完类之后,类的状态就置为RW_CONSTRUCTED
,而在调用class_addIvar
添加成员变量的时候,会判断当前类的状态是否是RW_CONSTRUCTED
,如果是则会添加成员变量失败。简而言之,成员变量存在ro
中,创建类成功之后ro
就是不可变的,所以就不能添加成员变量了。
2. 是否可以添加property
?如果可以,如何添加?
答案:可以添加,因为property
是存在rw
里的,所以可以在任意时刻添加。首先我们来看一下添加属性的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
方法需要传入的参数如下:
- 类
- 属性的名称
- 属性的修饰词数组
- 属性修饰词数组的元素个数
属性的修饰词数组中的第一个元素是按照属性的类型规定的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 |
属性的修饰词数组的最后一个元素则是V_
拼接属性名称。
综上所述,假如我们需要给TPerson
添加一个属性:
@property (nonatomic, copy) NSString *englishName;
那么我们可以如下实现:
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);
但是需要注意的是,此时添加的属性是无法进行赋值和取值操作的,因为我们并没有实现其set/get
方法。 只有实现了set/get
方法,动态添加的属性,才是一个完整的属性。
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;
}
由代码可知,需要的参数如下:
- 要添加方法的类
- 方法编号(方法名称)
- 方法实现的指针
- 方法签名
给类动态的添加方法的时候,首先通过类名和方法去寻找是否存在method_t
,如果存在则添加失败,否则将当前方法绑定到类的data
中的methods
里。这样方法就添加成功了。
以下代码给TPerson
的englishName
属性添加了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";
}
总结
当我们需要动态创建类的时候,一定要注意该类相关信息的处理。比如添加成员变量必须在注册类之前;添加属性必须为其添加set/get
方法才能正常使用。
网友评论