iOS-底层原理(24)-内存管理之面试题

一使用CADisplayLink、NSTimer有什么注意点?
  • 循环引用

范例代码

  • CADisplayLink
@property (strong, nonatomic) CADisplayLink *link;

// 1.发生内存泄露
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

- (void)linkTest {
    NSLog(@"%s", __func__);
}
  • NSTimer
@property (strong, nonatomic) NSTimer *timer;

// 1.会内存泄露
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

- (void)timerTest {
    NSLog(@"%s", __func__);
}
二 介绍下内存的几大区域
  • 代码段:编译之后的代码

  • 数据段

    • 字符串常量:比如NSString *str = @"123"
    • 已初始化数据:已初始化的全局变量、静态变量等
    • 未初始化数据:未初始化的全局变量、静态变量等
  • 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小

  • 堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大

三 讲一下你对 iOS 内存管理的理解
四 ARC 都帮我们做了什么?
  • LLVM + Runtime

首先利用LLVM,帮我们自动生成release,retain,autorelease代码
需要runtime运行时做一些事情
即ARC时LLVM编译器和Runtime系统相互协作的一个结果

五 weak指针的实现原理

将弱引用存储到一个哈希表里,当对象要销毁时,就会取出当前对象的弱引用表,将该表存储的弱引用都给清除掉

六 autorelease对象在什么时机会被调用release

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

  • 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer
    • 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()
    • 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

代码例子如下

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [[Person alloc] init];
    NSLog(@"%s", __func__);
}

这个Person什么时候调用release,是由RunLoop来控制的
它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
Person *person = [[[Person alloc] init] autorelease];

6.1 包含在@autoreleasepool中,则在pop的时候,即@@autoreleasepool作用域结束的时候销毁。
七 方法里有局部对象, 出了方法后会立即释放吗
  • MRC环境下
    不一定,是在当前runloop循环中,即将进入休眠时释放

  • ARC环境下

autorelease对象:在它所在的线程对应的本次runloop即将进入休眠时释放

非autorelease对象:出了作用域就释放

八 思考以下2段代码能发生什么事?有什么区别?
@property(nonatomic,strong)NSString *name;
// @property(nonatomic,copy)NSString *name;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        // 加锁
        self.name = [NSString stringWithFormat:@"abcdefghijk"];
        // 解锁
    });
}

运行结果

image.png
  • 分析

因为给self.name赋值,实际上是调用其set方法

- (void)setName:(NSString *)name {
    if (_name != name) {
        [_name release];
        _name = [name retain];
    }
}

set方法内部,会先执行release操作,然后再执行retain操作,如果是多个线程同时执行set方法,则会造成释放2次的情况,所有导致坏内存访问。

代码二

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        self.name = [NSString stringWithFormat:@"abc"];
    });
}

执行结果:正常访问没有奔溃报错。

  • 分析上面两个为什么会出现不同的执行结果
NSString *str1 = [NSString stringWithFormat:@"abc"];
NSString *str2 = [NSString stringWithFormat:@"123abc11111111"];

NSLog(@"%@ %@", [str1 class], [str2 class]);
NSLog(@"%p %p", str1,str2);

运行结果

image.png

因为一个是NSTaggedPointerString,一个是__NSCFString


项目连接地址- MemoryManage-CADisplayLinkNSTimer


本文参考MJ底层原理教程,非常感谢


  • 多多点赞,打赏更好,您的支持是我写作的动力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,157评论 1 32
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,667评论 8 265
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,785评论 18 399
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型...
    龍飝阅读 2,219评论 0 12
  • 你用动作揭示许多悬念 那细腻而精致的手 在空中蝶样飞舞 牵引我忧伤的眼睛 在无边的默默黑夜里 我都无力阻止 别伪装...
    MEIGUO27阅读 323评论 0 1