runtime:打印類屬性、方法信息

前言

這篇文章專注打印一個(gè)類的方法,屬性等信息例获。以及實(shí)踐過(guò)程中遇到的問(wèn)題的思考和探索。

正文

分條列舉說(shuō)明曹仗。

測(cè)試類 Person 頭文件定義:

#import <Foundation/Foundation.h>
#include <objc/message.h>

NS_ASSUME_NONNULL_BEGIN

@protocol HumanInfo <NSObject>
-(void)setSomeOneCountry;
@end

@interface Person : NSObject<HumanInfo>

/// 類屬性
@property(nonatomic, strong, class) NSString *address;
/// 實(shí)例屬性
@property(nonatomic, strong) NSString *phone;

/// 類方法
+(NSInteger)howOldAreYou;
/// 實(shí)例方法
-(void)whatName:(NSString * _Nullable)name;
-(NSString *_Nullable)fullName;

// MARK:- runtime
/// 獲取協(xié)議方法
-(void)getProtocolMethods;
/// 屬性的獲取榨汤。并且動(dòng)態(tài)修改屬性信息
-(void)getIvarAndChange;
/// 獲取實(shí)例方法
-(void)getInstanceMethod;
/// 獲取類方法
-(void)getClassMethod;

@end

NS_ASSUME_NONNULL_END

獲取協(xié)議方法

/// 獲取協(xié)議及協(xié)議方法
- (void)getProtocolMethods {
    unsigned int count;
    // 獲取協(xié)議列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Protocol *pro = protocolList[i];
        const char *protoclName = protocol_getName(pro);
        NSLog(@"協(xié)議名稱:%@", [NSString stringWithUTF8String:protoclName]);
        // 獲取協(xié)議方法名
        NSArray *names = [self methodListWithProtocol:pro];
        for (NSString *name in names) {
            NSLog(@"%@ 協(xié)議方法: %@", [NSString stringWithUTF8String:protoclName], name);
        }
    }
}

/// 獲取協(xié)議方法
-(NSArray<NSString *>*)methodListWithProtocol: (Protocol *)protocol {
    unsigned int count = 0;
    NSMutableArray<NSString *> *methodList = @[].mutableCopy;
    struct objc_method_description *methods = protocol_copyMethodDescriptionList(protocol, YES, YES, &count);
    for (unsigned int i = 0; i < count; i++) {
        struct objc_method_description method = methods[i];
        NSString *name = NSStringFromSelector(method.name);
        [methodList addObject:name];
    }
    free(methods);
    return methodList;
}

/// 協(xié)議方法
- (void)setSomeOneCountry {
    NSLog(@"設(shè)置所屬國(guó)家");
}

測(cè)試:

Person *p = [[Person alloc] init];
[p getProtocolMethods];

結(jié)果:

 協(xié)議名稱:HumanInfo
 HumanInfo 協(xié)議方法: setSomeOneCountry
  • 輸入 protocol_ ,根據(jù)快捷提示怎茫,可以看到很多為協(xié)議專門準(zhǔn)備的方法收壕,包括添加屬性、獲取屬性列表、方法列表等蜜宪。
  • __unsafe_unretained Protocol **protocolList 為什么要這么定義旬渠,通過(guò)查看 class_copyProtocolList 方法即可:
    OBJC_EXPORT Protocol * __unsafe_unretained _Nonnull * _Nullable 
    class_copyProtocolList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    

獲取屬性列表并動(dòng)態(tài)更改屬性值

/// 屬性的獲取。并且動(dòng)態(tài)修改屬性信息
-(void)getIvarAndChange {
    NSLog(@"修改前: %@", self.phone);
    unsigned int count;
    Ivar *vars = class_copyIvarList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar oneVar = vars[i];
        // 獲取名字和類型
        const char *memberName = ivar_getName(oneVar);
        const char *memberType = ivar_getTypeEncoding(oneVar);
        // 一次打印屬性名稱和屬性類型
        NSLog(@"屬性信息  %s: %s", memberName, memberType);
        
        // 修改屬性
        if (strcmp(memberName, "phone") == 0) {
            // 修改前
            NSString *name = (NSString *)object_getIvar(self, oneVar);
            NSLog(@"開始修改手機(jī)屬性  phone: %@", name);
            // 修改后
            object_setIvar(self, oneVar, @"telphoneNumber");
            // 修改后獲取
            NSString *newName = (NSString *)object_getIvar(self, oneVar);
            NSLog(@"修改后的手機(jī)屬性  %@", newName);
        }
    }
    free(vars);
    NSLog(@"方法列表獲取結(jié)束端壳,修改后屬性信息 phone: %@", self.phone);
}

測(cè)試:

[p getIvarAndChange];

結(jié)果:

2019-11-05 17:34:13.838345+0800 CateAndExt[51413:735475] 修改前: (null)
2019-11-05 17:34:13.838493+0800 CateAndExt[51413:735475] 屬性信息  phone: @"NSString"
2019-11-05 17:34:13.838639+0800 CateAndExt[51413:735475] 開始修改手機(jī)屬性  phone: (null)
2019-11-05 17:34:13.838763+0800 CateAndExt[51413:735475] 修改后的手機(jī)屬性  telphoneNumber
2019-11-05 17:34:13.838888+0800 CateAndExt[51413:735475] 屬性信息  _delegate: @"<HumanInfo>"
2019-11-05 17:34:13.838994+0800 CateAndExt[51413:735475] 方法列表獲取結(jié)束告丢,修改后屬性信息 phone: telphoneNumber
  • 不能獲取到類的類屬性。
  • 測(cè)試時(shí)建了 Person 的分類并添加屬性损谦,不能獲取到分類中的屬性信息岖免。
    #import "Person.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person (Man)
    @property(nonatomic, strong) NSString *nickName;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
  • 其他暫時(shí)沒(méi)問(wèn)題。

獲取實(shí)例方法列表

/// 獲取實(shí)例方法
/// 獲取實(shí)例方法
-(void)getInstanceMethod {
    unsigned int count;
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Method oneMethod = methodList[i];
        // 獲取方法名
        NSString *methodName = NSStringFromSelector(method_getName(oneMethod));
        // 獲取返回類型
        char retName[512] = {};
        method_getReturnType(oneMethod, retName, 512);
        // 獲取參數(shù)個(gè)數(shù)
        unsigned int argCount = method_getNumberOfArguments(oneMethod);
        // 方法指針地址
        IMP methodImp = method_getImplementation(oneMethod);
        NSLog(@"MethodName: %@ ?? retName: %s ?? argCount: %u ?? methodImp: %p",methodName, retName, argCount, methodImp);
    }
}

測(cè)試:

[p getInstanceMethod];

結(jié)果:

2019-11-05 18:04:33.361437+0800 CateAndExt[52474:754299] MethodName: getInstanceMethod ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3c100
2019-11-05 18:04:33.361690+0800 CateAndExt[52474:754299] MethodName: methodListWithProtocol: ?? retName: @ ?? argCount: 3 ?? methodImp: 0x10ec3bd90
2019-11-05 18:04:33.361835+0800 CateAndExt[52474:754299] MethodName: setSomeOneCountry ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3bed0
2019-11-05 18:04:33.361971+0800 CateAndExt[52474:754299] MethodName: whatName: ?? retName: v ?? argCount: 3 ?? methodImp: 0x10ec3b900
2019-11-05 18:04:33.362098+0800 CateAndExt[52474:754299] MethodName: getProtocolMethods ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3ba10
2019-11-05 18:04:33.362222+0800 CateAndExt[52474:754299] MethodName: getIvarAndChange ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3bf00
2019-11-05 18:04:33.362369+0800 CateAndExt[52474:754299] MethodName: getClassMethod ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3c2a0
2019-11-05 18:04:33.362543+0800 CateAndExt[52474:754299] MethodName: .cxx_destruct ?? retName: v ?? argCount: 2 ?? methodImp: 0x10ec3c350
2019-11-05 18:04:33.364629+0800 CateAndExt[52474:754299] MethodName: delegate ?? retName: @ ?? argCount: 2 ?? methodImp: 0x10ec3c310
2019-11-05 18:04:33.364777+0800 CateAndExt[52474:754299] MethodName: setDelegate: ?? retName: v ?? argCount: 3 ?? methodImp: 0x10ec3c330
2019-11-05 18:04:33.364899+0800 CateAndExt[52474:754299] MethodName: phone ?? retName: @ ?? argCount: 2 ?? methodImp: 0x10ec3c2b0
2019-11-05 18:04:33.365038+0800 CateAndExt[52474:754299] MethodName: fullName ?? retName: @ ?? argCount: 2 ?? methodImp: 0x10ec3b960
2019-11-05 18:04:33.365172+0800 CateAndExt[52474:754299] MethodName: nickName ?? retName: @ ?? argCount: 2 ?? methodImp: 0x10ec3b6a0
2019-11-05 18:04:33.365297+0800 CateAndExt[52474:754299] MethodName: setNickName: ?? retName: v ?? argCount: 3 ?? methodImp: 0x10ec3b6d0
2019-11-05 18:04:33.365419+0800 CateAndExt[52474:754299] MethodName: setPhone: ?? retName: v ?? argCount: 3 ?? methodImp: 0x10ec3c2d0

  • 該方法不能獲取到類方法照捡。
  • 方法列表包括屬性的 settergetter 方法颅湘。
  • 盡管該方法寫在 Person 中,但是獲取到了分類中的方法列表栗精。
  • 方法返回類型 @ 闯参、 v 分表代表什么意思,為什么參數(shù)個(gè)數(shù)會(huì)比看到的多了兩個(gè)悲立,參考文章 runtime Method精講鹿寨。

獲取類方法

/// 獲取類方法
-(void)getClassMethod {
    unsigned int count;
    Class metaclass = object_getClass([self class]);
    Method *methodList = class_copyMethodList(metaclass, &count);
    for (unsigned int i = 0; i < count; i++) {
        Method oneMethod = methodList[i];
        // 獲取方法名
        NSString *methodName = NSStringFromSelector(method_getName(oneMethod));
        // 獲取返回類型
        char retName[512] = {};
        method_getReturnType(oneMethod, retName, 512);
        // 獲取參數(shù)個(gè)數(shù)
        unsigned int argCount = method_getNumberOfArguments(oneMethod);
        // 方法指針地址
        IMP methodImp = method_getImplementation(oneMethod);
        NSLog(@"類方法MethodName: %@ ?? retName: %s ?? argCount: %u ?? methodImp: %p",methodName, retName, argCount, methodImp);
    }
}

修改分類:

#import "Person.h"

NS_ASSUME_NONNULL_BEGIN

@interface Person (Man)
@property(nonatomic, strong) NSString *nickName;

+(NSInteger)howOldAreYou;
+(NSInteger)howOldAreYouoneMore;


@end

NS_ASSUME_NONNULL_END

測(cè)試:

 [p getClassMethod];

結(jié)果:

2019-11-05 18:34:19.045761+0800 CateAndExt[53499:774413] 類方法MethodName: howOldAreYou ?? retName: q ?? argCount: 2 ?? methodImp: 0x10204e3b0
2019-11-05 18:34:19.045947+0800 CateAndExt[53499:774413] 類方法MethodName: howOldAreYou ?? retName: q ?? argCount: 2 ?? methodImp: 0x10204e5c0
2019-11-05 18:34:19.047406+0800 CateAndExt[53499:774413] 類方法MethodName: howOldAreYouoneMore ?? retName: q ?? argCount: 2 ?? methodImp: 0x10204e3d0
2019-11-05 18:34:19.047614+0800 CateAndExt[53499:774413] 類方法MethodName: address ?? retName: @ ?? argCount: 2 ?? methodImp: 0x10204e660
2019-11-05 18:34:19.048768+0800 CateAndExt[53499:774413] 類方法MethodName: setAddress: ?? retName: v ?? argCount: 3 ?? methodImp: 0x10204e680
  • 僅僅能獲取類方法,獲取不到實(shí)例方法薪夕。
  • 類方法脚草、以及類屬性,都能獲取到原献。
  • 分類中的類方法也能獲取到馏慨。分類寫了和主類完全一致的方法時(shí),兩個(gè)方法同時(shí)存在姑隅。調(diào)用重復(fù)的方法時(shí)写隶,優(yōu)先調(diào)用子類方法。當(dāng)然讲仰,可以通過(guò)runtime強(qiáng)制調(diào)用主類方法慕趴,這里不在討論。
  • 通過(guò)方法指針可確定叮盘,分配和主類重復(fù)的方法時(shí)實(shí)質(zhì)上是兩個(gè)不同的方法秩贰。

由此聯(lián)想,是否可以通過(guò) Class metaclass = object_getClass([self class]); 獲取類屬性柔吼。

/// 獲取類屬性
-(void)getClassIvar {
    unsigned int count;
    Class metaclass = object_getClass([self class]);
    objc_property_t *properties = class_copyPropertyList(metaclass, &count);
    for (unsigned int i = 0; i < count; i++) {
        objc_property_t oneP = properties[i];
        // 獲取名字和類型
        const char *memberName = property_getName(oneP);
        // 一次打印屬性名稱和屬性類型
        NSLog(@"類屬性信息  %s", memberName);
    }
    free(properties);
}

打印結(jié)果:

2019-11-05 18:34:19.050084+0800 CateAndExt[53499:774413] 類屬性信息  address
  • 使用 class_copyIvarList 不能獲取到類方法毒费,但是使用 class_copyPropertyList 則可以。引發(fā)另一個(gè)問(wèn)題:class_copyIvarListclass_copyPropertyList 有什么區(qū)別愈魏。

以下引用自 class_copyPropertyList與class_copyIvarList區(qū)別:

class_copyPropertyList返回的僅僅是對(duì)象類的屬性(@property申明的屬性)觅玻,而class_copyIvarList返回類的所有屬性和變量(包括在@interface大括號(hào)中聲明的變量)想际。

原作者是寫的測(cè)試函數(shù),我在項(xiàng)目中直接測(cè)試溪厘,和文中結(jié)果不太一樣胡本。

Person 添加屬性

@interface Person : NSObject<HumanInfo>

{
    NSInteger height;
    NSString *workName;
}

/// 類屬性
@property(nonatomic, strong, class) NSString *address;
/// 實(shí)例屬性
@property(nonatomic, strong) NSString *phone;
@property(nonatomic, assign) id <HumanInfo> delegate;

添加一個(gè)新的屬性打印方法:

/// 另一個(gè)獲取屬性的方法
-(void)getProperties {
    unsigned int count;
    unsigned int pCount;
    Ivar *vars = class_copyIvarList([self class], &count);
    objc_property_t *properties = class_copyPropertyList([self class], &pCount);
    
    for (unsigned int i = 0; i < count; i++) {
        Ivar oneVar = vars[i];
        // 獲取名字和類型
        const char *memberName = ivar_getName(oneVar);
        // 一次打印屬性名稱和屬性類型
        NSLog(@"class_copyIvarList 屬性  %s", memberName);
    }
    
    for (unsigned int i = 0; i < pCount; i++) {
        objc_property_t oneP = properties[i];
        // 獲取名字和類型
        const char *memberName = property_getName(oneP);
        // 一次打印屬性名稱和屬性類型
        NSLog(@"class_copyPropertyList 屬性  %s", memberName);
    }
    
    free(vars);
    free(properties);
}

打印結(jié)果:

2019-11-05 18:51:52.544776+0800 CateAndExt[54040:784325] class_copyIvarList 屬性  height
2019-11-05 18:51:52.545474+0800 CateAndExt[54040:784325] class_copyIvarList 屬性  workName
2019-11-05 18:51:52.546048+0800 CateAndExt[54040:784325] class_copyIvarList 屬性  phone
2019-11-05 18:51:52.546394+0800 CateAndExt[54040:784325] class_copyIvarList 屬性  _delegate
2019-11-05 18:51:52.546694+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  nickName
2019-11-05 18:51:52.547136+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  phone
2019-11-05 18:51:52.547538+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  delegate
2019-11-05 18:51:52.547878+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  hash
2019-11-05 18:51:52.548443+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  superclass
2019-11-05 18:51:52.549367+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  description
2019-11-05 18:51:52.549822+0800 CateAndExt[54040:784325] class_copyPropertyList 屬性  debugDescription
  • class_copyIvarList 只能找到當(dāng)前類的所有屬性,包括 @property 修飾的以及大括號(hào)修飾的變量畸悬。
  • class_copyPropertyList 對(duì)于當(dāng)前類只能打印 @property 修飾的屬性侧甫,但是,還能找到父類蹋宦、分類中的屬性披粟。
  • 飾的以及大括號(hào)修飾的變量。即當(dāng)前類所有的屬性冷冗。
  • class_copyPropertyList 能找到類屬性守屉。

后記

雖然這些僅僅是 runtime 的冰山一角,但是關(guān)于方法蒿辙、屬性的查詢拇泛、打印應(yīng)該做了比較詳細(xì)的分析,后續(xù)關(guān)于此類的問(wèn)題思灌,也會(huì)在這里補(bǔ)全俺叭。

參考文章:

class_copyPropertyList與class_copyIvarList區(qū)別

iOS 獲取類的屬性,實(shí)例方法,類方法等

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市习瑰,隨后出現(xiàn)的幾起案子绪颖,更是在濱河造成了極大的恐慌,老刑警劉巖甜奄,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異窃款,居然都是意外死亡课兄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門晨继,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烟阐,“玉大人,你說(shuō)我怎么就攤上這事紊扬⊙亚眩” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵餐屎,是天一觀的道長(zhǎng)檀葛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)腹缩,這世上最難降的妖魔是什么屿聋? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任空扎,我火速辦了婚禮,結(jié)果婚禮上润讥,老公的妹妹穿的比我還像新娘转锈。我一直安慰自己,他們只是感情好楚殿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布撮慨。 她就那樣靜靜地躺著,像睡著了一般脆粥。 火紅的嫁衣襯著肌膚如雪甫煞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天冠绢,我揣著相機(jī)與錄音抚吠,去河邊找鬼。 笑死弟胀,一個(gè)胖子當(dāng)著我的面吹牛楷力,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播孵户,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼萧朝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了夏哭?” 一聲冷哼從身側(cè)響起检柬,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竖配,沒(méi)想到半個(gè)月后何址,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡进胯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年用爪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁镐。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡偎血,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盯漂,到底是詐尸還是另有隱情颇玷,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布就缆,位于F島的核電站帖渠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏违崇。R本人自食惡果不足惜阿弃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一诊霹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渣淳,春花似錦脾还、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至棺蛛,卻和暖如春怔蚌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旁赊。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工桦踊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人终畅。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓籍胯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親离福。 傳聞我的和親對(duì)象是個(gè)殘疾皇子杖狼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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