准备工作
Clang的概述
Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/ Objective-C++编译器。它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过attribute((overloadable))来修饰函数),其目标(之一)就是超越GCC。
总的来说:Clang是由Apple主导编写,基于LLVM的C/C++/Object-C编译器。
Clang的使用
-
clang -rewrite-objc main.m -o main.cpp
把目标文件编译成c++文件 - Xcode在安装的时候顺带安装了
xrun
命令,xrun
命令在Clang
的基础上进行了一些封装,相对来说更好用些。以下就是xrun
的命令使用:
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (手机)
xcrun:
表示xcode run
sdk iphoneos:
是指编译在iphoneos
平台,如果指定在模拟器编译,可以用iphonesimulator
。
arch arm
:arch
是architectures
构架的意思,arm64
是苹果所用的构架中的一种其他还包括:arm7、arm7s
等。
导出项目C++文件分析
【第一步:导出c++文件,并且定位到MyPersion类的的相关代码】
注意:生成的C++文件是非常大的,不建议一行行详细探究,分析关键代码就行了!
【第二步:代码分析】
- 源码:
#import
#import "AppDelegate.h"
@interface MyPersion : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) double height;
@end
@implementation MyPersion
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
MyPersion *persion = [[MyPersion alloc]init];
persion.name = @"woai";
persion.nickName = @"wc";
persion.age = 18;
persion.height = 170.0;
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
- 转化之后代码:
#ifndef _REWRITER_typedef_MyPersion
#define _REWRITER_typedef_MyPersion
typedef struct objc_object MyPersion; //MyPersion类是结构体类型的
typedef struct {} _objc_exc_MyPersion;
#endif
extern "C" unsigned long OBJC_IVAR_$_MyPersion$_name;
extern "C" unsigned long OBJC_IVAR_$_MyPersion$_nickName;
extern "C" unsigned long OBJC_IVAR_$_MyPersion$_age;
extern "C" unsigned long OBJC_IVAR_$_MyPersion$_height;
//MyPerson对象的底层编译结果:第一个属性是isa,其他依次是在类中定义的属性和成员变量
struct MyPersion_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa,也是结构体类型的
int _age;
NSString *_name;
NSString *_nickName;
double _height;
};
/* @end */
// @implementation MyPersion
//MyPersion类的属性getter方法底层的实现
static NSString * _I_MyPersion_name(MyPersion * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MyPersion$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_MyPersion_setName_(MyPersion * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyPersion, _name), (id)name, 0, 1); }
static NSString * _I_MyPersion_nickName(MyPersion * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MyPersion$_nickName)); }
static void _I_MyPersion_setNickName_(MyPersion * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyPersion, _nickName), (id)nickName, 0, 1); }
static int _I_MyPersion_age(MyPersion * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_MyPersion$_age)); }
static void _I_MyPersion_setAge_(MyPersion * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_MyPersion$_age)) = age; }
static double _I_MyPersion_height(MyPersion * self, SEL _cmd) { return (*(double *)((char *)self + OBJC_IVAR_$_MyPersion$_height)); }
static void _I_MyPersion_setHeight_(MyPersion * self, SEL _cmd, double height) { (*(double *)((char *)self + OBJC_IVAR_$_MyPersion$_height)) = height; }
// @end
int main(int argc, char * argv[]) { //main函数的口
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//persion初始化以及属性setter方法的底层实现
MyPersion *persion = ((MyPersion *(*)(id, SEL))(void *)objc_msgSend)((id)((MyPersion *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyPersion"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)persion, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_mq_n7r4vx491nz9b2b3wpmz1mg00000gn_T_main_36bbd5_mi_0);
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)persion, sel_registerName("setNickName:"), (NSString *)&__NSConstantStringImpl__var_folders_mq_n7r4vx491nz9b2b3wpmz1mg00000gn_T_main_36bbd5_mi_1);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)persion, sel_registerName("setAge:"), 18);
((void (*)(id, SEL, double))(void *)objc_msgSend)((id)persion, sel_registerName("setHeight:"), 170.);
}
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
拓展一波:有些人很好奇getter方法的实现入name属性:static NSString * _I_MyPersion_nickName(MyPersion * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_MyPersion$_name)); }
,这究竟什么意思呢,为什么要这样子操作呢?这里我就解答一波!请往下...
getter方法找到属性是通过地址偏移来找到的,首先找到self(MyPersion类)的地址+属性name的偏移地址。说起来就是那么简单,但是对于真正分析过mach-o文件的同学来说其实不简单,都是同过地址查找一层层符号表实现的,有兴趣的可以了解一下这方面的知识哦。
【根据以上代码查看objc_object
结构体】
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object *id;
typedef struct objc_selector *SEL;
总结:发现objc_object有一个属性是isa,牛批!感觉离真相越来越近了,然后发现平时我们的id类型原来是结构体指针!当然SEL也是一个结构体指针。
【根据第二步代码查看NSObject_IMPL
结构体】
struct NSObject_IMPL {
Class isa;
};
得出:不难发现这就是isa。
请参考. 《isa&noPointerIsa详解》
总结:
- 对象的本质是个结构体。MyPerson对象在底层被编译成结构体,其中包含了isa和name等属性,且isa储存在内存中的第一位。
- 属性的setter方法在底层是通过调用objc_setProperty方法实现的。
拓展: objc_setProperty方法的实现
在objc-818.2源码中搜索objc_setProperty方法,可以看到实现逻辑如下:
//这里直接调用 reallySetProperty
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
//这里主要实现了新值的retain,以及旧值的release
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
//step1:retain新值
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
//step2:release旧值
objc_release(oldValue);
}
得出:系统在objc_setProperty方法中通过持有新值、释放旧值实现了属性的赋值。在属性赋值过程中,由于开发人员可以自定义各种属性,如果在底层直接实现赋值,会产生大量的临时变量。鉴于此,苹果提供objc_setProperty方法来作为一个中间层的通配方法,通过_cmd区分不同的属性,完成了setter方法的上下层关联。
帮助
1. Clang: a C language family frontend for LLVM
2. clang常用语法介绍