上一章中主要寫到runtime中類的實現(xiàn)太示,這一章我想主要寫一下關(guān)于runtime中類的一些相關(guān)操作。
一、成員變量與屬性的操作
1.基礎(chǔ)數(shù)據(jù)類型
先回顧一下class的定義
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
class結(jié)構(gòu)體中存儲了ivars濒翻,methodLists,protocols啦膜。這三個結(jié)構(gòu)體中分別存放了這個類包含的成員變量有送,成員方法,協(xié)議功戚。
Ivar
Ivar
是表示實例變量的類型娶眷,其實際是一個指向objc_ivar結(jié)構(gòu)體的指針,其定義如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 變量名
char *ivar_type OBJC2_UNAVAILABLE; // 變量類型
int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
objc_property_t
objc_property_t
是表示Objective-C聲明的屬性的類型啸臀,其實際是指向objc_property
結(jié)構(gòu)體的指針届宠,其定義如下:
typedef struct objc_property *objc_property_t;
objc_property_attribute_t
objc_property_attribute_t
定義了屬性的特性(attribute
),它是一個結(jié)構(gòu)體乘粒,定義如下:
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
2.成員變量相關(guān)操作函數(shù)
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
測試個例子
@interface SuperClass : NSObject {
NSString *name;
}
@property (nonatomic, strong) NSString *age;
@end
- (void)testRuntimeIvar {
// SuperClass *p = [SuperClass new];
unsigned int count = 0; //count記錄變量的數(shù)量
// 獲取類的所有成員變量
Ivar *members = class_copyIvarList([SuperClass class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = members[i];
// 取得變量名并轉(zhuǎn)成字符串類型
const char *memberName = ivar_getName(ivar);
NSLog(@"變量名 = %s",memberName);
}
// 獲取類的所有成員屬性
objc_property_t *properties =class_copyPropertyList([SuperClass class], &count);
for (int i = 0; i<count; i++)
{
objc_property_t property = properties[i];
const char* char_f =property_getName(property);
NSString *propertyName = [NSString stringWithUTF8String:char_f];
NSLog(@"屬性名 = %@",propertyName);
}
free(members);
free(properties);
}
打印結(jié)果
變量名 = _name
變量名 = _age
屬性名 = age
補充:我也想測試class_addIvar這個方法
class_addIvar([SuperClass class], "_test", sizeof(id), log2(sizeof(id)), "@")
但失敗了豌注,我看了一下文檔@note This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
好像這個方法必須寫在objc_allocateClassPair
和objc_registerClassPair
之間才有效。
3.屬性操作函數(shù)
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 為類添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
這一種方法也是針對ivar來操作的灯萍,不過它只操作那些property的值轧铁,包括擴展中的property。
/**
* 添加property
*/
objc_property_attribute_t attribute1 = {"T", "@\"NSString\""};
objc_property_attribute_t attribute2 = {"C", ""};
objc_property_attribute_t attribute3 = {"N", ""};
objc_property_attribute_t attribute4 = {"V", "_lcg"};
objc_property_attribute_t attributesList[] = {attribute1, attribute2, attribute3, attribute4};
if(class_addProperty([SuperClass class], "lcg", attributesList, 4)) {
NSLog(@"add property success!");
}
else {
NSLog(@"add property failure!");
}
/**
* 打印property的name和property_attribute_t
*/
unsigned int outCount;
objc_property_t *propertyList = class_copyPropertyList([SuperClass class], &outCount);
for (unsigned int i = 0; i < outCount; i++) {
objc_property_t property = propertyList[i];
const char *propertyName = property_getName(property);
const char *attribute = property_getAttributes(property);
NSLog(@"propertyName: %s, attribute: %s", propertyName, attribute);
unsigned int attributeCount;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &attributeCount);
for (unsigned int j = 0; j < attributeCount; j++) {
objc_property_attribute_t attribute = attributeList[j];
const char *name = attribute.name;
const char *value = attribute.value;
NSLog(@"attribute name: %s, value: %s", name, value);
}
free(attributeList);
}
free(propertyList);
上面代碼有幾個知識點需要說一下:
(1) 其中property_attribute的相關(guān)內(nèi)容需要說明下旦棉。
property_attribute為T@”NSString”,&,N,V_exprice時:
T 是固定的齿风,放在第一個
@”NSString” 代表這個property是一個字符串對象
& 代表強引用药薯,其中與之并列的是:’C’代表Copy,’&’代表強引用救斑,’W’表示weak童本,assign為空,默認為assign脸候。
N 區(qū)分的nonatomic和atomic穷娱,默認為atomic,atomic為空运沦,’N’代表是nonatomic
V_exprice V代表變量泵额,后面緊跟著的是成員變量名,代表這個property的成員變量名為_exprice携添。
property_attribute為T@”NSNumber”,R,N,V_yearsOld時:
T 是固定的嫁盲,放在第一個
@”NSNumber” 代表這個property是一個NSNumber對象
R 代表readOnly屬性,readwrite時為空
N 區(qū)分的nonatomic和atomic薪寓,默認為atomic亡资,atomic為空澜共,’N’代表是nonatomic
V_yearsOld V代表變量向叉,后面緊跟著的是成員變量名,代表這個property的成員變量名為_yearsOld嗦董。
使用例子參考:http://www.tuicool.com/articles/aY3Ujii官方參考:Property Type
(2) 添加property母谎,property_attribute_t是一個結(jié)構(gòu)體,沒有具體創(chuàng)建的方法京革,我們就只能使用{}這樣結(jié)構(gòu)體直接賦值過去奇唤。而且,添加property成功之后匹摇,它并不會生成實例屬性咬扇、setter方法和getter方法。如果要真正調(diào)用的話廊勃,還需要我們自己添加對應(yīng)的setter和getter方法懈贺。詳情使用請見:http://blog.csdn.net/meegomeego/article/details/18356169
4.協(xié)議相關(guān)操作
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
測試例子
//添加協(xié)議
Protocol *p = @protocol(StudentDataSource);
if(class_addProtocol([SuperClass class], p)) {
NSLog(@"添加協(xié)議成功!");
}
else {
NSLog(@"添加協(xié)議失敗!");
}
//判斷是否實現(xiàn)了指定的協(xié)議
if(class_conformsToProtocol([SuperClass class], p)) {
NSLog(@"遵循 %s協(xié)議", protocol_getName(p));
}
else {
NSLog(@"不遵循 %s協(xié)議", protocol_getName(p));
}
//獲取類的協(xié)議列表
unsigned int outCount;
Protocol * __unsafe_unretained *protocolList = class_copyProtocolList([SuperClass class], &outCount);
for (unsigned int i = 0; i < outCount; i++) {
Protocol *protocol = protocolList[i];
const char *name = protocol_getName(protocol);
NSLog(@"%s", name);
}
free(protocolList);
注意:這里我們需要先聲明一個協(xié)議
@protocol StudentDataSource <NSObject>
@end
5.動態(tài)創(chuàng)建類和對象
動態(tài)創(chuàng)建類涉及的函數(shù)
// 創(chuàng)建一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 銷毀一個類及其相關(guān)聯(lián)的類
void objc_disposeClassPair ( Class cls );
// 在應(yīng)用中注冊由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls );
注意:objc_disposeClassPair只能銷毀由objc_allocateClassPair創(chuàng)建的類,當有實例存在或者它的子類存在時坡垫,調(diào)用這個函數(shù)會拋出異常梭灿。
測試例子:
Class cls = objc_allocateClassPair([NSObject class], "Teacher", 0);
//添加成員變量,只能在運行時創(chuàng)建類添加冰悠,并且是在objc_allocateClassPair與objc_registerClassPair之間
if(class_addIvar(cls, "_level", sizeof(id), log2(sizeof(id)), "@\"NSString\"")) {
NSLog(@"添加_level成員變量成功");
}
else {
NSLog(@"添加_level成員變量失敗");
}
objc_registerClassPair(cls);
/**
* 當有實例存在不能銷毀類堡妒,所以講代碼放到里面
*/
{
//創(chuàng)建對象
SuperClass *p = [[cls alloc] init];
NSLog(@"%@", [p class]);
//設(shè)置值
[p setValue:@"高級講師" forKey:@"level"];
NSString *level = [p valueForKey:@"level"];
NSLog(@"level: %@", level);
}
//銷毀類,當有實例存在的時候是不能銷毀類
objc_disposeClassPair(cls);
6.實例操作相關(guān)函數(shù)
// 返回指定對象的一份拷貝
id object_copy ( id obj, size_t size );
// 釋放指定對象占用的內(nèi)存
id object_dispose ( id obj );
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對象分配的任何額外字節(jié)的指針
void * object_getIndexedIvars ( id obj );
// 返回對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設(shè)置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
// 返回給定對象的類名
const char * object_getClassName ( id obj );
// 返回對象的類
Class object_getClass ( id obj );
// 設(shè)置對象的類
Class object_setClass ( id obj, Class cls );
- 有這樣一種場景溉卓,假設(shè)我們有類A和類B皮迟,且類B是類A的子類搬泥。類B通過添加一些額外的屬性來擴展類A。現(xiàn)在我們創(chuàng)建了一個A類的實例對象伏尼,并希望在運行時將這個對象轉(zhuǎn)換為B類的實例對象佑钾,這樣可以添加數(shù)據(jù)到B類的屬性中。這種情況下烦粒,我們沒有辦法直接轉(zhuǎn)換休溶,因為B類的實例會比A類的實例更大,沒有足夠的空間來放置對象扰她。
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
- 如果實例變量的Ivar已經(jīng)知道兽掰,那么調(diào)用object_getIvar會比object_getInstanceVariable函數(shù)快,相同情況下徒役,object_setIvar也比object_setInstanceVariable快孽尽。
7.獲取類的定義
// 獲取已注冊的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );
// 創(chuàng)建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );
objc_getClassList和objc_copyClassList都是獲取所有已注冊的類;而objc_lookUpClass獲取指定的類忧勿,如果沒有注冊則返回nil杉女;objc_getRequiredClass也是獲取指定的類,不過如果這個類不存則鸳吸,則會崩潰熏挎;objc_getMetaClass專門用來獲取類的元類,每個類都有一個有效并且唯一的元類晌砾,如果這個類沒有注冊則返回nil坎拐。
/**
* 第一種獲取所有注冊的類
*/
Class *bufferClass;
int numClasses;
numClasses = objc_getClassList(NULL, 0);
if(numClasses > 0) {
bufferClass = (Class *)malloc(sizeof(Class)*numClasses);
numClasses = objc_getClassList(bufferClass, numClasses);
NSLog(@"numer of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = bufferClass[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(bufferClass);
}
/**
* 第二種獲取所有注冊的類
*/
unsigned int outCount;
Class *classLiset = objc_copyClassList(&outCount);
for (unsigned int i = 0; i < outCount; i++) {
Class cls = classLiset[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classLiset);
小結(jié):通過runtime中的定義,我們知道了類與成員變量的數(shù)據(jù)結(jié)構(gòu)养匈,通過這些數(shù)據(jù)結(jié)構(gòu)我們可以知道Objective-C上層的一些實現(xiàn)細節(jié)哼勇,并且也知道了操作這些數(shù)據(jù)結(jié)構(gòu)的相關(guān)API∨缓酰可以更靈活的操作這些數(shù)據(jù)积担。
相關(guān)文章
Runtime之類與對象總結(jié)
Objective-C Runtime 運行時之一:類與對象
Objective-C Runtime 運行時之二:成員變量與屬性