참조
https://medium.com/codex/how-xcode-work-when-we-build-the-ios-app-project-95fe95f0b17
위 글을 보고 정리한 글. 자세한 내용은 위 글 참조 wind
나는 Xcode가 소스 코드에서 실행되어 실행 가능한 앱(.ipa)이 되기까지 어떤 과정을 거치는지 잘 몰랐었고 궁금하였다. 이 글을 보면서 궁금증이 어느 정도 해결 될거라 생각한다.
Xcode의 메뉴를 보면 Show the report navigator
라는게 있고 내부엔 빌드라고 불리는 프로세스가 존재한다. 이것을 탭하면 빌드 준비에서 프로젝트 대상 빌드까지의 많은 프로세스가 표시된다. 이 글에서는 빌드 대상 프로세스의 단계, 특히 컴파일 및 링크라고 하는 프로세스에 대해서만 설명하겠습니다. Xcode 빌드 시스템에서 자신의 빌드 시스템으로 마이그레이션하려는 경우 매우 기본적인 개념이기 때문입니다.
참고로, 스위프트는 다른 프로그래밍 언어와 마찬가지로 컴파일러와 링커 프로세스는 동일하지만 스위프트 내부에 몇 가지 추가 프로세스가 있기 때문에 내부 컴파일러 프로세스가 다른 언어보다 복잡합니다. 이 부분은 여기서는 다루지 x
Swift는 컴파일 프로세스에서 프론트엔드와 백엔드의 2차원을 가집니다.
이는 일반적인 다른 언어처럼 어휘 분석, 구문 분석 및 의미론적 분석을 수행합니다. 이 프로세스의 예에서 Swift 코드는 프로그램의 구조화된 표현으로 변환하기 위해 파서에 전달될 텍스트 및 토큰으로 어휘 분석에 의해 취해집니다. 그리고 이 표현은 또 의미론적 분석으로 전달된 후 백엔드 프로세스로 전달됩니다.
Swift는 최적화 및 코드 생성 단계를 처리하기 위해 백엔드에서 정말 스마트한 작업을 수행합니다. 컴파일러는 최적화 및 이진 생성을 위해 LLVM을 사용합니다 Swift 코드가 AST(Abstract Syntax Tree) 형태로 파싱되면 의미 분석을 거쳐 SIL(Swift Intermediate Language)로 변환됩니다.
이 코드는 LLVM IR(Intermediate Representation)과 함꼐 분석 및 최적화를 거칩니다. 여기서 LLVM은 원래 Swift 코드를 어셈블리 코드로 변환하고 최종적으로 바이너리 파일로 끝나고 실행 가능한 앱으로 연결됩니다.
간단히 말해서 기본적으로 .swift
파일은 컴파일러 프로세스에서 LLVM에 의해 어셈블리 파일로 변환되고 최종적으로 실행 가능한 앱을 만드는 데 사용되는 바이너리 파일(.o
)이 됩니다.
바이너리 파일에서 실행 가능한 앱을 만드는 방법은 무엇인가요 ? 여기에는 연결 프로세스(링커)가 필요합니다. 링커는 하나 이상의 개체 파일을 단일 바이너리 또는 실행 파일로 결합하는 컴퓨터 시스템 프로그램입니다.
퍼즐을 상상해보세요. 전체 이미지가 보일 떄 까지 각 조각을 올바른 위치에 결합해야 합니다. 이 경우 퍼즐 조각을 결합하는 사람은 링커이고 퍼즐 의 각 조각이 Object File
이라는 것을 의미합니다.
예제 코드를 살펴보겠습니다.(링크 참조)
4개의 소스 코드를 생성하고 모든 파일을 컴파일해 봅시다.
swiftc main.swift John.swift Bob.swift People.swift -o Executable.out
이 명령에서 모든 파일을 작성하고 -o
뒤에 실행 파일 이름을 정의해야합니다. -o
는 출력을 의미하고. 이 명령을 실행하면 실행 간으한 앱의 이름이 Executable.out
으로 지정됩니다.
(내부 프로세스를 살펴보려면 -v
를 추가하면 됩니다.) 내부에서는 차례로 컴파일을 진행하고, 마지막에 모든 파일을 연결합니다.
안타깝게도 이 명령을 사용하면 코드를 변경할 때마다 위 모든 파일을 다시 컴파일해야 합니다. 그래서 하나의 모듈로 하나씩 분리해서 컴파일할 수 있기 때문에 하나의 파일에 변경 사항이 있으면 변경 파일이 있는 하나의 모듈만 컴파일합니다
이것은 또한 내부에 큰 파일이 있는 모놀리식 iOS 앱 프로젝트가 있고 작은 변경사항을 생성할 때 컴파일 시간이 오래 걸리는 이유이기도 합니다. 앱 프로젝트내의 모든 파일을 다시 컴파일 하기 때문입니다. 해결책은 이러한 파일을 모듈로 분리한다는 것입니다.
아래는 각 컴파일을 분리하려는 경우의 예입니다.
swiftc -c People.swift
이 친구는 종속성이 전혀 없기 때문에 이 명령을 직접 사용할 수 있습니다.
swiftc -c John.swift People.swift -module-name MyApp
John.swift
는 People.swift
와 종속성이 있으므로 명령에서 종속성 파일을 선언하고 하나의 모듈로 그룹화해야 합니다.
이 명령의 순서는 중요합니다. 첫 번째 순서는 개체 파일로 변환될 swift파일이고 나머지는 파일 종속성입니다.
swiftc -c Bob.swift People.swift -module-name MyApp
이 친구의 종속성도 위와 동일합니다. 따라서 동일하게 작성해줍니다.
swiftc -c main.swift Bob.swift John.swift People.swift -module-name MyApp
마지막으로 main.swift
입니다. 이 클래스에서는 전부 다 종속성이 있기 때문에 위와 같이 적어줍니다.
이제 저희는 main.o, John.o, Bob.o, People.o
4개의 오브젝트 파일을 얻었지만 프로그램을 실행하거나 볼 수 없기 때문에 하나의 실행 파일로 연결해야 합니다.
이러한 모든 바이너리 파일을 연결하려면 ld or swiftc
명령어를 사용합니다.
(ld
는 좀 더 복잡합니다.. 여기서는 swiftc
를 사용 !)
swiftc main.o People.o Bob.o John.o -o Executable.app
위 과정이 Xcode에서의 컴파일과 링킹 과정입니다 !