iOS-alloc & init & new

432 阅读6分钟

问题先行:

Persion *p = [Persion alloc];
p.age = 10;
​
Persion *p1 = [p init];
Persion *p2 = [p init];    
Persion *p3 = p;
Persion *p4 = p;
​
NSLog(@"p.age = %d\np1.age= %d\np2.age= %d",p.age,p1.age,p2.age);
NSLog(@"%@ ==== %p ==== %p"
,p,p,&p);
NSLog(@"%@ ==== %p ==== %p" ,p1,p1,&p1);
NSLog(@"%@ ==== %p ==== %p");

1、如上代码p1、p2、p是否一样?
2、如上代码第一行控制台输出结果是什么?
3、如上代码p1、p2和p3、p4是否一样?
4、如上代码后三行控制台输出是什么样子的?
5、字节对齐算法,可以有几种写法?

@interface ASTest2 : NSObject
@property(nonatomic, assign) double a;
@property(nonatomic, assign) int c;
@property(nonatomic) char *b;
@property(nonatomic, assign) short d;
@end
@implementation ASTest2
@end@interface ASTest : NSObject
@property(nonatomic, assign) double a;
@property(nonatomic) char *b;
@property(nonatomic, assign) int c;
@property(nonatomic, assign) short d;
@end
@implementation ASTest
@endNSLog(@"%lu", class_getInstanceSize(ASTest.class));
NSLog(@"%lu", class_getInstanceSize(ASTest2.class));

6、如上代码,输出结果相等吗?为什么?

资料准备:

1、objc源码下载opensource.apple.com/
2、libmalloc源码下载opensource.apple.com/tarballs/li…

流程分析(符号断点方式):

1、在对应的alloc方法处打断点,进入之后再添加一个符号断点alloc往下执行 2、再添加一个_objc_rootAlloc的符号断点往下执行 3、再添加一个_objc_rootAllocWithZone的符号断点往下执行 依照这个方法依次往下执行即可。。。。。。
注意
1、这个流程中并没有出现callAlloc,是因为编译器优化的结果。
2、这里的展示的流程可能会遗漏掉一些例如objc_alloc,因为objc源码存在如下消息转发的逻辑

static void 
fixupMessageRef(message_ref_t *msg)
{    
    msg->sel = sel_registerName((const char *)msg->sel);
    if (msg->imp == &objc_msgSend_fixup) { 
        if (msg->sel == @selector(alloc)) {
            msg->imp = (IMP)&objc_alloc;
        } else if (msg->sel == @selector(allocWithZone:)) {
            msg->imp = (IMP)&objc_allocWithZone;
        } else if (msg->sel == @selector(retain)) {
            msg->imp = (IMP)&objc_retain;
        } else if (msg->sel == @selector(release)) {
            msg->imp = (IMP)&objc_release;
        } else if (msg->sel == @selector(autorelease)) {
            msg->imp = (IMP)&objc_autorelease;
        } else {
            msg->imp = &objc_msgSend_fixedup;
        }
    }
 ......
}

流程分析(control+step into方式):

1、按住control的同时,点击 Step into 2、依照这个方法依次往下执行即可。。。。。。 3、注意看这个流程和符合断点的流程有点不一样,正是👆所说的原因。

流程分析(汇编分析方式):

1Xcode工具栏选择Debug --> Debug Workflow --> Always Show Disassembly,这个选项表示始终显示反汇编 2、然后结合control + step into方式往下执行即可

流程方法分析:

objc_alloc(Class cls)

+ (id)alloc {
    return _objc_rootAlloc(self);
}
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
​
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

通过源码看调用的是_objc_rootAlloc方法,为什么实际走的是objc_alloc方法呢?

通过注释// Calls [cls alloc]
通过fixupMessageRef函数可知,调用alloc方法时会被转发到objc_alloc
根本原因是llvm对alloc等一些特殊函数调用的优化

callAlloc(Class cls, bool checkNil, bool allocWithZone=false)

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

ALWAYS_INLINE
强制内联,所有加了__attribute__((always_inline))的函数再被调用时不会被编译成函数调用而是直接扩展到调用函数体内
编译优化slowpath、fastpath

#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
  • 首先我们要知道__builtin_expect是什么。其实,这个指令是gcc引入的,作用是允许程序员将最有可能执行的分支告诉编译器。这个指令的写法为:__builtin_expect(EXP, N)
  • 目的:编译器可以对代码进行优化,以减少指令跳转带来的性能下降,即性能优化;
  • 作用:允许程序员将最有可能执行的分支告诉编译器;
  • 指令的写法为:__builtin_expect(EXP, N)。表示 EXP==N的概率很大;
  • fastpath定义中__builtin_expect((x),1)表示 x 的值为真的可能性更大;即执行 if 里面语句的机会更大;
  • slowpath定义中的__builtin_expect((x),0)表示 x 的值为假的可能性更大,即执行 else 里面语句的机会更大; Xcode配置 hasCustomAWZ()
bool hasCustomAWZ() const {
     return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
// class or superclass has default alloc/allocWithZone: implementation
// Note this is is stored in the metaclass.
#define FAST_CACHE_HAS_DEFAULT_AWZ    (1<<14)

1、判断缓存中是否有默认的alloc/allocWithZone方法(这个值会存储在metaclass中)
2、NSObject系统在llvm编译时就已经初始化好了,因此缓存中就有alloc/allocWithZone方法了
3、这就是为什么NSObject和自定义类的alloc流程不太一样

_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;
    if (fastpath(!zone)) {
        obj = class_createInstance(cls, 0);
    } else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
    if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls);
    return obj;
}

class_createInstance(Class cls, size_t extraBytes)

id
class_createInstance(Class cls, size_t extraBytes)
{
    if (!cls) return nil;
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

核心方法源码分析:

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());
​
    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
​
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
​
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
​
    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
​
    if (fastpath(!hasCxxCtor)) {
        return obj;
    }
​
    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

该核心方法主要包括以下几个核心知识点

1判断构造、析构方法实现
1、hasCxxCtor()是判断当前class或者superclass是否有.cxx_construct构造方法的实现
2、hasCxxDtor()是判断判断当前class或者superclass是否有.cxx_destruct析构方法的实现
2判断某个类是否支持优化的isa
canAllocNonpointer()是具体标记某个类是否支持优化的isa,即是对 isa 的类型的区分,如果一个类和它父类的实例不能使用 isa_t 类型的 isa 的话,返回值为 false.在 Objective-C 2.0 中,大部分类都是支持的
3内存空间计算
instanceSize(extraBytes)
4calloc向系统申请开辟内存,并返回地址指针

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
  size_t k, slot_bytes;
​
  if (0 == size) {
    size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
  }
  k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
  slot_bytes = k << SHIFT_NANO_QUANTUM;              // multiply by power of two quanta size
  *pKey = k - 1;                          // Zero-based!return slot_bytes;
}
​
#define NANO_REGIME_QUANTA_SIZE  (1 << SHIFT_NANO_QUANTUM)  // 16
#define SHIFT_NANO_QUANTUM    4

从👆逻辑可知,segregated_size_to_fit方法其实是对申请的内存大小进行16字节对齐,最终以16字节的倍数来分配空间,最少分配16字节。
5isa初始化以及类关联
obj->initInstanceIsa(cls, hasCxxDtor);

init、new、allocWithZone:

init的流程

第一步
- (id)init {
    return _objc_rootInit(self);
}
第二步
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;
}

一种编程思想,主要是为了让子类重写

new的流程

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

allocWithZone的流程

// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

总结:

一个对象的创建整个流程大体如下
1、编译优化判断
2、空间大小计算+8字节对齐+内存对齐
3、系统开辟空间+16字节对齐
4、isa初始化、类关联

问题先行解答:

1、p1、p2、p是一样的
2、p.age = 10 p1.age= 10 p2.age= 10
3、p1、p2和p3、p4是一样的
4、p、p1、p2、的第一、二个值输出都一样,都为Persion开辟的内存空间地址,p、p1、p2、的第三个值输出都不样,为各自的地址指针
5、字节对齐三种写入如下

/// 方案1,相对位运算效率要低
func word_align(x: UInt32) -> UInt32 {
    return (x + 7) / 8 * 8
}
/// 方案2,通过右移左移,低三位清0
func word_align(x: UInt32) -> UInt32 {
    return ((x + 7) >> 3) << 3
}
/// 方案3(推荐方案),另一种低三位清0方式
func word_align(x: UInt32) -> UInt32 {
    return (x + 7) & (~7)
}

6、相等,本质上由于内存对齐规则会导致需要的内存大小不一样,但是OC会进行属性重排。