Clang -rewrite-objc

NSFish發表於2018-03-25

Objective-C類成員變數深度剖析一文中,作者通過分析Clang生成的LLVM中間碼得出瞭如下結論:

LLVM為每個類的每個成員變數都分配了一個全域性變數,用於儲存該成員變數的偏移值。

結論是對的,但要讀LLVM中間碼,不免令人頭大。一個簡單的辦法是用

Clang -rewrite-objc *.m

將OC程式碼轉換成C++程式碼,然後分析C++程式碼即可。

Let`s do it

以Command Line Tool模板為例,比如

//Dummy.h
@interface Dummy : NSObject {
@public
    int myInt;
}

@end

//Dummy.m
@implementation Dummy
@end

在main.m中,有

#import <Foundation/Foundation.h>
#import "Dummy.h"

int main(int argc, const char * argv[]) {  
    Dummy *dummy = [Dummy new];
    dummy->myInt;
      
    return 0;
}

執行

Clang -rewrite-objc main.m

開啟生成的main.cpp,可以看到

//Foundation.h展開後的一大堆C++定義
//...

/* @end */

#ifndef _REWRITER_typedef_Dummy
#define _REWRITER_typedef_Dummy
typedef struct objc_object Dummy;
typedef struct {} _objc_exc_Dummy;
#endif

extern "C" unsigned long OBJC_IVAR_$_Dummy$myInt;
struct Dummy_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int myInt;
};

/* @end */
int main(int argc, const char * argv[]) {
    Dummy *dummy = ((Dummy *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Dummy"), sel_registerName("new"));
    (*(int *)((char *)dummy + OBJC_IVAR_$_Dummy$myInt));

    return 0;
}

其中OBJC_IVAR_$_Dummy$myInt就是儲存Dummy的myInt成員變數偏移值的全域性變數。

如果用@property,會稍微複雜一些,比如

//Dummy.h
@interface Dummy : NSObject
@property (nonatomic, assign) int myInt;

@end

//Dummy.m
@implementation Dummy
@end

執行

Clang -rewrite-objc Dummy.m

開啟生成的Dummy.cpp,可以看到

extern "C" unsigned long OBJC_IVAR_$_Dummy$_myInt;
struct Dummy_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _myInt;
};

// @property (nonatomic, assign) int myInt;

/* @end */


// @implementation Dummy



static int _I_Dummy_myInt(Dummy * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Dummy$_myInt)); }
static void _I_Dummy_setMyInt_(Dummy * self, SEL _cmd, int myInt) { (*(int *)((char *)self + OBJC_IVAR_$_Dummy$_myInt)) = myInt; }
// @end

對應property的setter和getter,Clang相應地生成了兩個static function,兩者都通過OBJC_IVAR_$_Dummy$myInt來訪問myInt。

相關文章