AI智能
改变未来

iOS:底层原理之内存管理相关


CADisplayLink、NSTimer使用注意

CADisplayLink 保证调用频率和刷帧频率一致,60 FPS, 不用设置时间间隔,每秒钟60次。
可以使用 proxy 代理解决循环引用
CADisplayLink、NSTimer 会对 target 产生强引用,如果 target 又对它们产生强引用,那么就会引发循环引用。

解决方案1.使用block

解决方案2.使用代理对象(NSProxy)

NSProxy 也属于基类

代理,用于解决循环引用,用于消息转发,不会在父类查找方法
NSObject 和 NSProxy 区别

GCD定时器

NSTimer 依赖于 RunLoop,如果 RunLoop 的任务过于繁重,可能会导致 NSTimer 不准时
而 GCD 的定时器会更加准时,GCD 定时器,不依赖 Runloop,会很准时,依赖内核。

iOS 程序的内存布局

低地址-> 高地址
保留->代码段->数据段(字符串常量,已初始化全局数据,未初始化数据)>堆->栈内存-> 内核区域

  • 代码段编译之后的代码
  • 数据段
      字符串常量
    • 已初始化的全局变量、静态变量
    • 未初始化的全局变量、静态变量
  • 堆 (低->高)
      通过 alloc malloc calloc 动态分配的内存
  • 栈 (高->低)
      函数调用开销( )

    Tagged Pointer

    • 从 64bit 开始,iOS引入了 Tagged Pointer 技术,用于优化 NSNumber、NSDate、NSString 等小对象的存储
    • 在没有使用 Tagged Pointer 之前, NSNumber 等对象需要动态分配内存、维护引用计数等,NSNumber 指针存储的是堆中 NSNumber 对象的地址值
    • 使用 Tagged Pointer 之后,NSNumber 指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
    • 当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
    • objc_msgSend 能识别 Tagged Pointer,比如 NSNumber 的 intValue 方法,直接从指针提取数据,节省了以前的调用开销NSNumber *number = @10;
    • NSLog(@“%d\”,[number intValue]);
  • 如何判断一个指针是否为Tagged Pointer?
      iOS平台:最高有效位是1(第64bit)
    • Mac平台:最低有效位是1

    OC对象的内存管理

    在 iOS中,使用引用计数来管理 OC 对象的内存
    一个新创建的 OC 对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
    调用 retain 会让 OC 对象的引用计数+1,调用 release 会让OC对象的引用计数 -1

    **内存管理的经验总结 **

    当调用 alloc、new、copy、mutableCopy 方法返回了一个对象,在不需要这个对象时,要调用 release 或者 autorelease 来释放它
    想拥有某个对象,就让它的引用计数 +1;
    不想再拥有某个对象,就让它的引用计数 -1

    可以通过以下私有函数来查看自动释放池的情况

    extern void _objc_autoreleasePoolPrint(void);

    copy 和 mutableCopy

    引用计数器的存储 retaincount

    dealloc

    autoreleasePool 自动释放池

    自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

    调用了 autorelease 的对象最终都是通过 AutoreleasePoolPage 对象来管理的

    源码分析

    • -clang 重写@autoreleasepool
    • -objc4 源码:NSObject.mm

    AutoreleasePoolPage 的结构


    调用 push 方法会将一个 POOL_BOUNDARY 入栈,并且返回其存放的内存地址

    调用 pop 方法时传入一个 POOL_BOUNDARY 的内存地址,会从最后一个入栈的对象开始发送 release消息,直到遇到这个 POOL_BOUNDARY

    id *next 指向了下一个能存放 autorelease 对象地址的区域

    runloop 和 autoreleasePool

    iOS 在主线程的 Runloop 中注册了2个Observer

    • -第1个 Observer监听了 kCFRunLoopEntry 事件,会调用 objc_autoreleasePoolPush()
  • -第2个 Observer
      监听了 kCFRunLoopBeforeWaiting事件,会调用 objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    • 监听了 kCFRunLoopBeforeExit 事件,会调用 objc_autoreleasePoolPop()
    • 点赞
    • 收藏
    • 分享
    • 文章举报

    GS-NICE发布了177 篇原创文章 · 获赞 0 · 访问量 2704私信关注

  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » iOS:底层原理之内存管理相关