iOS对象的本质

准备工作

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 armarcharchitectures构架的意思,arm64是苹果所用的构架中的一种其他还包括:arm7、arm7s等。

导出项目C++文件分析

【第一步:导出c++文件,并且定位到MyPersion类的的相关代码】

提取C++文件并定位

注意:生成的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常用语法介绍

你可能感兴趣的:(iOS对象的本质)