知识点概要
引入
alloc干了什么?
为什么要有init?
为什么要有new?直接使用有何缺陷?
[NSObject alloc]为什么没有进入alloc方法?
引入
Person *p1 = [[Person alloc]init];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
NSLog(@"%@ - %p - %p",p1,p1,&p1);
NSLog(@"%@ - %p - %p",p2,p2,&p2);
NSLog(@"%@ - %p - %p",p3,p3,&p3);
p1、p2、p3结果相同吗?&p1、&p2、&p3结果相同吗?
答案是:前者相同,后者不同,输出结果如下:

我们来回顾C语言指针的知识:
int temp = 3;//定一个int类型变量
int *a = &temp;//声明一个int类型的指针,指向的地址是temp的内存地址
//直接输出a和直接输出&temp的结果一样,都是temp的内存地址,而&a表示的是a的内存地址,因为指针类型也是一个变量,需要空间去存储
printf("%p,%p,%p\n",a,&a,&temp);
那么,对于&p1、&p2、&p3结果不相同就可以明白了,话不多说,上图:

至于p1、p2、p3调用init之后结果相同,是接下来要探索的问题。
alloc干了什么?
怎么查看?
1.下符号断点
1)我们知道会调用alloc方法,那么,先打上alloc符号断点

可以看到,会调用NSObject类中的alloc方法,因为我们自定义的类继承自NSObject
2)在上一步中,可以看到jmp指令,会进入_objc_rootAlloc,所以,继续为_objc_rootAlloc打上断点

3)继续按照之前的方式,符号断点_objc_rootAllocWithZone

4)可以看到会调用callq指令,调用了runtime中的instanceSize、calloc方法

调用instanceSize方法,计算需要开辟多大的内存,接着会调用calloc开辟这样一个大小的空间。最后,会调用initInstanceIsa方法,将对象与这个空间关联
2.根据符号断点查找库,然后,去苹果开源库下载源码,看源码,下面介绍三种方法查看。
1)第一种就是符号断点
2)在你要进入的位置,打上断点,然后,按住control键,点击调试按钮的“step into”,多点几次,会跳入另外一个断点


打上符号断点,点击下一步,如下图,我们就知道库名是objc

3)打上断点,debug选为汇编模式,找到对应的方法objc_alloc,打符号断点


总结:
1.通过符号断点,可以看到alloc的流程是:
alloc-->_objc_rootAlloc-->_objc_rootAllocWithZone-->retrun obj
_objc_rootAllocWithZone方法会调用核心的三个方法:
instanceSize:计算空间大小,并进行16字节对齐
calloc:根据大小,开辟一个这样的内存空间
initInstanceIsa:将开辟的内存空间与对象关联
注意:上述的三个核心方式其实不在_objc_rootAllocWithZone里面调用,而是在_class_createInstanceFromZone方法中调用的,原因稍后再说。
2.通过源码,调用流程如下:
alloc—>_objc_rootAlloc—>callAlloc—>_objc_rootAllocWithZone-->_class_createInstanceFromZone
可以看到,callAlloc、_class_createInstanceFromZone没有被调用。因为在编译时,编译器会根据指令进行优化,直接进入_objc_rootAllocWithZone

从代码及注释中都可以看到,调用时,会被优化
3.一个对象的大小,与其属性有关,属性越多,占用的空间越多,且按照16自字节的方式对齐(现在的新版本都是16字节对齐,之前是8字节对齐。为什么要对齐?读取按照一定字节读取,不去计算,会更加快速安全)。注意,没有属性的对象,最小占用16字节大小,
init干了什么?为什么要有init?
我们直接上源码


可以看到,直接将对象返回,啥也没有做,不是我们想的会初始化空间值。
为什么要有init?
1.提供一个方法,对外给开发者使用,可以让开发者定义自己个性的初始化内容。
2.单一职责原则,与开辟内存空间功能分开
为什么要有new?有何缺陷?
new是一个类方法,调用简单,实际是alloc+init。
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
缺陷是:我们自定义了init,使用new不会调用我们自定义的init方法,造成一个对象的创建出问题。例如
- (instancetype)initWithName:(NSString *)name
{
self = [super init];
if (self) {
self.className = name;
}
return self;
}
探讨[NSObject alloc]为什么没有进入alloc方法?
其实,无论是NSObject还是它的子类,调用alloc或者[[XXX alloc]init],优先调用以下代码:
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
那么,我么进入callAlloc方法看看
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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));
}
我们知道,现在是objc2.0,所以会进入第一个分支。首先不为nil,其次调用cls->ISA()->hasCustomAWZ(),看源码:
#if FAST_CACHE_HAS_DEFAULT_AWZ
bool hasCustomAWZ() const {
return !cache.getBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
cache.setBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
cache.clearBit(FAST_CACHE_HAS_DEFAULT_AWZ);
}
#else
bool hasCustomAWZ() const {
return !(bits.data()->flags & RW_HAS_DEFAULT_AWZ);
}
void setHasDefaultAWZ() {
bits.data()->setFlags(RW_HAS_DEFAULT_AWZ);
}
void setHasCustomAWZ() {
bits.data()->clearFlags(RW_HAS_DEFAULT_AWZ);
}
#endif
判断本类是有默认alloc/allocWithZone方法。很显然,NSObject实现了,所以,直接调用_objc_rootAllocWithZone方法创建了对象。当然了,如果是子类,而子类没有默认alloc/allocWithZone方法,则会走消息发送objc_msgSend,调用alloc方法,源码如下:
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
走消息发送,最终会调用父类NSObject的alloc方法。此时,流程和之前流程一样,callAlloc->_objc_rootAllocWithZone
总结
1.为什么明明调用是alloc方法,实际调用的是objc_alloc方法?此时此刻,不要在心里一万头草泥马飘过,冷静下来。自己没有更改,源码中也没有更改,那么,答案就只有一种了,编译器做了优化。在这里可以在LLVM源码中找到答案,具体过程就不说了,太难了,知道就行。
2.在不考虑优化的情况下,alloc的详细流程如下:

网友评论