iOS进阶笔记(三) Objc关键字

📣 iOS进阶笔记目录


isa

isa的本质是union isa_t类型的共用体
共用体的特点是:所有成员都共用一块内存,内存大小为最大成员的内存

isa源码结构

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

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)// isa 位域
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

ISA_BITFIELD定义为:

define ISA_BITFIELD   // iOS 的定义                                    
        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 unused            : 1;                                       
        uintptr_t has_sidetable_rc  : 1;                                       
        uintptr_t extra_rc          : 19

下面对ISA_BITFILED进行逐一说明:

ISA_BITFIELD成员 说明
nonpointer

表示是否对 isa 指针开启指针优化
0:纯isa指针,存储对象(class对象和meta-class对象)的内存地址;
1:优化的,isa,使用了位域, 包含更丰富的信息(类信息、对象的引用计数等)

has_assoc

关联对象标志位,0表示没有关联对象; 没有释放时,能更快地释放对象

has_cxx_dtor

表示该对象是否有 C++ 或者 Objc 的析构器。
如果有析构函数,则需要做析构逻辑,;
如果没有,则可以更快的释放对象;

shiftcls

存储类指针的地址。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针;

magic

用于调试器判断当前对象是真的对象还是没有初始化的空间;

weakly_referenced

对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放;

unused

当为1时,则该对象标记为使用过。

has_sidetable_rc

当对象引用计数extra_rc大于2^19 - 1时,则需要借用该变量存储进位;

extra_rc

当表示该对象的引用计数值,实际上是引用计数值减1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。


isa的创建过程


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

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

💡为什么要 & ISA_MASK运算?
isa是共用体,占用33位,第 3-35位用来表示 class(即共用体中的 uintptr_t shiftcls)
ISA_MASK (0x0000000ffffffff 8ULL) 转换成二进制就是32位全为1的16进制数据 ,isa & ISA_MASK 相当于将shiftcls最高位置0,只有32位值有效。


💡怎么计算一个类对象及元类对象的内存地址?
1)class对象的地址 = instance对象的 isa & ISA_MASK
2)meta-class对象的地址 = class对象的 isa & ISA_MASK


以Person对象内存地址计算为例:

struct person_objc_class {
    Class isa;
    NSString *_name;
    int _age;
};

void get_class_adress()
{
    /**
     person isa:0x1d8001000023a5
     personClass 地址:0x00000001000023a0
     arm64下 ISA_MASK 为0x0000000ffffffff8ULL;x_86_64下 ISA_MASK为0x00007ffffffffff8ULL
     
     计算过程
       0x001d8001000023a5   person->isa
     & 0x00007ffffffffff8   ISA_MASK
       0x00000001000023a0   personClass
     */
    
    Person *person = [[Person alloc] init];
    Class personClass = [person class];
    struct person_objc_class *p1 = (__bridge struct person_objc_class *)person;
    NSLog(@"personClass:%p p1->isa:%p",personClass,p1->isa);// 打印结果为:0x00000001000023a0  0x1d8001000023a5
}


💡isa怎样与类进行关联的❓

isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
 shiftcls = (uintptr_t)newCls >> 3;
}

通过setClass函数可知,类地址cls通过右移3位的赋值给了isa的shiftcls属性,建立类与isa的绑定。


小结

首先看下这张经典的isa和superclass指针结构图


💡对象的isa指针指向哪里?
1) 实例对象的isa指向它的类对象class;
2) 类对象的isa指向它的meta-class对象;
3)元类对象的isa指向它基类的元类meta-class;
4)基类的元类isa指向它自身。


💡对象superclass指向哪里?
1)类对象的superclass指向它的父类对象;
2)NSObject类对象的superclass指向nil(NSObject没有父类所以其superclass指向nil);
3)meta-class对象的superclass指向它父类的meta-class;
4)根类的元类对象的superclass指向根类的类对象(NSObject根类的元类对象的superclass指向NSObject根类的类对象)。



self和super

1、self

  • self本质

表示当前对象(指针),为当前方法调用者;
self 是局部变量,在方法调用过程中需要以参数的方式带入到方法中参与方法体的具体执行过程;
self调用方法时,本质上执行了objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

2、super

  • super本质

super 是预编译指令,而非指针,不是方法调用者;
super调用方法时,本质上执行了objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

  • super结构
struct objc_super {
     id receiver;// self
     Class superClass;
 };

💡现有Student类,其父类为Person。[super setAge:] 过程是怎么执行的?

objc_super 结构体指向的 superClass 的方法列表开始找 setAge 的selector,找到后再以 objc_super->receiver 去调用这个 selector。self才是 setAge: 消息的接收者


id

本质是指向 objc_object的指针,typedef struct objc_object *id;



Class

本质是指向objc_class(继承自objc_object)的指针, typedef struct objc_class *Class



IMP

IMP本质是函数指针,代表函数的具体实现,typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...)



SEL

定义:typedef struct objc_selector *SEL;

代表方法(或函数名),也叫选择子,底层与char *类似

可以通过 @selector(方法名) 和sel_registerName(const char * _Nonnull str)获取

可以通过sel_getName()和NSStringFromSelector()转成字符串

不同类中相同的方法名,所对应的选择器是相同的



_cmd

_cmd表示当前方法的SEL,通过clang可知晓,每个方法默认将_cmd和self作为方法的形参传入方法体中。

如👇的Peron对象的-sleep方法

@implementation Student
- (void)sleep{}
@end

static void _I_Student_sleep(Student * self, SEL _cmd) {

}


Method

本质是指向objc_method结构体的指针,typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name // 函数名称
    char * _Nullable method_types // 编码(返回值类型,参数类型)
    IMP _Nonnull method_imp // 指向函数的指针(函数地址)
}


@encode(Type Encoding)

Apple提供了@encode()指令,用于将类型转为相应的字符串标识符(即method_t中 const char *types)。

常用Code

Code 含义
i int
@ An object (whether statically typed or typed id)
: A method selector (SEL)
# A class object (Class)
v Void

举个栗子🌰
-(void)test方法的types表示为v16@0:8
1)v表示返回值为void;
2)16表示参数总共内存大小(字节数),第一个参数为self,占用8字节,第二个参数为SEL struct objc_selector *类型,占用8个字节;
3)@表示第一个参数类型为对象类型(即self),0表示第一个参数内存起始值,因为是第一个参数,内存从0开始;
4):表示第二个参数类型为SEL,8表示第二个参数内存起始值,由于第一次参数为self,占用8个字节,故第二个参数从8开始。


@dynamic

告诉编译器不自动生成settter和getter方法,可以通过runtime在运行时决议


以上

------------------ End ------------------

posted @ 2021-08-04 20:54  ITRyan  阅读(123)  评论(0编辑  收藏  举报