天天看點

Runtime筆記(五)—— super的本質

Runtime系列文章

Runtime筆記(一)—— isa的深入體會(蘋果對isa的優化)

Runtime筆記(二)—— Class結構的深入分析

Runtime筆記(三)—— OC Class的方法緩存cache_t

Runtime筆記(四)—— 刨根問底消息機制

Runtime筆記(五)—— super的本質

[Runtime筆記(六)—— Runtime的應用…待續]-()

[Runtime筆記(七)—— Runtime的API…待續]-()

Runtime筆記(八)—— 記一道變态的runtime面試題

從面試題出發

請看如下代碼

****************************CLPerson.h
@interface CLPerson : NSObject

@end

****************************CLStudent.h
@interface CLStudent : CLPerson

@end

****************************CLStudent.m
@implementation CLStudent
- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"[self class] = %@",[self class]);
        NSLog(@"[self superclass] = %@",[self superclass]);
        NSLog(@"[super class] = %@",[super class]);
        NSLog(@"[super superclass] = %@",[super superclass]);
    }
    return self;
}
@end

****************************main.m
int main(int argc, char * argv[]) {
    @autoreleasepool {

        [[CLStudent alloc] init];

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
           

請問調用

[[CLStudent alloc] init];

會有什麼列印結果。

CLStudent

[self class]

—— 這個應該沒有什麼疑問,結果應該是

[self class] = CLStudent

[self superclass]

—— 這個應該沒有什麼疑問,結果應該是

[self superclass] = CLPerson

[super class]

—— 我們重寫父類方法的時候,如果需要執行父類方法的邏輯,通常會加一句

[super 方法名]

,那麼

super

不是指向父類的指針呢,如果是的話,這裡的列印結果應該是

[super class] = CLPerson

[super superclass]

—— 根據上面的推斷,那麼這裡應該是列印

CLPerson

的父類,也就是

[super superclass] = NSObject

###實際列印結果
2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent
2019-08-12 20:59:23.580700+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson
2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent
2019-08-12 20:59:23.580882+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson
           

通過實際的代碼調試,我們看到

[super class]

[super superclass]

的列印結果不是我們所預期的。怎麼回事呢?

super是什麼

要解釋上面的問題,我們還是要回到問題的本質去探讨。首先我們簡化一下代碼

****************************CLPerson.h
@implementation CLStudent
- (void)run;
@end

****************************CLPerson.m
@implementation CLStudent
- (void)run {
    
    NSLog(@"CLPerson Run");
}
@end
****************************CLStudent.m
@implementation CLStudent
- (void)run {
    
    [super run];
    NSLog(@"CLStudent Run");
}
@end

           

我們在指令行視窗通過指令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc CLStudent.m -o CLStudent.cpp

拿到

CLStudent.m

編譯後的中間代碼

CLStudent.cpp

在其中可以看到

run

方法的底層實作如下

static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {

//[super run];
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("CLStudent"))}, sel_registerName("run"));

//NSLog(@"CLStudent Run");
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}



*************???簡化一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {

//[super run];
   objc_msgSendSuper((__rw_objc_super){
            (id)self,   
            (id)class_getSuperclass(objc_getClass("CLStudent"))
           },   
            @selector(run));

//NSLog(@"CLStudent Run");不重要,不用管
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_CLStudent_5be081_mi_0);
}


*************♥️♥️♥️再精簡一下
static void _I_CLStudent_run(CLStudent * self, SEL _cmd) {
//⚠️⚠️⚠️結構體單獨抽出來
struct __rw_objc_super arg = {
            (id)self,   
            (id)class_getSuperclass(objc_getClass("CLStudent"))
		};

//???[super run];
   objc_msgSendSuper(arg, @selector(run));
}
           

從精簡後的代碼,可以看出來,

[super run];

底層實際上是調用了函數

objc_msgSendSuper(arg, @selector(run));

,首先我們來分析一下它的兩個參數。第二個參數相信不用多解釋了,就是一個方法選擇器

SEL

,重點看一下第一個參數,這是一個結構體

__rw_objc_super

,我們在目前的中間代碼裡面就可以找到其定義,或者,你也可以通過

objc_super

在objc源碼裡面找到它的定義,如下所示

//♥️♥️♥️C++中間代碼裡的定義
struct __rw_objc_super { 
	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

//⚠️⚠️⚠️objc源碼中的定義
/// Specifies the superclass of an instance. 
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 */
};
           

該結構題裡面其實就兩個成員變量

  • id receiver;

    —— 消息接受者,其實實參傳遞的就是

    self

    ,也就是

    CLStudent

    的執行個體對象
  • Class super_class;

    —— 父類,通過中間碼裡面該結構體初始化的指派代碼

    (id)class_getSuperclass(objc_getClass("CLStudent")

    可以看出,這個父類就是

    CLStudent

    的父類類對象

    [CLPerson class]

    .

接着我們在看一下

objc_msgSendSuper

裡面究竟幹了什麼事情。根據函數名字,我們可以在

message.h

裡面找到相關的申明

/** 
 * Sends a message with a simple return value to the superclass of an instance of a class.
 * 
 * @param super A pointer to an \c objc_super data structure. Pass values identifying the
 *  context the message was sent to, including the instance of the class that is to receive the
 *  message and the superclass at which to start searching for the method implementation.
 * @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
 * @param ...
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method identified by \e op.
 * 
 * @see objc_msgSend
 */
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif
           

解讀一下注釋裡面對于參數的說明

  • super

    —— 是一個指向結構體指針

    struct objc_super *

    ,它裡面的内容是{

    消息接受者 recv

    消息接受者的父類類對象 [[recv superclass] class]

    },

    objc_msgSendSuper

    會将

    消息接受者的父類類對象

    作為消息查找的起點。
  • op

    —— 消息接受者接受的消息/方法,也就是需要去查找的方法

    為了加深了解,我們對比一下給對象正常發送消息後的查找流程

[obj message]

-> 在

obj

的類對象

cls

查找方法 -> 在

cls

的父類對象

[cls superclass]

查找方法 -> 在更上層的父類對象查找方法 -> … -> 在根類類對象

NSObject

裡查找方法

[super message]

-> 在

obj

的類對象

cls

查找方法(跳過此步驟) -> (直接從這一步開始)在

cls

的父類對象

[cls superclass]

查找方法 -> 在更上層的父類對象查找方法 -> … -> 在根類類對象

NSObject

裡查找方法

是以對于開頭的面試題,真正的運作邏輯如下

Runtime筆記(五)—— super的本質
Runtime筆記(五)—— super的本質
Runtime筆記(五)—— super的本質
Runtime筆記(五)—— super的本質

小結

  • 消息接受者:

    CLStudent

    的執行個體對象
  • 最終調用的方法:基類

    NSObject

    -(Class)class

    方法
    2019-08-12 20:59:23.580048+0800 iOS-Runtime[25274:2862267] [self class] = CLStudent
               
  • 消息接受者:仍然是

    CLStudent

    的執行個體對象
  • 最終調用的方法:基類

    NSObject

    -(Class)class

    方法
    2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super class] = CLStudent
               
  • 消息接受者:

    CLStudent

    的執行個體對象
  • 最終調用的方法:基類

    NSObject

    -(Class)superclass

    方法
    2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [self superclass] = CLPerson
               
  • 消息接受者:仍然是

    CLStudent

    的執行個體對象
  • 最終調用的方法:基類

    NSObject

    -(Class)superclass

    方法
    2019-08-12 20:59:23.580797+0800 iOS-Runtime[25274:2862267] [super superclass] = CLPerson
               

因為

-class

-superclass

方法底層實際上裡面是使用了Runtime的API,簡單來說就是如下

- (Class)class
{
    return object_getClass(self);
}

- (Class)superclass
{
    return class_getSuperclass(object_getClass(self));
}
           

是以隻要明白誰是真正的消息接受者,相信最後的列印結果就很容易了解了。

OK,super的本質探讨就到這裡。

Runtime系列文章

Runtime筆記(一)—— isa的深入體會(蘋果對isa的優化)

Runtime筆記(二)—— Class結構的深入分析

Runtime筆記(三)—— OC Class的方法緩存cache_t

Runtime筆記(四)—— 刨根問底消息機制

Runtime筆記(五)—— super的本質

[Runtime筆記(六)—— Runtime的應用…待續]-()

[Runtime筆記(七)—— Runtime的API…待續]-()

Runtime筆記(八)—— 記一道變态的runtime面試題