一.alloc的底层原理
1.如下代码:person1,person2,person3这三个对象相同吗?
FKPerson *person1 = [FKPerson alloc];
FKPerson *person2 = [person1 init];
FKPerson *person3 = [person1 init];
NSLog(@"person1 = %@ person2 = %@ person3 = %@",person1,person2,person3);
打印结果:
person1 = person2 = person3 =
打印结果是相同的,alloc和init到底做了什么?为什么结果一样??
alloc底层做了什么?首先需要去苹果官网下载一份libobjc.dylib的源码
// 1
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
// 2
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
// 3
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
// 4
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
// 5
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
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
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; // 此时obj已经分配好内存,对象创建完成
}
alloc流程:
- alloc
- _objc_rootAlloc
- callAlloc
- class_createInstance
- _class_createInstanceFromZone
二.init的设计
// init源码底层什么都没做,直接返回obj
- (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;
}
通过底层代码看到,init没有做什么操作,直接返回了obj。这样做是一种抽象工厂设计模式,这样会是我们的代码实现更加自由,我们可以在子类中重写init方法,在重写的init方法中做一些初始化操作。
- (instancetype)init
{
self = [super init];
if (self) {
self.name = @"finley";
self.age = 18;
}
return self;
}
三.编译器优化分析
LLVM优化,到底优化了什么?
连接 编译 运行 空闲 C++
代码如下:
int sum(int a, int b) {
return a + b;
}
int main(int argc, char * argv[]) {
int c = sum(10, 20);
NSLog(@"sum == %d",c);
return 0;
}
转化为汇编代码(没有LLVM优化)
FKTestDemo`main:
0x10098a804 <+0>: sub sp, sp, #0x30 ; =0x30
0x10098a808 <+4>: stp x29, x30, [sp, #0x20]
0x10098a80c <+8>: add x29, sp, #0x20 ; =0x20
0x10098a810 <+12>: mov w8, #0xa
0x10098a814 <+16>: mov w9, #0x14
0x10098a818 <+20>: stur wzr, [x29, #-0x4]
0x10098a81c <+24>: stur w0, [x29, #-0x8]
0x10098a820 <+28>: str x1, [sp, #0x10]
0x10098a824 <+32>: mov x0, x8
0x10098a828 <+36>: mov x1, x9
0x10098a82c <+40>: bl 0x10098a7e4 ; sum at main.m:12
0x10098a830 <+44>: str w0, [sp, #0xc]
-> 0x10098a834 <+48>: ldr w8, [sp, #0xc]
0x10098a838 <+52>: mov x30, x8
0x10098a83c <+56>: mov x10, sp
0x10098a840 <+60>: str x30, [x10]
0x10098a844 <+64>: adrp x0, 2
0x10098a848 <+68>: add x0, x0, #0xc0 ; =0xc0
0x10098a84c <+72>: bl 0x10098ab24 ; symbol stub for: NSLog
0x10098a850 <+76>: mov w8, #0x0
0x10098a854 <+80>: mov x0, x8
0x10098a858 <+84>: ldp x29, x30, [sp, #0x20]
0x10098a85c <+88>: add sp, sp, #0x30 ; =0x30
0x10098a860 <+92>: ret
开启LLVM优化:
开启LLVM优化后的汇编代码如下:
FKTestDemo`main:
0x100f46a80 <+0>: sub sp, sp, #0x20 ; =0x20
0x100f46a84 <+4>: stp x29, x30, [sp, #0x10]
0x100f46a88 <+8>: add x29, sp, #0x10 ; =0x10
-> 0x100f46a8c <+12>: orr w8, wzr, #0x1e
0x100f46a90 <+16>: str x8, [sp]
0x100f46a94 <+20>: adr x0, #0x1634 ; @"sum == %d"
0x100f46a98 <+24>: nop
0x100f46a9c <+28>: bl 0x100f46b04 ; symbol stub for: NSLog
0x100f46aa0 <+32>: mov w0, #0x0
0x100f46aa4 <+36>: ldp x29, x30, [sp, #0x10]
0x100f46aa8 <+40>: add sp, sp, #0x20 ; =0x20
0x100f46aac <+44>: ret
开启LLVM优化后可以看到,汇编代码简化了很多。LLVM优化主要是优化了连接 编译 运行 空闲时间 C++代码。
四._class_creatInstanceFromZone源码分析
_class_createInstanceFromZone源码如下:
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
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
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;
}
首先我们创建一个对象时,最主要的是给对象分配一块内存。需要知道分配内存的大小。 size_t size = cls->instanceSize(extraBytes);这段代码就是给对象分配内存的。
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK; // 8字节对齐
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
由上面的代码可知道,对象的最小内存是16bytes。
五.系统字节对齐算法分析
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK; // WORD_MASK 7UL 8字节对齐
}
自定义8字节对齐算法:
int funMethod(int num) {
return (num + 7) >> 3 << 3;
}
// @property (nonatomic, copy) NSString *name; // 8
// @property (nonatomic, assign) NSInteger age; // 4
FKPerson *persion = [FKPerson alloc]; // person里面有两个属性
NSLog(@"---%zu",class_getInstanceSize([persion class])); // 24
上面的代码按照8位对齐,输出结果应该为16,但是结果为24,为什么?
因为person这个对象里面会有一个默认创建的isa指针,占8位。
int main(int argc, char * argv[]) {
FKPerson *persion = [FKPerson alloc];
persion.age = 18;
persion.name = @"finley";
persion.height = 185;
NSLog(@"--- %zu",class_getInstanceSize([persion class])); // 32
NSLog(@"--- %zu",malloc_size((__bridge const void *)(persion))); // 32
// isa -- age -- height -- name : 8 + 4 + 4 + 8 = 24 字节对齐---32
// isa -- age -- name -- height : 8 + 8 + 8 + 8 = 32 字节对齐---32
return 0;
}
上述代码的内存是怎么分配的:
对象占用大小:8字节对齐
系统分配内存大小:16字节对齐
系统分配内存算法:
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) { // 16倍数对齐
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;
}
LLDB调试结果
(lldb) p persion
(FKPerson *) $5 = 0x0000000283414ae0
(lldb) x 0x0000000283414ae0
0x283414ae0: 35 10 06 01 a1 01 00 00 12 00 00 00 b9 00 00 00 5...............
0x283414af0: e8 00 06 01 01 00 00 00 00 00 00 00 00 00 00 00 ................
(lldb) p 0x12
(int) $6 = 18
(lldb) p 0xb9
(int) $7 = 185
(lldb) p 0x01010600e8
(long) $8 = 4312137960
(lldb) po $8
finley
根据调试结果可以猜出内存分配的方式是:
isa -- age -- height -- name : 8 + 4 + 4 + 8 = 24 字节对齐---32
六.底层原理探索的方式
汇编分析、LLDB分析、源码分析
七.常见面试题
1.什么是runtime?
runtime是由C/C++、汇编编写的一套API,提供给OC使用的运行时特性。
参考源码:https://opensource.apple.com/tarballs/objc4/
https://opensource.apple.com/tarballs/libmalloc/