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 <UIKit/UIKit.h>
#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常用语法介绍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容