iOS-OC底层03:isa是怎么关联类的

前沿

经过看apple的开源源码,了解到共同体(isa_t)和位域(ISA_BITFIELD)在底层起到至关重要的作用。 首先先对用到的位域和共同体简单回顾。

共同体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

union Data
{
   int I;
   float f;
   char  str[20];
};
 
int main( )
{
   union Data data;        
 
   data.i = 10;
   data.f = 220.5;
   strcpy( data.str, "C Programming");
 
   printf( "data.i : %d\n", data.i);
   printf( "data.f : %f\n", data.f);
   printf( "data.str : %s\n", data.str);
 
   return 0;
}
输入结果
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。
我们做一下调整

int main( )
{
   union Data data;        
 
   data.i = 10;
   printf( "data.i : %d\n", data.i);
   
   data.f = 220.5;
   printf( "data.f : %f\n", data.f);
   
   strcpy( data.str, "C Programming");
   printf( "data.str : %s\n", data.str);
 
   return 0;
}
输入结果
data.i : 10
data.f : 220.500000
data.str : C Programming

在这里,所有的成员都能完好输出,因为同一时间只用到一个成员。

位域

struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status;

上面的结构中,status 变量将占用 4 个字节的内存空间,但是只有 2 位被用来存储值。如果您用了 32 个变量,每一个变量宽度为 1 位,那么 status 结构将使用 4 个字节,但只要您再多用一个变量,如果使用了 33 个变量,那么它将分配内存的下一段来存储第 33 个变量,这个时候就开始使用 8 个字节。

关联类

如想了解alloc的前期,请参考
obj->initInstanceIsa(cls, hasCxxDtor);===》initIsa(cls, true, hasCxxDtor);

我们先了解一下对象的成员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
};

#   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

1.nonpointer:表示是否对 isa 指针开启指针优化 0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等
2.has_assoc:关联对象标志位,0没有,1存在
3.has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
4.shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
5.magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
6.weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,
没有弱引用的对象可以更快释放。和dealloc方法有关系
7.deallocating:标志对象是否正在释放内存
8.has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位
9.extra_rc:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
我们来分析一下initIsa具体实现

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)cls);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());

        isa_t newisa(0);
#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
        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;
//设置类,因为cls的类型也是为isa_t,去掉前面三位,设置值时,后面的会没地方存储会自动抹掉。
        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;
    }
}

验证


image.png

我们操作的思想如下图


123.png

lldb命令

p:打印
po:打印描述,如果在类中重新description,那po的打印结果就是description中的返回字符串
memory read p:p为某个对象,含义就是打印p对象的内存情况
x p:也是打印p对象的内存情况
x/4gx p:也是打印p对象的内存情况后面的4gx为打印格式,一段8字节4段打印p对象的内存
x/16gw objc2:打印objc2的内存情况,打印格式 是4字节一组打印16组数据

计算大小

sizeof(p),因为p为对象的指针,指针为8字节,所以返回是8
sizeof(int),我们知道int是4个字节,所以返回是4字节
如果我们想知道某个对象的占有的内存是多少怎么办呢?下面有两个方法
class_getInstanceSize([LGPerson class]) 和malloc_size((__bridge const void *)(person)),
class_getInstanceSize为实力的内存大小,8字节对齐
malloc_size为系统分配的内存大小,因为需要16字节对齐,所以分配的内存要大于等于所占用的内存

Clang

clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件
UIKit报错问题
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / Applications/Xcode.app/Contents/Developer/Platforms/ iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,要更好用一些
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 (手机)

分析对象

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

通过
clang -rewrite-objc main.m -o main.cpp 后的主要代码

struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
}
// @end

我们可以看出NSObject_IVARS就是我们研究的isa,_I_LGPerson_name和I_LGPerson_setName就是get和set方法
我们可以看出set的方法调用的本质就是objc_setProperty

setMethod.png

补充内容dealloc的过程

_objc_rootDealloc->obj->rootDealloc();->rootDealloc()

objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

//如果没有开启指针优化,没有弱引用,没有关联,没有cxx,没有映射表直接释放
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}
id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

你可能感兴趣的:(iOS-OC底层03:isa是怎么关联类的)