OC底层原理(三)、isa、对象和类结构、属性粗略分析

OC底层原理汇总

从探索isa的初始化开始

OC底层原理(一).alloc实际调用流程分析内容最后的流程图中,_class_createInstanceFromZone,我们分为三步:

  • 1、size = cls->instanceSize(extraBytes);获取对象需要分配的内存大小
  • 2、obj = (id)calloc(1, size);如何申请内存
  • 3、obj->initInstanceIsa(cls, hasCxxDtor);初始化isa

上一篇中我们分析了获取需要分配的大小,以及具体如何分配内存,也就是第1步和第2步。本篇我们继续对第三步进行探索,也就是isa的创建以及初始化。

分析initInstanceIsa方法的实现

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

initInstanceIsa内部调用了initIsa,并且传入第一个参数为cls(类的地址),第二个参数为true,第三个为hasCxxDtor,我们看看initIsa实现

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        //如果是纯指针的isa,那么isa仅仅用于存放类地址
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        //分配一个isa_t变量newisa,并把所有位清零
        isa_t newisa(0);
        /*
         是否支持索引,默认苹果平台都不支持的
         SUPPORT_INDEXED_ISA为1表示类表的索引
        */
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        /*
         0x001d 8000 0000 0001ULL
         给isa一个初始值0x001d800000000001ULL
         在这个初始值中,已经设置了nonpointer和magic的值
         */
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        /*
         将类的地址信息右移三位,放入预设好的类的存储位置
         */
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

initIsa中,做了isa位域中四个初始化,分别是给magicnonpointerhas_cxx_dtorshiftcls赋了值。
大致流程如下:

initIsa流程

OC底层原理(一).alloc实际调用流程分析内容中,我们知道initIsa来自于最初的alloc调用。也就是说,一个类调用alloc,除了分配内存以及内存对齐外,它还对内部的成员变量isa作了初始化,每个对象一定有一个叫isa的成员变量,那么,isa到底是什么呢?

isa是什么

使用clang重写分析

我们在main.m中创建一个继承自NSObject的类LWPerson,代码如下:

#import <Foundation/Foundation.h>

@interface LWPerson : NSObject

@property (nonatomic,copy) NSString *name;

@property (nonatomic,assign) short age;

@property (nonatomic,assign) BOOL isMan;


@end

@implementation LWPerson

+ (void)LWPersonClassMethod{
    NSLog(@"%s",__func__);
}
- (int)LWPersonInstanceMethod{
    NSLog(@"%s",__func__);
    NSArray *array = @[@1,@2,@3];
    return [array[1] intValue];
}

@end
int main(int argc, const char * argv[]) {
    
    LWPerson *person = [LWPerson alloc];
    person.name = @"Jobs";
    person.age = 5;
    person.isMan = YES;
    
    [person LWPersonInstanceMethod];
    [LWPerson LWPersonClassMethod];
    
    return 0;
}

我们在终端使用如下clang命令将main.m重写为C、C++实现

clang -rewrite-objc main.m -o main.cpp

执行命令后,在目录中生成一个main.cpp文件,我们打开它,搜索LWPerson,我们删除和main中无关的数据后,得到最后的代码如下:

#ifndef _REWRITER_typedef_LWPerson
#define _REWRITER_typedef_LWPerson
typedef struct objc_object LWPerson;
typedef struct {} _objc_exc_LWPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LWPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_LWPerson$_isMan;
struct LWPerson_IMPL {
    //这就是isa
    struct NSObject_IMPL NSObject_IVARS;
    BOOL _isMan;
    short _age;
    NSString *_name;
};


// @property (nonatomic,copy) NSString *name;

// @property (nonatomic,assign) short age;

// @property (nonatomic,assign) BOOL isMan;


/* @end */


// @implementation LWPerson

//类方法重写
static void _C_LWPerson_LWPersonClassMethod(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_0,__func__);
}
//实例方法重写
static int _I_LWPerson_LWPersonInstanceMethod(LWPerson * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_1,__func__);
    NSArray *array = ((NSArray *(*)(Class, SEL, ObjectType  _Nonnull const * _Nonnull, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(3U, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 2), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 3)).arr, 3U);
    return ((int (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("objectAtIndexedSubscript:"), (NSUInteger)1), sel_registerName("intValue"));
}

//name的getter方法
static NSString * _I_LWPerson_name(LWPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LWPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

//name的setter方法
static void _I_LWPerson_setName_(LWPerson * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LWPerson, _name), (id)name, 0, 1); }
//age的getter方法
static short _I_LWPerson_age(LWPerson * self, SEL _cmd) { return (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)); }
//age的setter方法
static void _I_LWPerson_setAge_(LWPerson * self, SEL _cmd, short age) { (*(short *)((char *)self + OBJC_IVAR_$_LWPerson$_age)) = age; }
//isMan的setter方法
static BOOL _I_LWPerson_isMan(LWPerson * self, SEL _cmd) { return (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)); }
//isMan的setter方法
static void _I_LWPerson_setIsMan_(LWPerson * self, SEL _cmd, BOOL isMan) { (*(BOOL *)((char *)self + OBJC_IVAR_$_LWPerson$_isMan)) = isMan; }
// @end
int main(int argc, const char * argv[]) {
    //alloc方法调用
    LWPerson *person = ((LWPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("alloc"));
    //调用setName:
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_h4_g62cv3xn0ys3gpzsj_wjmtmh0000gn_T_main_8bac4e_mi_2);
    //调用setAge:
    ((void (*)(id, SEL, short))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), (short)5);
    //调用setIsMan:
    ((void (*)(id, SEL, BOOL))(void *)objc_msgSend)((id)person, sel_registerName("setIsMan:"), ((bool)1));

    //调用实例方法LWPersonInstanceMethod
    ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("LWPersonInstanceMethod"));
    //调用类方法LWPersonClassMethod
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LWPerson"), sel_registerName("LWPersonClassMethod"));

    return 0;
}

从上面的代码可以看出以下几点:

  • 1.OC类和对象底层都是结构体
  • 2.每个对象都有一个默认的isa变量,它由NSObject继承而来
  • 3.属性的本质,就是实例变量+setter+getter
  • 4.实例变量值的获取,就是对象首地址+地址偏移
  • 5.属性值的设置,对于简单类型值,直接通过地址偏移设置,其他底层都是调用了objc_setProperty函数。
  • 6.方法的调用,都是使用objc_msgSend发送消息

OC类和对象底层都是结构体

我们打开objc781源码,可以在objc-private.h中找到struct objc_object的定义,如下所示:

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;
    ......
}

我们看到struct objc_object中,有一个成员变量isa,另外有三个获取isa的成员方法:

  • Class ISA()方法用于非taggedPointer对象获取isa
  • Class rawISA();方法用于非taggedPointer对象非nonpointer对象获取isa
  • Class getIsa();方法用于taggedPointer对象获取isa

另外,它还提供了直接获取isabits成员的方法uintptr_t isaBits() const,用const在函数末尾修饰,代表一个常成员函数,仅能读取。

此外,我们可以看到我们常用于指向对象类型的id类型,它是struct objc_object *的别名。

/// A pointer to an instance of a class.
typedef struct objc_object *id;

objc-runtime-new.h文件中,我们可以找到struct objc_class的定义,如下所示:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
}

从上面可以看出,struct objc_class继承于objc_object,它有四个成员:

  • isa,来自于它的父类,isa_t类型
  • superclass,它是一个指向父类的指针
  • cachecache_t类型,存储缓存的结构体
  • bitsclass_data_bits_t类型的结构体,存储了类中方法、协议、成员列表等的信息。

同样,我们可以找到关于Class的定义,它是struct objc_class *的别名

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

对象和类结构如下图


对象与类结构体

isa结构

我们可以找到isa的定义,如下图所示:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

从代码我们可以看出,isa_t是一个联合体,它有两个成员,分别为Class clsuintptr_t bits

我们知道,所谓联合体,也叫共用体,它的所有成员变量共用一段内存,它的大小等于最大的成员变量的大小,所以,我们可以得出,isa_t内存所占大小为8字节,64位

我们再看定义中更重要的一段定义ISA_BITFIELD;,它在isa.h中,如下所示

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

我们先不讨论内部的执行结果,我们先谈谈关于联合体中定义struct,而struct中定义一连串的成员,这个是什么呢,在C语言中,我们将它叫做位域

位域

位域,也叫位段,它是一种特殊的结构体类型,其所有成员的长度均是以二进制位为单位定义的,结构体的成员被称为位段,位段定义的一般形式如下:

struct 结构名
{
  类型 变量名1:长度
  类型 变量名2:长度
  ……
  类型 变量名n:长度
}

位段定义类型必须是intunsignedsigned中的一种,也就是必须为整形

位段的特性如下:

  • 从低位到高位排列
  • 一个位段必须存在一个存储单元中,不能跨两个存储单元,如果本单元不够容纳某位段,则从下一个单元开始存储该位段
  • 可以用%d%x%u%o等格式字符,以整数形式输出位段
  • 在数值表达式中引用位段时,系统自动将位段转换为整形数。

isa中的位域意义

isa位段定义的意义如下图所示

isa位域

在不同平台下,isa的内部存储区域如下图

isa位域占位

使用lldb对验证isa存储类地址

在之前,我们提到struct objc_obejct的成员函数ISA()是获取nonpointer isa的类地址的方法,我们来看看它的实现

inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

核心代码为(Class)(isa.bits & ISA_MASK);,这一步将isa的数据与一个ISA_MASK进行按位与,就得到了类的地址信息。

define ISA_MASK        0x0000000ffffffff8ULL

我们使用一开始例子,打断点如下


断点

然后断点执行到这一步时候,我们使用如下命令得到结果

(lldb) x/4gx person
0x10054aa30: 0x001d8001000032f5 0x0000000000050001
0x10054aa40: 0x0000000100002040 0x0000000000000000
(lldb) po 0x001d8001000032f5 & 0x0000000ffffffff8ULL
LWPerson

这样就验证了isa是存储类信息的。

objc_setProperty源码分析

objc_setProperty源码如下:

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);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        //如果偏移量为0,就是设置isa,也就是设置类地址信息
        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;
        //新值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();
    }
    //旧值release
    objc_release(oldValue);
}

从源码中我们可以看到,retainrelease的操作在底层已经帮我们完成了,所有涉及引用计数的属性都要调用到objc_setProperty方法,这是一种适配器模式的思想,它大大的节约了我们的代码量。

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