iOS原理 文章汇总
前言
Objective-C
是一种面向对象的语言,其中最重要的两个概念就是『类』和『对象』。
- 类:对同一类事物的高度抽象,定义了这类事物的属性以及行为准则(方法)。
- 对象:类的一个实例,是一个具体的事物。
举例来说,我们可以把『汽车』称为一个类,在这个类下面有很多具体对象,比如小轿车、卡车等。而在OC中,对象分为三类:instance对象(实例对象)、class对象(类对象)、meta-class对象(元类对象)。
一、instance对象
实例对象是通过类alloc出来的对象,每次调用alloc都会创建新的实例对象。
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
NSLog(@"obj1 = %@, &obj1 = %p", obj1, &obj1);
NSLog(@"obj2 = %@, &obj2 = %p", obj2, &obj2);
//打印结果
obj1 = , &obj1 = 0x7ffeefbff460
obj2 = , &obj2 = 0x7ffeefbff468
从打印结果可知,obj1和obj2指向的是不同的内存地址,所以对NSObjce类调用两次alloc方法,就创建了两个实例对象。需要注意的是,这里的obj1和obj2并不是实例对象本身,只是指向实例对象的地址,在编程中可以通过obj1和obj2来操作对象。他们的关系如下图所示:
obj1和obj2是两个指针对象,他们内存空间里保存的分别是NSObject的实例对象1和实例对象2的地址,这样可以通过obj1和obj2来访问实例对象,并进行操作。
二、class对象
类对象,在运行时由系统创建,一个类只会生成一个类对象,实例对象通过成员isa访问类对象。下面通过三种不同方式来获取NSObject的类对象,并打印地址:
NSObject *obj = [[NSObject alloc] init];
Class cls1 = [NSObject class];
Class cls2 = [obj class];
Class cls3 = object_getClass(obj);
NSLog(@"cls1 = %p \ncls2 = %p \ncls3 = %p", cls1, cls2, cls3);
//打印结果
cls1 = 0x100334140
cls2 = 0x100334140
cls3 = 0x100334140
从打印结果可知,三种方式获取到的类对象的地址都是一样的,说明一个类只会生成一个类对象。
三、meta-class对象
元类是类对象所属的类,其数据结构和类一致。因此实例对象是类的实例,而类对象是元类的实例。元类对象也是在运行时由系统创建的,只会生成一个,类对象也是通过其isa访问到元类对象。
四、实例对象的本质
介绍完对象的概念后,现在来探索实例对象在底层的呈现样式。总所周知OC底层还是C\C++,然后再经过汇编转换成机器语言,最后被计算机CPU所识别。我们可以通过Clang
将目标文件进行编译,然后查看对象的底层编译结果。
4.1 Clang
Clang是⼀个由Apple主导编写,基于LLVM的C/C++/Objective-C编译器,常用语法可以参考这篇文章。这里主要通过下面的命令来进行编译:
- 把⽬标⽂件编译成c++⽂件
clang -rewrite-objc main.m -o main.cpp
rewrite-objc
:重写objc语言
main.m
:是需要重写的目标文件
-o
:表示输出
main.cpp
:输出的c++文件
- 编译转换时指明编译平台
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等。
4.2 编译步骤
先在main.m文件里声明一个TestPerson类
//TestPerson类里只有一个name属性
@interface TestPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation TestPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
然后打开终端,cd进入main.m所在文件夹,执行clang -rewrite-objc main.m -o main.cpp
命令,即可得到编译后的main.cpp文件。
4.3 分析编译结果
打开main.cpp文件,直接搜索TestPerson,即可看到其编译结果。
//Person对象的底层编译结果
struct TestPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //isa
NSString *_name; //name
};
//name属性setter/getter方法的实现
static NSString * _I_TestPerson_name(TestPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TestPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_TestPerson_setName_(TestPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TestPerson, _name), (id)name, 0, 1); }
//main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_0w_4ns7bgrd4m9fd04w08706cr00000gp_T_main_001b7d_mi_0);
}
return 0;
}
从编译结果可以得到下面两个结论:
- 对象的本质是个结构体。TestPerson对象在底层被编译成结构体,其中包含了isa和name两个属性,且isa储存在内存中的第一位。
- 属性的setter方法在底层是通过调用
objc_setProperty
方法实现的。
4.4 objc_setProperty方法的实现
在objc-781
源码中搜索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常用语法介绍