在oc 中我们经常和对象打交道,一个对象是如何被创建出来的,[alloc init] 做了什么,既然是创建对象必然要分配内存,又分配了多少内存,为什么是这么多内存,创建的时候还做了什么
对象的初探
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
NSLog(@"%@-%p-%p", p1, p1, &p1);
NSLog(@"%@-%p-%p", p2, p2, &p2);
NSLog(@"%@-%p-%p", p3, p3, &p3);
打印结果:
alloc 申请开辟内存,p1 指针指向了 alloc 开辟好的内存地址,p1指针在init初始化之后赋值给p2,p3, &p1,&p2,&p3的地址栈空间上的内存地址,初始化的内存还是同一块内存,三者指向同一块内存
汇编结合源码调试分析
1.首先在这里打个断点
2.进入汇编跳转的方式,Xcode -> Debug -> Debug Workflow -> Always Show Disassembly
3.可以看到
刚才汇编找到的执行方法 objc_alloc 添加符号断点
添加符号断点 _objc_rootAllocWithZone
添加符号断点 calloc
这里我们是来探究alloc的执行流程,通过上面截图看出可以通过汇编和符号断点的方式找到对应的执行流程,方便在源码中寻找点击进入官网获取源码
alloc的主线流程
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// 重磅提示 这里是核心方法
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;//当前类为nil或不存在,直接返回nil
if (fastpath(!cls->ISA()->hasCustomAWZ())) {//AWZ methods: +alloc / +allocWithZone:
//如果这个必然存在的类的指针对象有默认的 `alloc/allocwithzone`方法
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));
}
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
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)
{
// 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)) {//然后判断obj是否存在,不存在的话就返回一个badAcllocHandler或者nil
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);//初始化isa
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags)return object_cxxConstructFromClass(obj, cls, construct_flags);
}
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes. 对所有对象至少开辟16字节的空
if (size < 16) size = 16;
return size;
}
综上大概可以得到这样一个流程图:
fastpath & slowpath
宏定义
#define fastpath(x) (__builtin_expect(bool(x), 1)) // bool(x) 大概率为真
#define slowpath(x) (__builtin_expect(bool(x), 0)) // bool(x) 大概率为假
说明: __builtin_expect((x),1) 表示 x 的值为真的可能性更大;
__builtin_expect((x),0) 表示 x 的值为假的可能性更大。
__builtin_expect(EXP, N) 的作用,我们可以做一个假设。 比如我现在有个箱子,箱子里面有5个球,4个是蓝色的,1个是红色的。我们知道大概率下。我们随机抽取的球的颜色一定是蓝色的。这样我们就可以用这个指令,来减少指令跳转带来的性能下降
#define likely(x) (__builtin_expect(bool(x), 1)) // bool(x) 大概率为真
#define unlikely(x) (__builtin_expect(bool(x), 0)) // bool(x) 大概率为假
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSArray *balls = [[NSArray alloc] initWithObjects:@"Blue", @"Blue", @"Blue", @"Blue", @"Red", nil];
int index = arc4random() % 5;
NSString *resultStr;
if (likely([[balls objectAtIndex: index] isEqualToString: @"Blue"])) {
resultStr = @"Blue";
} else {
resultStr = @"Red";
}
SCNSLog(@"%@", resultStr);
}
我们可以使用likely/unlikely宏来预测 x 的结果,然后通知编译器在编译时优化这个分支的汇编代码。也就是说,使用 likely(),执行 if 后面的语句的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。通过这种方式,编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。
~