前言
這篇文章專注打印一個(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
- 該方法不能獲取到類方法照捡。
- 方法列表包括屬性的
setter
和getter
方法颅湘。 - 盡管該方法寫在
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_copyIvarList
和class_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ǔ)全俺叭。
參考文章: