详细探究alloc到底干了什么?

1、怎么去探究alloc?

我们在xcode中按住command键点进去就进入了一下页面

+ (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
\- (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer");
\- (void)finalize OBJC_DEPRECATED("Objective-C garbage collection is no longer supported");
\- (id)copy;
\- (id)mutableCopy;

到这个页面之后,就再也无法进去了,臣妾想啊,但是要不到啊。

正确的方法是:

去苹果的开源库中去下载相应的代码,去做进一步探究。附链接

苹果开源库

当然这个也是太麻烦了。还需要各种配置才能运行起来,像我们这么“勤快的小蜜蜂”,当然是找别人配置好的呀。比如我撸过来的,可以直接用了

源码下载

2、alloc的流程

源码下载之后,我们直接最新的818运行起来,新建一个文件ELPerson。

我们断点先断上,待他停住之后,我们直接上图。

#import <Foundation/Foundation.h>
#import "ELPerson.h"
int main(int argc, const char * argv[]) {
  @autoreleasepool {
    // insert code here...
    NSLog(@"Hello, World!");
    ELPerson *per = [ELPerson alloc];
    NSLog(@"%@",per);
     
  }
  return 0;
}

然后,我们摁住command+Step into,这样就进去了。

libobjc.A.dylib`+[NSObject alloc]:
  0x10033dd60 <+0>: pushq %rbp
  0x10033dd61 <+1>: movq  %rsp, %rbp
  0x10033dd64 <+4>: subq  $0x10, %rsp
  0x10033dd68 <+8>: movq  %rdi, -0x8(%rbp)
  0x10033dd6c <+12>: movq  %rsi, -0x10(%rbp)
-> 0x10033dd70 <+16>: movq  -0x8(%rbp), %rdi
  0x10033dd74 <+20>: callq 0x1002d86f0        ; _objc_rootAlloc at NSObject.mm:1948
  0x10033dd79 <+25>: addq  $0x10, %rsp
  0x10033dd7d <+29>: popq  %rbp
  0x10033dd7e <+30>: retq  

进去之后我们就来到这个页面,这个最上面的意思就是,方法调用到了libobjc.A.dylib这个动态库里面去了,汇编代码看不懂没关系,我们继续往下看看。后面有个

callq 0x1002d86f0        ; _objc_rootAlloc at NSObject.mm:1948

很明显调用_objc_rootAlloc方法去了。这样我们就可以看到,方法调用的流程了。但是这个方法太不友好了。里面都是汇编,操作麻烦。很容易打击我们探索的积极性。所以我们直接摁住command找到alloc点进去

+ (id)alloc {
  return _objc_rootAlloc(self);
}

我们继续进去

callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);

这样就可以了 ,简单易懂。遇到if()else()这样的分支,我们直接下断点去分析就好了

前面的流程基本都是无意义的方法调用,我们直接找到

_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());
  // 一次读取类的信息以提高性能
  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);

}

我们点击instanceSize()进去之后看看实现

 inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
      return cache.fastInstanceSize(extraBytes);
    }
    size_t size = alignedInstanceSize() + extraBytes;
    // CF 要求所有对象至少为16个字节。
    if (size < 16) size = 16;
    return size;
  }

我们看看,这个地方已经加入了缓存,可能苹果觉得不加缓存太慢了,在我们新的版本中加入了缓存。下面是一个字节对齐算法。CF 要求所有对象至少为 16 个字节。

我们直接打印下,验证也可以看出来进行了字节对齐

2021-06-07 12:37:11.197898+0800 KCObjcBuild[53421:1865440] <ELPerson: 0x1018100a0>**
**(lldb)** **po extraBytes**
88
**(lldb)** **po size**
96

再看下

size_t size = alignedInstanceSize() + extraBytes;

里面的实现是

  uint32_t alignedInstanceSize() const {
   return word_align(unalignedInstanceSize());
  }
 uint32_t unalignedInstanceSize() const 
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
  }
//OC对象成员变量8字节对齐运算
static inline uint32_t word_align(uint32_t x) {
  eturn (x + WORD_MASK) & ~WORD_MASK;
}

再继续探索,就来到开辟内存

 obj = (id)calloc(1, size);

最后调用object_cxxConstructFromClass(),从基类开始递归调用,构造成功就返回我们的对象。

id 
object_cxxConstructFromClass(id obj, Class cls, int flags)
{
  ASSERT(cls->hasCxxCtor()); // required for performance, not correctness
  id (*ctor)(id);
  Class supercls;
  supercls = cls->getSuperclass();
  // Call superclasses' ctors first, if any.
  if (supercls && supercls->hasCxxCtor()) {
    bool ok = object_cxxConstructFromClass(obj, supercls, flags);
    if (slowpath(!ok)) return nil; // some superclass's ctor failed - give up
  }
  // Find this class's ctor, if any.
  ctor = (id(*)(id))lookupMethodInClassAndLoadCache(cls, SEL_cxx_construct);
  if (ctor == (id(*)(id))_objc_msgForward_impcache) return obj; // no ctor - ok
  // Call this class's ctor.
  if (PrintCxxCtors) {
    _objc_inform("CXX: calling C++ constructors for class %s", 

          cls->nameForLogging());
  }
  if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok
  supercls = cls->getSuperclass(); // this reload avoids a spill on the stack
  // This class's ctor was called and failed.
  // Call superclasses's dtors to clean up.
  if (supercls) object_cxxDestructFromClass(obj, supercls);
  if (flags & OBJECT_CONSTRUCT_FREE_ONFAILURE) free(obj);
  if (flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
    return _objc_callBadAllocHandler(cls);
  }
  return nil;
}

3、去验证内存

我们先给ELPerson添加几个属性,赋值之后断点断上,然后

(lldb)** **x per**

0x100c2c870: 75 82 00 00 01 80 1d 01 30 40 00 00 01 00 00 00 u.......0@......

0x100c2c880: 00 00 00 00 00 00 00 00 12 00 00 00 00 00 00 00 ................

也可以View Memory查看内存分布

image
lldb

至此,我们的探索流程就告一段落了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容