概念
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ò)容,將新的變量寫入到ro的ivars中。
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變量是存在于class的class_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里。這樣方法就添加成功了。
以下代碼給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";
}
總結(jié)
當(dāng)我們需要?jiǎng)討B(tài)創(chuàng)建類的時(shí)候,一定要注意該類相關(guān)信息的處理。比如添加成員變量必須在注冊(cè)類之前;添加屬性必須為其添加set/get方法才能正常使用。