OC基础

runtime 打印 SDK 的私有类信息

2017-09-09  本文已影响278人  yehot

更新一:❗️ runtime 打印私有类的属性、方法、遵守协议
更新二:❗️❗️一行 LLDB 命令打印一个类的属性、方法

一、runtime 获取 SDK 中的全部类名

近期的一个项目里,我们自己开发的埋点模块和友盟 SDK 在兼容上发生了点问题,定位问题的过程中,想看看友盟 SDK 里有哪些类,但是友盟 SDK 只提供了 3 个 public header:

uimeng-sdk

办法其实也很简单,用 runtime 很容易 get 到当前项目里都有哪些类文件被编译进来了,即使打包成 .a .framework 的 SDK 也不例外

#import <objc/runtime.h>

- (void)showAllClassNameInProject {
    int allClasses = objc_getClassList(NULL,0);
    Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * allClasses);
    allClasses = objc_getClassList(classes, allClasses);
    
    for (int i = 0; i < allClasses; i++) {
        Class clazz = classes[i];
        NSLog(@"当前项目中全部 class: %@", NSStringFromClass(clazz));
    }
    free(classes);
}

效果如下:

2017-09-09 14:42:20.248  当前项目中全部 class WKObject
2017-09-09 14:42:20.248  当前项目中全部 class WKNSURLRequest
2017-09-09 14:42:20.249  当前项目中全部 class WKNSURLAuthenticationChallenge
2017-09-09 14:42:20.249  当前项目中全部 class WKNSURL
2017-09-09 14:42:20.249  当前项目中全部 class WKNSString
2017-09-09 14:42:20.249  当前项目中全部 class WKNSError
2017-09-09 14:42:20.249  当前项目中全部 class JSExport
2017-09-09 14:42:20.249  当前项目中全部 class NSLeafProxy
2017-09-09 14:42:20.249  当前项目中全部 class NSProxy
2017-09-09 14:42:20.250  当前项目中全部 class _UITargetedProxy
2017-09-09 14:42:20.250  当前项目中全部 class _UIViewServiceReplyControlTrampoline
2017-09-09 14:42:20.250  当前项目中全部 class _UIViewServiceReplyAwaitingTrampoline
2017-09-09 14:42:20.290  当前项目中全部 class _UIViewServiceUIBehaviorProxy
2017-09-09 14:42:20.290  当前项目中全部 class _UIViewServiceImplicitAnimationDecodingProxy
2017-09-09 14:42:20.290  当前项目中全部 class _UIViewServiceImplicitAnimationEncodingProxy
……
// 以下省略 5000 多行

这就有点尴尬了,把项目中导入的系统的 UIKit.framework Foundation.framework 都打出来了,那我们再排除下 UI_UINS 打头的类:

   Class clazz = classes[i];
   NSString *className = NSStringFromClass(clazz);
   if (![className hasPrefix:@"UI"] && ![className hasPrefix:@"_UI"] && ![className hasPrefix:@"NS"]) {
       NSLog(@"当前项目中全部 class: %@", className);
   }

结果如下:

2017-09-09 15:06:35.995  当前项目中全部 class: WKObject
2017-09-09 15:06:35.995  当前项目中全部 class: WKNSURLRequest
2017-09-09 15:06:35.995  当前项目中全部 class: WKNSURLAuthenticationChallenge
2017-09-09 15:06:35.995  当前项目中全部 class: WKNSURL
2017-09-09 15:06:35.996  当前项目中全部 class: WKNSString
2017-09-09 15:06:35.996  当前项目中全部 class: WKNSError
2017-09-09 15:06:35.996  当前项目中全部 class: JSExport
2017-09-09 15:06:35.996  当前项目中全部 class: WebMainThreadInvoker
2017-09-09 15:06:35.996  当前项目中全部 class: BSZeroingWeakReferenceProxy
2017-09-09 15:06:35.996  当前项目中全部 class: __NSGenericDeallocHandler
// 省略 2000 多行

wtf, 系统框架类名前缀也是五花八门,if else 的排除是不可能了,得换个思路。

那么,到底怎么区分一个类是系统的,还是用户自定义的呢?查了一下,还真没有办法区分,因为这个问题根本就不成立:
所谓系统的类,说白了就是苹果提供的 framework 里的类,也都是苹果自己自定义的。

中文搜不到,找了一圈在 stackoverflow 找到了这个:How to judge a class whether System's or Custom's?

简单点说就是,虽然区分不了什么系统不系统的类,但是 Framework 是分动态库和静态库的。而苹果开发的 Framework 都是以动态库的形式参与编译打包,只链接到 app 里,不会打包到 App 的 Bundle 中。但所有除了苹果以外的开发者,都只能以静态库 Framework 打包 SDK(上架),代码都会被打包到 App 的 Bundle 中,那么问题就简单了:

- (void)showCustomClassNameOnly {
    int allClasses = objc_getClassList(NULL,0);
    Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * allClasses);
    allClasses = objc_getClassList(classes, allClasses);
    
    for (int i = 0; i < allClasses; i++) {
        Class clazz = classes[i];
        NSBundle *b = [NSBundle bundleForClass:clazz];
        if (b == [NSBundle mainBundle]) {
            NSLog(@"自定义 class: %@", NSStringFromClass(clazz));
        }
    }
    free(classes);
}

输出结果如下:

2017-09-09 14:43:27.291  自定义 class: UMTMemoryBuffer
2017-09-09 14:43:27.291  自定义 class: UMTProtocolUtil
2017-09-09 14:43:27.292  自定义 class: UMTBinaryProtocol
2017-09-09 14:43:27.292  自定义 class: UMTBinaryProtocolFactory
2017-09-09 14:43:27.292  自定义 class: MobClickApp
2017-09-09 14:43:27.292  自定义 class: MobClickUtility
2017-09-09 14:43:27.292  自定义 class: UMEventMgr
2017-09-09 14:43:27.293  自定义 class: MobClickSocialAnalytics
2017-09-09 14:43:27.293  自定义 class: MobClickSocialWeibo
2017-09-09 14:43:27.293  自定义 class: MobClick
2017-09-09 14:43:27.293  自定义 class: UMAnalyticsConfig
2017-09-09 14:43:27.294  自定义 class: UMANDeflated
2017-09-09 14:43:27.294  自定义 class: UmengUncaughtExceptionHandler
2017-09-09 14:43:27.330  自定义 class: MobClickEvent
2017-09-09 14:43:27.330  自定义 class: MobClickInternal
2017-09-09 14:43:27.330  自定义 class: UMANUtil
2017-09-09 14:43:27.331  自定义 class: MobClickConfig
2017-09-09 14:43:27.331  自定义 class: MobClickSession
2017-09-09 14:43:27.332  自定义 class: umeng_envelopeConstants
2017-09-09 14:43:27.332  自定义 class: UMEnvelope
2017-09-09 14:43:27.332  自定义 class: MobClickGameAnalytics
2017-09-09 14:43:27.333  自定义 class: MobClickLocation
// 省略

这样,排除 Demo 项目里的 AppDelegateViewController 这两个类,我们就能看到友盟 SDK 里的全部类名了,搞定!

Note

1、在 FLEX 源码中,发现了更简单的实现方式:

- (void)flexShowClassNames {
    unsigned int classNamesCount = 0;
    
    // 用 executablePath 获取当前 app image
    NSString *appImage = [NSBundle mainBundle].executablePath;

    // objc_copyClassNamesForImage 获取到的是 image 下的类,直接排除了系统的类
    const char **classNames = objc_copyClassNamesForImage([appImage UTF8String], &classNamesCount);
    if (classNames) {
        NSMutableArray *classNameStrings = [NSMutableArray array];
        for (unsigned int i = 0; i < classNamesCount; i++) {
            const char *className = classNames[i];
            NSString *classNameString = [NSString stringWithUTF8String:className];
            [classNameStrings addObject:classNameString];
        }
        NSArray *allClassNames = [classNameStrings sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
        NSLog(@"---%@", allClassNames);
        free(classNames);
    }
}

2、objc_getClassList 和 objc_copyClassList 用法区别

objc_getClassList 需要两次才能获取,较麻烦

    // int objc_getClassList(Class *buffer, int bufferCount) 获取已经注册的类
    // 第一个参数 buffer :已分配好内存空间的数组,
    // 第二个参数 bufferCount :数组中可存放元素的个数,返回值是注册的类的总数
    int allClasses = objc_getClassList(NULL,0);
    Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * allClasses);
    allClasses = objc_getClassList(classes, allClasses);

objc_copyClassList 代码相对简单:

    // Class *objc_copyClassList(unsigned int *outCount)
    // 该函数的作用是获取所有已注册的类,和上述函数 objc_getClassList 参数传入 NULL 和  0 时效果一样
    unsigned int outCount;
    Class *classes = objc_copyClassList(&outCount);

二、使用 runtime 打印一个类的全部属性、方法

在获取到 SDK 私有类的文件名后,还需要进一步获取类的属性、方法、遵守的协议。

简单的调用几个 runtime 的 API 就可以做到 ,代码参见 Demo
,效果如下:

ivar[0] ----  B : _observerRegistered
ivar[1] ----  B : _appInBackGround
ivar[2] ----  B : _appBeKilling
ivar[3] ----  B : _appCrashed
ivar[4] ----  i : _sessionStatus
ivar[5] ----  d : _lastLaunchTime
instance method[0] ---- setObserverRegistered:
instance method[1] ---- setAppCrashed:
instance method[2] ---- setAppBeKilling:
instance method[3] ---- setSessionStatus:
instance method[4] ---- setLastLaunchTime:
instance method[5] ---- beginSessionTime
instance method[6] ---- endSessionTime
instance method[7] ---- appActivate:
instance method[8] ---- observerRegistered
instance method[9] ---- appInactivate:
instance method[10] ---- ensureSessionLaunch
instance method[11] ---- appInBackGround
instance method[12] ---- setAppInBackGround:
instance method[13] ---- appBeKilling
instance method[14] ---- appCrashed
instance method[15] ---- sessionStatus
instance method[16] ---- lastLaunchTime
class method[0] ---- profileSignInPUID:
class method[1] ---- profileSignInPUID:withProvider:
class method[2] ---- profileSignOff
class method[3] ---- signInPUID:provider:
class method[4] ---- startWithAppkey:reportPolicy:channelId:
class method[5] ---- startWithAppkey:
class method[6] ---- sharedInstance

Demo 戳这里

三、使用 LLDB 命令打印一个类的全部属性、方法

在已经获取到类名的情况下,其实使用简单的一行 LLDB 命令,就可以更方便的获取到一个类的属性、方法,不用写一行代码,而且打印内容更便于阅读:

在运行着的项目任意位置打断点,在控制台 po 需要打印的类名 + _shortMethodDescription:,eg:

 po [MobClickSession _shortMethodDescription]

输出:

<MobClickSession: 0x108788af8>:
in MobClickSession:
    Class Methods:
        + (void) profileSignInPUID:(id)arg1; (0x108755da6)
        + (void) profileSignInPUID:(id)arg1 withProvider:(id)arg2; (0x108755dc6)
        + (void) profileSignOff; (0x108755e21)
        + (void) signInPUID:(id)arg1 provider:(id)arg2; (0x108755c74)
        + (void) startWithAppkey:(id)arg1 reportPolicy:(int)arg2 channelId:(id)arg3; (0x1087563b1)
        + (void) startWithAppkey:(id)arg1; (0x108756994)
        + (id) sharedInstance; (0x108755b76)
    Properties:
        @property (nonatomic) BOOL observerRegistered;  (@synthesize observerRegistered = _observerRegistered;)
        @property (nonatomic) BOOL appInBackGround;  (@synthesize appInBackGround = _appInBackGround;)
        @property (nonatomic) BOOL appBeKilling;  (@synthesize appBeKilling = _appBeKilling;)
        @property (nonatomic) BOOL appCrashed;  (@synthesize appCrashed = _appCrashed;)
        @property (nonatomic) int sessionStatus;  (@synthesize sessionStatus = _sessionStatus;)
        @property (nonatomic) double lastLaunchTime;  (@synthesize lastLaunchTime = _lastLaunchTime;)
    Instance Methods:
        - (void) setObserverRegistered:(BOOL)arg1; (0x1087569be)
        - (void) setAppCrashed:(BOOL)arg1; (0x108756a1e)
        - (void) setAppBeKilling:(BOOL)arg1; (0x1087569fe)
        - (void) setSessionStatus:(int)arg1; (0x108756a3e)
        - (void) setLastLaunchTime:(double)arg1; (0x108756a60)
        - (void) beginSessionTime; (0x108755fd4)
        - (double) endSessionTime; (0x108756340)
        - (void) appActivate:(id)arg1; (0x108755e41)
        - (BOOL) observerRegistered; (0x1087569ae)
        - (void) appInactivate:(id)arg1; (0x108755ea0)
        - (void) ensureSessionLaunch; (0x10875638e)
        - (BOOL) appInBackGround; (0x1087569ce)
        - (void) setAppInBackGround:(BOOL)arg1; (0x1087569de)
        - (BOOL) appBeKilling; (0x1087569ee)
        - (BOOL) appCrashed; (0x108756a0e)
        - (int) sessionStatus; (0x108756a2e)
        - (double) lastLaunchTime; (0x108756a4e)
(NSObject ...)
上一篇 下一篇

猜你喜欢

热点阅读