IOS底層探索方法的本質

方法的本質

1.準備測試代碼:c++

#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
-(void)saySomething;
@end
@implementation ABPerson
-(void)saySomething
{
    NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ABPerson *p = [ABPerson alloc];
        [p saySomething];
        NSLog(@"Hello, World!");
    }
    return 0;
}

複製代碼

2.編譯成.pp文件git

clang -rewrite-objc main.m  -o main.cpp
複製代碼

3.打開main.cpp文件分析: 圖片.png 在入口main方法中,調用了alloc方法和saySomething方法,在產生的c++代碼中,都調用了objc_msgSend這個方法,而且傳了兩個參數,第一個是消息的接受者(id),第二個是方法編號(SEL)。github

消息發送的函數定義以下:markdown

id objc_msgSend(id self, SEL _cmd, ...);
複製代碼

因此他仍是能夠傳更多的參數的架構

4.修改函數,添加兩個參數函數

#import <Foundation/Foundation.h>
@interface ABPerson : NSObject
-(void)saySomething:(NSString *)a b:(NSString *)b;
@end
@implementation ABPerson
-(void)saySomething:(NSString *)a b:(NSString *)b
{
    NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ABPerson *p = [ABPerson alloc];
        [p saySomething:@"A" b:@"B"];
        NSLog(@"Hello, World!");
    }
    return 0;
}
複製代碼

5.從新編譯,並查看函數調用 圖片.pngoop

圖片.png

因此結論就是:方法的本質就是消息的發送測試

使用objc_msgSend調用方法

既然方法調用的本質就是經過objc_msgSend發送消息,那麼就用它來調用方法。ui

1.導入頭文件spa

#import <objc/message.h>
複製代碼

調用方法代碼:

objc_msgSend(p,@selector(saySomething));
複製代碼

圖片.png 若是報錯,配置一下紅框位置,請其設置爲NO 圖片.png

objc_msgSendSuper定義

objc_msgSendSuper定義:

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ ) 複製代碼

objc_msgSendSuper包含的頭兩個參數分別是結構體objc_super和方法編號SEL

結構體objc_super定義:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus) && !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
複製代碼

包含了一個接受者receiver和一個super_class

objc_msgSendSuper使用

1.定義一個ABTeacher繼承ABPerson,並實現doSomething方法

@interface ABTeacher : ABPerson
-(void)doSomething;
@end
@implementation ABTeacher
-(void)doSomething
{
    NSLog(@"%s",__func__);
}
@end
複製代碼
ABTeacher *t = [ABTeacher alloc];
struct objc_super ab_objc_super;
ab_objc_super.receiver = t;
ab_objc_super.super_class = ABTeacher.class;
objc_msgSendSuper(&ab_objc_super,@selector(doSomething));
複製代碼

圖片.png ab_objc_super.super_class是從哪一個類開始查找,若是改爲ABPerson.class,就是從ABPerson查找,若是ABPerson沒實現就拋出異常,也不會再查找ABTeacher

objc_msgSend彙編分析

圖片.png 打開objc源碼找到objc_msgSend是在源碼中彙編實現: 查看arm64架構的

圖片.png

  • cmp p0,:判斷當前消息接收者是否爲0
  • b.le LNilOrTagged:若是支持tagged pointer,走這步
  • b.eq LReturnZero:不支持tagged pointer,走這步,返回0
  • ldr p13, [x0]:將消息接收者isap13
  • GetClassFromIsa_p16 p13, 1, x0:將消息接收者的class存到p16

GetClassFromIsa_p16是如何經過isa找到class的?

圖片.png 圖片.png 由於GetClassFromIsa_p16 p13, 1, x0,因此srcp13也就是isaneeds_auth1執行ExtractISA,ExtractISA就是將isa&ISA_MASK的結果給$0,也就是將拿到的classp16

上面的過程就是經過消息接收者獲取到class

(未完)

補充

protocol分析

代碼準備:

#import <Foundation/Foundation.h>
@protocol ABPersonDelegate <NSObject>
-(void)masterNB;
@end

@interface ABPerson : NSObject<ABPersonDelegate>
-(void)saySomething;
@end

複製代碼
#import "ABPerson.h"
@implementation ABPerson
-(void)masterNB
{
    NSLog(@"%s",__func__);
}
-(void)saySomething
{
    NSLog(@"%s",__func__);
}
@end
複製代碼

圖片.png 圖片.png

  • p/x ABPerson.class:打印Class首地址
  • p (class_data_bits_t *)0x0000000100008868:首地址偏移32個字節拿到bits並將地址強轉成class_data_bits_t類型
  • p $1->data():調用class_data_bits_t中的data()函數
  • p *$2:打印data
  • p $3.protocols() :調用protocols(),獲取protocol列表
  • p $4.list:拿到protocol_list_t首地址
  • p $5.ptr :打印protocol_list_t首地址
  • p *$6 :打印protocol_list_t,發現count=1
  • p $7.list[0]:打印第一個
  • p (protocol_t *)$8:強轉成protocol_t類型

圖片.png

  • p *$9:查看protocol_t結構並找到 mangledNameABPersonDelegate
  • p $10.instanceMethods:獲取實例方法
  • p $11.get(0).big():讀取 method_list_t中的實例方法
相關文章
相關標籤/搜索