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环境下
    马上释放,只要出了作用域就释放
八 思考以下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"];
        // 解锁
    });
}

运行结果

iOS-底层原理(24)-内存管理之面试题_第1张图片
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底层原理教程,非常感谢


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

你可能感兴趣的:(iOS-底层原理(24)-内存管理之面试题)