在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。