ios-Runtime調用私有方法


  有時在代碼中會有需要調用私有方法的場景,如不想import太多頭文件;想組件設計一些解耦的模塊;查看別人模塊中未暴露的代碼進行分析等。

  在 ios 中調用私有方法有很多種方式,主要是通過Runtime去實現。下面自己也測試一下。

  新建一個Person類,Person.h中不寫代碼,Person.m中如下:

#import "Person.h"

@implementation Person

- (void)eat
{
    NSLog(@"xxx eat====");
}

- (void)eat:(NSString *)str str2:(NSString *)str2 str3:(NSString *)str3
{
    NSLog(@"xxx eat====%@==%@==%@", str, str2, str3);
}

@end

 

【找到該類methodLists里的方法】

  要想調用私有方法,首先要知道類有什么哪些方法。可以通過如下代碼得到方法的一些信息:(不管私有還是公有,只要在該類的methodLists里)

// 獲取實例方法
- (void)getMethods
{
    int outCount = 0;
    Person *p = [Person new];
    Method *methods = class_copyMethodList([p class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        NSLog(@"=============%d", i);
        // 獲取方法名
        Method method = methods[i];
        SEL methodName = method_getName(method);
        NSLog(@"方法名= %@", NSStringFromSelector(methodName));
        
        // 獲取參數
        char argInfo[512] = {};
        unsigned int argCount = method_getNumberOfArguments(method);
        for (int j = 0; j < argCount; j ++) {
            // 參數類型
            method_getArgumentType(method, j, argInfo, 512);
            NSLog(@"參數類型= %s", argInfo);
            memset(argInfo, '\0', strlen(argInfo));
        }
        
        // 獲取方法返回值類型
        char retType[512] = {};
        method_getReturnType(method, retType, 512);
        NSLog(@"返回類型值類型= %s", retType);
    }
    free(methods);
}

  上面代碼使用runtime獲取一些方法的信息:方法名,參數對應的類型,返回值類型。上面這個方法打印結果如下:

=============0
方法名= eat
參數類型= @
參數類型= :
返回類型值類型= v
=============1
方法名= eat:str2:str3:
參數類型= @
參數類型= :
參數類型= @
參數類型= @
參數類型= @
返回類型值類型= v

  打印的類型是一些符號,不知道這是什么鬼,但其實這是蘋果的類型編碼。它對照的含義如下:

v    A void —— (為空)
@    An object (whether statically typed or typed id) —— (id類型)
:    A method selector (SEL) —— (方法名)

  上面打印的方法信息中 eat 方法也有兩個參數,實際每個方法都有兩個隱藏參數。(_cmd是當前方法編號)

【調用私有方法】

  調用私有方法有多種方式,但其實最終都大同小異。如下:

  1. 使用  performSelector  下面2和3行結果一樣。這樣比使用對象直接調用好處是編譯器不會報錯,也不用方法暴露頭文件。

1 Person *p = [Person new];
2 [p performSelector:@selector(eat)]; // log: xxx eat====
3 [p performSelector:NSSelectorFromString(@"eat")]; // log: xxx eat====

  2. 使用 objc_msgSend ,對比上面 objc_msgSend 好處是傳遞多個參數時更為方便。objc_msgSend深入學習

Person *p = [Person new]; // 需要引用 Person.h 頭文件
objc_msgSend(p, @selector(eat:str2:str3:), @"1", @"2", @"3"); // log: eat====1==2==3

  3. 利用函數實現IMP,IMP類型結構與objc_msgSend底層是同一類型,與2中實現無差別

Person *p = [Person new];
IMP imp = [p methodForSelector:@selector(eat)];
void (* tempFunc)(id target, SEL) = (void *)imp;
tempFunc(p, @selector(eat)); // log: xxx eat====

  4. 使用類對象發送消息,上面3種方式都需要引用 Person.h 頭文件,這里類對象進行調用可以解決這個問題。

Class pClassObj = NSClassFromString(@"Person");
objc_msgSend([pClassObj new], @selector(eat));

【類對象】

  進入 objc.h 中查看 Class 與 Object 結構定義。每個 objc_object 下都有一個 isa 指針指向這個對象所屬的 objc_class。

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

 

  再看一下 objc_class 定義的結構。它里面只有一個 isa 指針,下面那些屬性在 objc2 中已經不可用了。

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

  它里面的 isa 指向的是 MetaClass (元類)。Class 和 MetaClass :

  • 當我們對一個實例發送消息時(-開頭的方法),會在該 instance 對應的類的 methodLists 里查找。
  • 當我們對一個類發送消息時(+開頭的方法),會在該類的 MetaClass 的 methodLists 里查找。

  

  • 每個 Class 都有一個 isa 指針指向一個唯一的 Meta Class
  • 每一個 Meta Class 的 isa 指針都指向最上層的 Meta Class,即 NSObject 的 MetaClass,而最上層的 MetaClass 的 isa 指針又指向自己

  獲取類對象

[NSObject class];

  獲取元類對象

Class class = [NSObject class];
Class metaClass = objc_getClass(class);

 

【實例對象成員變量】

  上面可以知道,實例對象的方法存放在它對應類對象 (class) 的 methodsList 中,類方法存放它對應的元類 (metaClass) 的 methodsList 中。

  一個 new 出來的對象除了它的方法,它還有成員變量。

  • 成員變量是存放在實例對象之中

  假如有一個 Person 對象,這個實例對象結構體中存放信息應該是這樣,即成員變量存放在結構體,方法信息通過 isa 去找相應的類對象。

struct Student_IMPL {
    Class isa;
    int _age;
    int _height;
};

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM