每日一問09——runtime類的操作

上一章中主要寫到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_allocateClassPairobjc_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 運行時之二:成員變量與屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猬仁,隨后出現(xiàn)的幾起案子帝璧,更是在濱河造成了極大的恐慌,老刑警劉巖逐虚,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聋溜,死亡現(xiàn)場離奇詭異,居然都是意外死亡叭爱,警方通過查閱死者的電腦和手機撮躁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來买雾,“玉大人把曼,你說我怎么就攤上這事杨帽。” “怎么了嗤军?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵注盈,是天一觀的道長。 經(jīng)常有香客問我叙赚,道長老客,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任震叮,我火速辦了婚禮胧砰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苇瓣。我一直安慰自己尉间,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布击罪。 她就那樣靜靜地躺著哲嘲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪媳禁。 梳的紋絲不亂的頭發(fā)上眠副,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音损话,去河邊找鬼侦啸。 笑死槽唾,一個胖子當著我的面吹牛丧枪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庞萍,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼拧烦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钝计?” 一聲冷哼從身側(cè)響起恋博,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎私恬,沒想到半個月后债沮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡本鸣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年疫衩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荣德。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡闷煤,死狀恐怖童芹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鲤拿,我是刑警寧澤假褪,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站近顷,受9級特大地震影響生音,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窒升,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一久锥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧异剥,春花似錦瑟由、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至督怜,卻和暖如春殴瘦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背号杠。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工蚪腋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姨蟋。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓屉凯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眼溶。 傳聞我的和親對象是個殘疾皇子悠砚,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,721評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言堂飞,那么這個「動態(tài)」表現(xiàn)在哪呢灌旧?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,195評論 0 7
  • 第42周回顧 計劃內(nèi)任務(wù)完成情況 整體說明(1) 數(shù)據(jù)說明工作部分9件任務(wù)完成了7件,有1件在進展中绰筛,另外1件任務(wù)...
    易仁小生在進化閱讀 455評論 0 1
  • 01 人的一切痛苦皆是對自己無能的憤怒枢泰,這是王小波的一句話,我不記得從哪里看來的了铝噩,慶幸我也有那樣的痛苦衡蚂,倒推回去...
    不鬧海就鬧心的哪吒閱讀 453評論 2 19
  • 臨近開學,最后的寒假過完了。這個假期見了好久不見的好朋友讳窟,喝了好久不喝的酒让歼,多上了兩年大學,感觸真的好深丽啡。 原本大...
    遇見不一樣的自己閱讀 268評論 2 0