一使用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"];
// 解锁
});
}
运行结果
- 分析
因为给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);
运行结果
因为一个是
NSTaggedPointerString
,一个是__NSCFString