iOS 对象与方法的本质

对象与方法的本质

Objective-C 对象Class的本质是结构体。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
#endif

} OBJC2_UNAVAILABLE;

NSObject的创建过程

我们都知道我们初始化一个对象都是这样子
NSObject *obj = [[NSObject alloc]init];
那么alloc 做了什么? init 又做了什么呢?

  • alloc
    我们可以从开发者中心下载到苹果开源的代码,我们这里举例使用的是objc750
    我们通过LLDB调试可以看到,对象alloc时,在汇编中调用了_objc_rootAlloc,明显这就是在苹果源码中的调用入口。

    image.png

  • 这里补充一下[obj new],相信聪明的你已经懂了。

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

LLDB继续往下走,可以发现在_objc_rootAlloc又调用了class_createInstance,我们在源码中跳转也能找到相关的方法

image.png

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// ************* 跳转 **************
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// ************* 跳转 **************
image.png

可见对象alloc的根本原理就在这个方法里面了!

  • class_createInstance
    这个方法最终会来到这里。记 得 看 注 释 。
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    // 一些判断参数 包括两个c++析构器和"是否可以创建Nonpointer",打断点进来之后,fast为true
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    // 计算对象所需要的内存空间
    // 这里有涉及字节对齐,iOS中以8字节对齐,
    // 什么意思呢,size必须为8的倍数,假设对象所需内存空间为12字节,那么这个size就是16。
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    // zone = nil && fast为true
    if (!zone  &&  fast) {
        //开辟内存空间,开辟后,obj还不具有NSObject该有的功能
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        // 初始化isa
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

  • 啥是isa
    isa是一个指针。在Objective-C中,任何类的定义都是对象。类和类的实例没有任何本质上的区别。任何对象都有isa指针。
    obj->initInstanceIsa(cls, hasCxxDtor);最终将会来到
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    // 如果不做nonpointer优化,
    // nonpointer优化是啥?
    // 使isa地址中包含了类信息、对象的引用计数等。
    // 这里传入的nonpointer 为true
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());
       
        // new一个isa_t
        // isa_t是一个联合体包含了这些变量
        // uintptr_t nonpointer        : 1;表示是否对 isa 指针开启指针优化 ,
        // uintptr_t has_assoc         : 1;关联对象标志位,0没有,1存在。
        // uintptr_t has_cxx_dtor      : 1;该对象是否有 C++ 析构器,如果有,则需要做析构逻辑,没有,则可以更更快的释放对象。
        // uintptr_t shiftcls          : 33;存储类指针的值。
        // uintptr_t magic             : 6;用于调试器判断当前对象是真的对象还是没有初始化的空间 。
        // uintptr_t weakly_referenced : 1;标志对象是否被指向或者曾经指向⼀一个 ARC 的弱变量,没有弱引用的对象可以更快释放
        // uintptr_t deallocating      : 1;标志对象是否正在释放内存。
        // uintptr_t has_sidetable_rc  : 1;标志对象是否有用到散列表,当对象引⽤计数大于 10 时,则需要借⽤该变量存储进位。
        // uintptr_t extra_rc          : 19;当表示该对象的引用计数值,实际上是引用计数值减 1,例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要用 has_sidetable_rc
        // 在64位的机器上,uintptr_t = unsigned long int
        // 在32位的机器上,uintptr_t =unsigned int
        
        isa_t newisa(0);
        
        //一系列 isa参数赋值
        // 其中 isa.magic isa.nonpointer 都包含在
        // 中ISA_MAGIC_VALUE
#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;
        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;
    }
}

这里展示一个创建完的isa中的具体参数


image.png

到了这里,obj的alloc就完整了。

iOS中,还有一个初始化方法allocWithZone,当然,我们在上文也有出现过,实际上alloc跟allocWithZone走的是同一个方法,只是参数区别在于一个zone,实际上zone已经被系统忽视(ignore)了,官方文档也默认让传入nil,所以,我们在平时创建出来的对象,不论你是否有传zone,官方都默认无视。


image.png

image.png

那么init呢?

  • init
    实际上 init 只是一个抽象方法,并没有做什么操作。
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

所以当我们对象alloc了之后,就一直可以对alloc出来的对象操作了。如下图。(工程名请无视 ^ _ ^ )


Objective-C 方法Method的本质

本质是发送消息,任何的方法的调用都会编译成消息。
消息的组成:
objc_msgSend(id self, SEL _cmd, ...)
消息接受者,消息编号,参数 (消息体)

objc_msgSend(id self, SEL _cmd, ...)

消息的组成由 id self,SEL _cmd 以及后续的参数其中,
id self 可以理解为调用方法的对象,消息接受者,即下文中的 "s"。

SEL _cmd 为方法名,传入类似sel_registerName("MethodName"),即下文中的"[s walk];"方法。

若方法中需要传入参数,就在后面拼接,即下文中的 "[s say:]"方法。

获取SEL的三种方法:
1. Runtime提供的          sel_registerName
2. Objective-C编译器提供的 @selector()
3. NSObject提供的         NSSelectorFromString()

SEL与IMP(implementation)的关系:
sel_registerName("MethodName")为方法编号,通过方法编号找到方法对应的IMP是函数实现的指针,
最后直接调用函数。
比喻:通过书页(SEL) 找到该页的具体内容  (IMP)

  • 实例方法调用
        Student *s = [Student new];
        [s walk];
        objc_msgSend(s, @selector(walk));

        //带参数
        [s say:@"Hello World"];
        objc_msgSend(s, sel_registerName("say:"), "Hello World");

  • 类方法调用
        [Student run];
        objc_msgSend(objc_getClass("Student"), sel_registerName("run"));

        //带参数
        [Student say:@"Hello World"];
        objc_msgSend(objc_getClass("Student"), sel_registerName("say:"), "Hello World");

  • 如何给父类发消息
        Student *s = [Student new];

        // 向父类发消息(对象方法)
        struct objc_super superClass;
        superClass.receiver = s; //确定这个消息发送的对象,即发送给s的父类 
        superClass.super_class = class_getSuperclass([s class]);
        objc_msgSendSuper(& superClass, sel_registerName("walk"));

        //向父类发消息(类方法)
        struct objc_super superClass;
        superClass.receiver = [s class];
        superClass.super_class = class_getSuperclass(object_getClass([s class]));
        objc_msgSendSuper(& superClass, sel_registerName("run"));
        //带参数
        objc_msgSendSuper(& superClass, sel_registerName("say:"),@"Hello World");
  • 问答

问:对象方法和类方法存在哪?
答:对象方法存在类里,类方法存在元类中

问:“类方法”在元类中是以什么样的姿态存在
答:实例方法。因为:对象在类中是实例,类在元类中也是对象、实例,参考:isa走位图

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