一使用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
- 分析
因為給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
因為一個是
NSTaggedPointerString
,一個是__NSCFString