• 사전 지식
    • Mach-O: 런타임에 실행되는 파일들의 총징
      • executable: 애플리케이션의 메인 바이너리
      • dylib: 동적 라이브러리(다른 프레임워크에서는 DSO, DLL이라고도 함)
      • Bundle: 링크되지 못하는 Dylib. dlopen()으로 런타임에 열어야 함
    • Image: executable, dylib, bundle을 개별적으로 통칭하는 말
    • Framework: 여기서는 리소스와 헤더들을 가지는 Dylib를 의미
    • Mach-O 이미지 파일 구조
      • 파일을 세그먼트로 나눠진다. 세그먼트는 대문자료 표기한다.
      • 세그먼트는 페이지 사이즈의 배수로 나타난다.
        • arm64에서는 16KB
        • 다른 곳에서는 4KB
      • 섹션은 세그먼트를 나눈것. 소문자로 표시한다.
    • 일반적인 세그먼트들
      • __TEXT: 헤더, 코드, 읽기 전용 상수들
      • __DATA: R/W가 가능한 데이터들. 전역 변수, 정적 변수 등
      • __LINKEDIT: 프로그램을 로딩하기 위한 정보가 적혀있는 메타데이터
    • Mach-O 유니버셜 파일: 두개 이상의 아키텍쳐용 바이너리가 포함된 Mach-O
      • 이를 표현하기 위한 헤더가 추가적으로 들어간다(Fat Header)
      • Fat Header는 1페이지 크기이며 아키텍처 종류와 해당 아키텍쳐의 바이너리의 시작지점 오프셋 정보가 들어간다.
      • 툴과 런타임이 모두 유니버셜을 지원해야 한다.
    • 굳이 페이지 단위로 관리해야되나? 메모리 낭비인데? -> 가상 메모리 때문에
    • 가상 메모리
      • 물리 메모리를 직접 사용하지 않게 하는 것
      • 프로세스 주소(논리 주소)를 물리 주소로 변환하기 위한 테이블(Map)을 가진다.
      • 페이지 폴트: 원하는 페이지가 물리 메모리에 없는 현상. 페이지 교체를 일으키며 이 때 스레드가 멈춘다.
      • 페이지 공유: 프로세스사이에서 페이지의 내용을 공유할 수 있다.
      • 파일의 페이지 지원: mmap을 이용하면 페이지 단위로 파일을 읽을 수 있어서 전체 파일을 한꺼번에 메모리에 올리지 않고도 파일을 읽을 수 있다.
      • CoW지원
      • 퍼미션 지원
    • dylib 보안
      • ASLR: Address Space Layout Randomization, 이미지를 랜덤한 곳에 로드한다.
      • Code Signing: 각 페이지의 내용을 해싱한다. 이 해시값은 _LINKEDIT에 들어가며, 페이지가 로딩될 때 변조 여부를 체크할 수 있다.
    • main() 이전에 무슨 일이 일어나는가?
      • exec(): 커널이 어플리케이션을 새로운 주소공간에 매핑해준다.
        • 시작 주소는 랜덤이다.(ASLR)
        • 시작 주소 이하의 값은 접근 불가능하게 설정된다(rwx 권한 모두 없음)
          • 32bit에서는 4KB 이하
          • 64bit에서는 4GB이하
          • Null포인터 참조, 포인터 잘림 오류 등을 캐치하기 위해 사용
      • 처음에는 공유 라이브러리가 없어서 쉬웠으니 이후 공유 라이브러리가 나오면서 상황은 복잡해졌다. -> 공유 라이브러리를 로딩하기 위한 별도 프로그램을 만들어서 로딩한다(dyld, 다른 플랫폼에서는 LD.SO)
        • 앱이 로딩되면, dyld가 로딩된다.
        • dyld는 프로세스와 같이 돌아가면서 의존성이 있는 dylibs를 로딩해준다.
        • dyld는 앱과 동일한 퍼미션을 가져간다.
      • dyld의 동작과정
        1. Load Dylibs
          • dylib 리스트를 파싱한다.
          • 필요한 dylib를 찾아서 읽는다.
          • 이때 dylib을 검증하고, 코드 사인 확인까지 하고 나서야 mmap으로 메모리에 매핑을 하게 된다.
          • 직접적으로 dylib를 로딩하면, 각 dylib의 숨겨진 의존성까지 모두 로딩한다.
          • 일반적으로 앱 하나가 100~400개 정도의 dylib을 로딩한다.
            • 대부분은 OS에서 제공하는 dylib
            • OS가 제공해주는 것은 로딩이 최적화가 되어있다.
        2. Rebase, Bind, ObjC 단계 -> 앱에서 dylib의 코드를 정상적으로 호출할 수 있도록 바인딩해주는 단계
          • 그런데 한 dylib에서 다른 dylib를 호출하려고 하면 문제가 된다. -> 코드 사인 때문에 dylib을 변경할 수가 없기 때문에..
          • 그래서 code-gen(현재는 dynamic PIC(Position Independent Code))를 사용한다.
            • 코드는 어떤 주소에도 로딩될 수 있고, 변경되지 않는다.
            • 그래서 데이터 영역에 실제 주소를 담은 테이블을 만든다.
            • rebase: 이미지 내에서의 포인터값 조정
              • 이미지 로딩 위치에 따른 오프셋 차이를 보정해준다.
                • slide(actual_address - preferred_address)값을 더해줘서 다시 쓰는 것
              • 이러한 포인터들의 위치는 또 __LINKEDIT영역에 인코딩되어 있다.
              • 데이터 영역이 바뀌기 때문에 더티 페이지가 된다. 따라서 IO 비용이 생기는데, 이러한 작업은 순차적으로 일어나기 때문에 프리패칭이 가능하다.
            • binding: 포인터를 외부 이미지로 설정하는 것
              • Dyld가 심볼 이름을 찾아서 포인터를 설정해준다.
              • 심볼을 찾는 과정이 복잡하기 때문에 rebasing보다 계산량이 많다.
                • 다만 이미 iO는 거의 끝났고, 페이지 폴트도 거의 일어나지 않는다.
            • Objc 설정
              • Objc 관련 설정은 대부분 rebasing과 binding이 해결해준다. -> 하지만 몇가지 추가적으로 더 해줘야 될 것이 있다.
                • 클래스를 이름으로 지정해서 인스턴스화할 수 있기 때문에, 글로벌 테이블에 클래스 이름을 등록해놓는 과정이 필요하다.
                • 카테고리로 인해 추가된 메소드들을 리스트에 넣는다.
                • fragile base class problem은 일어나지 않는다.(offset을 수정해주기 때문에) -> 참조 url(Dynamic ivars: solving a fragile base class problem, http://www.sealiesoftware.com/blog/archive/2009/01/27/objc_explain_Non-fragile_ivars.html)
              • 셀렉터는 유니크해야 한다.
            • Initializer: 동적으로 수정하는 과정 -> 정적으로 할 수 있는 것 이외의 모든 작업을 수행한다.
              • c++은 생성자를 여기서 만든다.
              • Objc은 +load 메소드를 호출한다(deprecated되긴 했지만…)
              • 이러한 초기화 과정은 bottom-up으로 이루어진다.
  • 실전 -> 결론만 말하면 실행전에 할 일을 줄여야 된다.
    • 대략 400ms 정도의 시간이 적당하다. 20초가 넘어가면 OS가 앱을 죽인다.
      • 가장 느린 디바이스에서 측정해볼것
    • 정리: 앱 실행전에 일어나는 작업 -> 참고 영상(iOS App Performance: Responsiveness - WWDC 2012 - Videos - Apple Developer)
      • 이미지 파싱
      • 이미지 매핑
      • 리베이싱
      • 바인딩
      • 이미지 초기화
      • main() 호출
      • UIApplicationMain 호출
      • applicationWillFinishLaunching호출
    • Warm vs Cold Launch
    • 어떻게 측정할것인가? -> 앱의 코드가 실행되기 전을 측정해야 한다.
    • 시작시간 최적화
  • 참조 문서: Apple Developer Documentation