iOS的内存管理和runloop

原创文章
声明:作者声明此文章为原创,未经作者同意,请勿转载,若转载,务必注明本站出处,本平台保留追究侵权法律责任的权利。
全栈老韩
全栈工程师,擅长iOS App开发、前端(vue、react、nuxt、小程序&Taro)开发、Flutter、React Native、后端(midwayjs、golang、express、koa)开发、docker容器、seo优化等。

内存管理 & Runloop

一、内存管理

  1. 自动引用计数Related Count会自动给对象加retain,release.
  2. 函数是objc_retain
复制代码
objc_retain(id _Nullable objc)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
  • 传进需要引用计数的对象
  • isa指针是不是 TaggedPointer
  • TaggedPointer针对小类型,当我们存储,比如NSNumber、NSString,字符串的长度小于固定长度时,我们使用TaggedPointer来优化它们的isa指针.目的是,存储的更高效.
  • 并不是所有对象的isa指针类型都是一样的
  • 所以对于NSNumber、NSString不用进行内存引用计数加1,内存管理语义是不一样的
  • isa指针,64位,如果都用来存储地址,是不明智的。还存储了别的信息.
  • retain函数
复制代码
objc_object::retain()
{
  assert(!isTaggedPointer());
  if (fastpath(!ISA()->hasCustomRR())) {
      return rootRetain();
  }
  
  return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
  • rootRetain()
复制代码
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
....
}
  • 操作了sideTable:
    是HashTable,存储的是我们当前对象的引用计数,弱引用表,锁

  • 执行了函数

复制代码
id objc_object::sidetable_retain()
{
...
}

SideTable的定义

复制代码
*   // Template parameters.
    enum HaveOld { DontHaveOld = false, DoHaveOld = true };
    enum HaveNew { DontHaveNew = false, DoHaveNew = true };

    struct SideTable {
      spinlock\_t slock;
      // 引用计数,也是一个hash表
      RefcontMap refcnts;
      // 弱引用表,hash表
      weak\_table\_t weak\_table;
      SideTable() {
          memset(&weak_table, 0, sizeof(weak_table));
      }
      
      ~SideTable() {
          _objc_fatal("Do not delete SideTabel.");
      }
      
      void lock() { slock.lock(); }
      void unlock() { slock.unlock(); }
      void forceReset() { slock.forceReset(); }
      
      // Address-ordered lock discipline for a pair of side tables.
      
      template<HaveOld, HaveNew>
      static void lockTwo(SideTabel *lock1, SideTabel *lock2);
      template<HaveOld, HaveNew>
      static void unlockTwo(SideTabel *lock1, SideTabel *lock2);
}
  • 引用计数加1

    // 位移运算
    refcntStorage += SIDE_TABLE_RC_ONE;

这里的1,是经过位移后的1.

weak:

  • Runtime维护了一张全局的弱引用表
  • 调用了id objc_initWeak(id *location, id newObj)函数,location是地址,然后其中调用了
    storeWeak方法来存储
  • 根据传进来的newobj来操作SideTabel来操作.
  • 在弱引用表中插入的是weak_entry_t,找到表起始地址,while循环找到我们存储的对象.
  • 有性能情况,不断的通过hash运算来查询、插入、删除等.
  • 总之,weak就是通过hash运算找到弱引用表,插入或者删除
  • dealloc: 自动清理弱引用表.

Runloop

  1. 使用NSRunLoop currentLoop,是对CFRunLoop的封装.
复制代码
struct __CFRunLoop {
  ...
  // 唤醒
  // mach_port内核通讯
  __CFPort _wakeUpPort; // used for CFRunLoopWakeUp
  ...
  pthread_t _pthread;
  ...
  CFMutableSetRef _commonModes;
  // timer,source, observer
  CFMutableSetRef _commonModeItems;
  CFRunLoopModeRef _currentMode;
  ...
}
  1. 线程和RunLoop一一对应.
  2. CommonMode并不是mode类型,而是一个特殊标识
复制代码
struct __CFRunLoopMode {
    ...
    CFMutableSetRef _source0; // 不具备主动唤醒线程能力,手动唤醒线程。可创建并添加到当前的runloop中的
    CFMutableSetRef _source1; // 基于port端口事件,可主动唤醒线程
    ...
}

示例解说

复制代码
Dispatch\_async(dispatch\_get\_main\_queue(), ^{
  NSLog(@"1");
  [self performSelector:@selector(test) withObject\:nil afterDelay:0];
  NSLog(@"3");
})

(void)test {
  NSLog("current Thread -- %@", \[NSThread currentThread]);
  NSLog(@"2");
}

结果输出:

复制代码
1 
3 
current thread -- <NSThread: ..>{number = 1, name = main}
2
  • 查看performSelector的注释,就是往当前线程中的RunLoop中注册了一个NSTimer.
  • performSelector要求, 运行在default mode中,并且当前的runloop是执行的
  • 主线程默认runloop开启
  • 当runloop回来的时候,timer时间到了,再去执行selector,也就是performselector就是注册了一个timer,然后等待timer时间到了,回到default mode中时,再执行selector.

注:如果是在dispatch_get_global_queue中时,输出:

复制代码
1 
3

原因:
global队列的runloop默认不开启.

暂无评论,快来发表第一条评论吧