2023년 1월 17일 • ☕️ 2 min read
autoreleasepool 개념을 Swift에서 알 필요가 있을까?
.
필요하다. 😂 그 이유를 알기 위해선 autoreleasepool 을 사용하게 된 배경을 알아야한다. Obj-C로 거슬러 올라가보자. (신기한 내용이라 이 글을 참고하여 작성했다.)
ARC가 등장하기 이전, 개발자는 직접 retain
과 release
를 삽입하여 메모리를 관리했다. 인스턴스를 참조하고 있는 포인터 개수가 0이 되어야 메모리에서 해제된다.
아래는 Label을 생성하는 메소드 예제이다.
-(NSString *)getCoolLabel {
NSString *label = [[NSString alloc] initWithString:@"SwiftRocks"];
return label;
}
NSString alloc
을 통해 retain count를 1 증가시켰고 메소드를 호출하는 곳에 사용할 수 있도록 리턴했기에 retain count가 1 증가하여 2가 된다.
하지만 위 코드는 문제가 있다. release
를 통해 메모리에서 해제해주어야 하지만, 메모리를 해제시킬 수 있는 길이 없다. 아래 코드를 보자.
-(NSString *)getCoolLabel {
NSString *label = [[NSString alloc] initWithString:@"SwiftRocks"];
[label release]; // ❌
return label;
[label release]; // ❌
}
리턴 전에 release()
를 호출한다면, label은 retain count가 0이 되어 메모리 해제되어 사용 시점에 앱이 크래시난다. 리턴 후에 release()
를 호출한다면, 실행되지 않는 코드이다.
이를 위한 해결책으로 autorelease라는 개념이 등장한다.
-(NSString *)getCoolLabel {
NSString *label = [[NSString alloc] initWithString:@"SwiftRocks"];
return [label autorelease];
}
객체의 retain count를 바로 감소시키는 대신, autorelease를 통해 pool에 저장하고, 미래에 release를 실행한다. 기본적으로 객체가 저장되는 pool은 메모리 릭없이 label을 사용하고 난 후, 해당 쓰레드의 런루프가 끝나는 시점에 해제된다. 그러므로, 위 문제점들은 해결된다.
하지만, autorelease를 사용하면 앱에 백만개 이상의 인스턴스를 한번에 저장하고 있는 경우가 생긴다. 만약 파일의 콘텐츠 크기가 너무 크다면 앱이 크래시가 날 수 있다. 이 때, @autoreleasepool
블록을 통해 방지할 수 있다. autoreleasepool은 해당 블록이 끝나면 블록 안에 있는 인스턴스들은 할당 해제되는 것을 보장한다. 아래처럼 for문 루프 한번이 끝나는 시점에 할당된 인스턴스들이 할당해제된다.
-(void)emojifyAllFiles {
int numberOfFiles = 1000000;
for(i=0;i<numberOfFiles;i++) {
@autoreleasepool {
NSString *contents = [self getFileContents:files[i]];
NSString *emojified = [contents emojified];
[self writeContents:contents toFile:files[i]];
}
}
}
autoreleasepool을 사용하지 않는다면 1000000개의 파일들을 담을 메모리 공간이 필요하지만, 이제는 메모리 사용량을 유지할 수 있게 된다.
이론적으로 알 필요 없다. Swift에서는 ARC 최적화로 인하여 위와 같이 autorelease를 통해 담아두어야할 상황이 생기지 않는다. 그리고, 순수한 Swift에서는 autorelease라는 개념이 존재하지 않는다.
그러나, 알아야한다. Objective-C 레가시 코드가 Foundation 프레임워크 내에 존재하기 때문이다.
아래 Data
initializer는 Obj-C [NSData dataWithContentsOfURL]
메소드와 브릿지되어있기 때문에 내부적으로 autorelease
가 불린다.
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
메모리 사용량 문제가 발생하므로 autoreleasepool
로 해결할 수 있다.
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
이런 레가시 코드들을 어떻게 확인할 수 있을까?
없다 😭
Allocation instrument 디버깅툴로 직접 확인하는 방법밖에 아직 찾지 못했다.
.
.