MRR(Manual Retain-Release) 붐은 온다. a.k.a 메모리 관리


https://github.com/jaemyeong/MRR

응용프로그램에서 메모리 관리란 프로그램이 실행되는 동안 메모리를 할당하고, 사용하고, 작업이 끝나면 메모리를 해제하는 과정을 의미한다. Objective-C에서는 소유권 개념으로 메모리를 관리하고 있다.

Objective-C는 두가지 방법으로 메모리 관리를 할 수 있는데, 런타임 환경에서 NSObject의 참조 카운팅(Reference Counting)을 명시적으로 추적 관리하는 MRR 방법과 컴파일할 때 미리 정해진 규칙으로 메모리 관리 코드를 삽입해주는 ARC 방법이 있다.

Xcode로 프로젝트를 생성하면 기본적으로 ARC 환경으로 설정된다. 왜냐하면 ARC 개념이 2011년도에 발표되었으므로…ㅋㅋㅋ

하지만 여기서는 MRR을 사용하여 프로젝트를 생성해 볼 것이다. 이유는 없다 그냥 다시 한 번 향수를 느끼고 싶달까..

프로젝트 생성

일단 프로젝트 언어를 Objective-C로 설정하여 생성한다.

이제 프로젝트의 빌드 설정으로 가서 ARC 설정을 NO로 변경한다.

이제 해당 프로젝트의 메모리 관리는 메뉴얼로 진행해야한다.

해당 프로젝트를 실행하면 바로 앱이 충돌이 나며 종료될 것이다. 이유는 빌드 설정을 MRR에 맞도록 변경했으나 아직 템플릿 코드는 ARC에 맞추어져 있어서 메모리 관리가 제대로 되지 않아 충돌이 발생하는 것이다.

먼저 앱의 엔트리포인트인 main.m 파일을 편집한다.

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

코드를 해석하면

  1. strong 변수 appDelegateClassName을 선언한다.
  2. autoreleasepool을 생성.
  3. AppDelegate의 클래스 타입으로 NSString 문자열 생성 (참조 카운팅 1, autorelease 대기)
  4. appDelegateClassName 변수에 값 대입 (참조 카운팅 1 증가)
  5. autoreleasepool 종료 되면서 autorelease 대기중이던 객체들 모두 릴리즈 (참조 카운팅 1 감소)
  6. UIApplicationMain 실행
  7. UIApplicationMain 함수가 종료되고 값을 반환 하면서 main 함수의 컨텍스트 종료
  8. strong 변수 appDelegateClassName 에 할당된 자원 회수 (참조 카운팅 1 감소)

순서로 진행되는데 MRR 환경에서는 strong 변수 개념이 없으므로 appDelegateClassName 변수에 값을 대입 할 때 자동으로 참조 카운팅을 증가 시켜주는 코드가 들어가지 않는다. 따라서 UIApplicationMain을 실행하는 시점에 appDelegateClassName 변수가 가지고 있는 객체의 참조 카운트가 0이 되면서 해당 값으로 UIApplicationMain 함수를 실행 하려고 하기 때문에 충돌이 나고 종료 되는 것이다.

이제 위 코드를 MRR에 맞게 변경하면 다음과 같다.

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

그냥 모든 코드를 autoreleasepool 블럭에 넣고 실행하면 끝이다. 애초에 현재 버전의 Xcode에서 Objective-C 템플릿이 왜 저렇게 변경되었는지 모르겠지만 옜날에는 위와 같은 모습이였다.

NSStringFromClass 함수로 반환된 값이 autorelease 대기 상태이고 해당 값을 사용하여 UIApplicationMain 함수를 실행하고 함수가 종료되면 해당 값을 리턴 하면서 autoreleasepool 블럭이 종료되면서 autorelease 대기중이던 자원이 모두 회수된다. 깔끔하다.

iOS 바닥에서 고이다 못해 썩은 분들은 아래와 같은 모양의 main 함수도 많이 보았을 것이다. ㅋㅋㅋ

int main(int argc, char * argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    int ret = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    
    [pool drain];
    
    return ret;
}

이제 이 프로젝트로 무엇을 만들어본다?…🤔