// Using the network fetcher
@implementation EOCClass {
EOCNetworkFetcher *_networkFetcher; // self -> fetcher
NSData *_fetchedData;
}
- (void)downloadData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data){ // fetcher -> block
NSLog(@"Request URL %@ finished", _networkFetcher.url);
_fetchedData = data; // block -> self
}];
}
@end
retain cycle 이 생기기 때문에 memory leak 이 발생한다.
이를 해결하는 방법은? 하나를 nil 로 만들어버리면 된다.
- block 안에서 fetcher를 nil 로 만들어버리면 cycle 이 끊긴다
- fetcher 안에서 block 을 실행한 후 block 자체를 nil 로 만들어버려도 cycle 이 끊긴다
그런데 만약 block 을 public 으로 공개했다면? 나중에 내 코드를 사용하게 되는 사람이 block 을 직접 nil 로 만들어야 한다.
그리고 사용자가 그렇게 잘 해주리라는 보장이 없다. 그러므로 조심조심
// Using the network fetcher
@implementation EOCClass {
EOCNetworkFetcher *_networkFetcher; // self -> fetcher
NSData *_fetchedData;
}
- (void)downloadData {
NSURL *url = [[NSURL alloc] initWithString:@"http://www.example.com/something.dat"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
__weak EOCClass *weakSelf = self;
[_networkFetcher startWithCompletionHandler:^(NSData *data){ // fetcher -> block
NSLog(@"Request URL %@ finished", weakSelf.networkFetcher.url);
weakSelf.fetchedData = data; // block -> self (하지만 약한 참조)
}];
}
@end
그런데 만약...
[_networkFetcher startWithCompletionHandler:^(NSData *data){ // fetcher -> block
NSLog(@"Request URL %@ finished", weakSelf.networkFetcher.url);
// 여기까진 weakSelf 가 살아있다가
// 갑자기 dealloc 이 되어서 (View Controller 가 dismiss 됐다거나) weakSelf 가 nil이 되었다면?
// 앞에 까지만 실행되고 여기부터 실행되지 않을 것이다.
weakSelf.fetchedData = data; // block -> self
// 그렇다고 crash 가 나는 건 아님. weakSelf->fetchData 같은 문법을 쓰면 crash가 나지만 . 문법은 괜찮다.
// 그러나 중간까지만 실행되고 갑자기 끝나는 건 확실히 정상적인 작동은 아니다.
}];
@end
그러니까 이렇게 되어야 한다.
[_networkFetcher startWithCompletionHandler:^(NSData *data){ // fetcher -> block
EOCClass *strongSelf = weakSelf;
if (strongSelf == nil) { return; }
// block -> self (약한 참조. 따라서 self 가 dealloc 되었을 가능성이 있다)
// dealloc 되었으면 그대로 종료.
// 그렇지 않으면 self 가 예기치 못하게 dealloc 되는 걸 막기 위해 retain 한다.
NSLog(@"Request URL %@ finished", strongSelf.networkFetcher.url);
strongSelf.fetchedData = data;
}];
@end
근데 또 만약
[_networkFetcher startWithCompletionHandler:^(NSData *data){ // fetcher -> block
EOCClass *strongSelf = weakSelf;
if (strongSelf == nil) { return; }
[strongSelf.dataManager insertData:data withCompletionHandler:^(Bool completed){
// 여기서 self 를 참조하고 싶다면? 가령 self 의 completed 라는 property 에 저장하고 싶다거나?
}];
}];
@end
1) strongSelf
2) EOCClass *newStrongSelf = weakSelf;
3) __weak EOCClass *newWeakSelf = strongSelf; { EOCClass *newStrongSelf = newWeakSelf; }