Xcode에서 Project를 생성하면, 하나의 App Target이 자동으로 생성된다.
이때의 App Target은 하나의 Module이 된다.
별도의 Framework를 추가하지 않는 이상,
모든 코드는 Single Target에 추가되게 된다.
위와 같이 하나의 Module로 이루어진 App을 "Monolitic App"이라 부른다.
이러한 Monolitic App은 여러 가지 단점을 가지고 있다.
하나의 모듈에 모든 소스코드가 들어있기 때문에 internal과 public을 구분할 수 없다.
이로 인해, 객체들은 무분별하게 참조될 가능성이 있으며,
이는 코드 변경의 영향 범위를 파악하기 어려워진다.
또한, 특정 기능만 테스트하고 싶다 해도 Project내의 모든 소스코드를 빌드해야 한다.
App의 규모가 점점 커질 수록 빌드시간은 더욱더 늘어가게 될 것이다.
이는 결국 생산성 저하로 이어질 수 있다.
즉, 우리는 "모듈화"를 통해
같은 기능의 코드는 묶어 응집도를 높이고 다른 모듈 간의 결합도를 낮춰야 한다.
Swift에서 모듈화를 진행하는 방법으로는 크게 3가지가 있다.
- Library
- Framework
- Swift Package
사전 지식
Swift에서 모듈화를 진행하는 방법에 대해 알아보기 앞서,
미리 알아두면 좋은 개념들을 먼저 설명하고자 한다.
Target
네트워킹이 있는 App에서 테스트시에는 실제 서버가 아닌,
테스트용 서버를 통해 네트워킹을 진행할 필요가 있다.
이때, 테스트와 배포용 각각 Project를 생성하기에는 비효율적이다.
따라서, 이럴때 Project 내에서 Target을 추가하여 관리한다.
Target은 위와 같이 "Single Product"를 정의한다.
Target을 Module과 혼용하여 사용하는 경우도 있지만,
Module은 Grouping의 초점을 맞춘다면,
Target은 Build의 목적 혹은 목표에 초점을 맞춘다.
또한, Target이 App, Framework, Library라면 Module이 될 수 있지만,
Resource Bundle과 같이 Source를 포함하지 않는 경우 Module이 되지 않는다.
하나의 Xcode Project 혹은 Workspace에서는 여러 개의 Target을 가질 수 있는데,
Target은 특정 product를 build하기 위한 여러 instructions들을 가지고 있다.
이 Instruction에는 Build Settings와 Build Phase가 있다.
Build Settings는 Project로부터 상속받지만, Override가 가능하다.
Scheme
Project에선 한 번에 하나의 Target만 Active 할 수 있는데,
Scheme는 어떤 Target을 어떻게 Build할지에 대한 정보를 가지고 있다.
Project
Project는 내부의 모든 Product에 대한 Source file과 Resource file을 모두 포함한다.
하나의 App에는 여러 Project가 있을 수 있는데,
해당 경우에는 Workspace를 통해 여러 Project를 묶어 관리할 수 있다.
Bundle
Bundle이란 executable code와 관련된 resource를 가진 표준화된 계층 구조의 디렉토리이다.
패키지 내용 보기를 눌러보게 되면 아래와 같이 Bundle은 Executable Code와 Resource를 포함한 계층 구조를 가지고 있다.
Package란 Directory를 Finder가 하나의 파일인 것처럼 사용자에게 제공해 준다.
위의 경우에는 Package와 Bundle이 같지만, 항상 같은 것만은 아니다.
Library
Library는 code와 data들의 집합이다.
Library에는 image와 같은 Resource는 포함하지 못한다.
앞서 설명했듯이, Target은 Product가 App, Library, Framework인 경우 Module이 될 수 있다.
따라서 Library는 Target을 추가하여 생성해야 하는데,
우리는 이를 App Target 혹은 사용할 Target에 병합하여 사용해야 한다.
이렇게 Library를 병합하는 것을 Linking이라고 하는데,
Linking시점이 Compile time이냐 Runtime이냐에 따라 나눌 수 있다.
- Static Library
- Dynamic Library
Static Library
Static Library는 Compile타임에 Linking이 이루어진다.
Static Library는 Compile하게 되면, Object File(.o)로 변환된다.
이렇게 변환된 Static Library의 경우,
Compile시에 Static Linker의 input으로 들어가,
의존성을 가진 Target의 Source File과 병합되어 하나의 Object File이 나오게 된다.
이때, 의존성을 가진 Target이 App Target이라면, Executable File로 변환된다.
Object file : Compiler에 의해 생성된 기계어로 구성된 파일
Executable file : Cpu에서 수행할 수 있는 바이너리(기계어)로 이루어진 파일
위의 과정들은 모두 Compile time에 이루어진다.
앱이 실행되면, Address Space의 Heap 영역에 Excutable File이 로드된다.
많은 Static Library를 link 하게 되며, executable file은 점점 커지게 된다.
이는 Heap영역을 많이 차지하게 되며, launch time이 느려지게 된다.
또한, Static Library가 업데이트되었다면, Client App은 개발자가 다시 linking하지 않는 이상 새로운 기능을 사용할 수 없다.
Compile타임에 Linking 작업이 이루어지기 때문에, 개발자는 다시 compile하여 새로운 executable file과 함께 배포해야 한다.
Dynamic Library
Dynamic Library는 Runtime에 필요한 경우(동적으로) 로드되고 linking된다.
Dynamic Library 경우는 Library의 소스코드가 Executable file에 포함되는 것이 아닌!
Reference가 포함된다.
즉, 둘은 Linker에 의해 병합이 되지만,
Library의 소스코드가 아닌 Reference와 병합이 된다.
이는, Runtime에 필요한 경우
Dynamic Linker는 Heap 영역의 Reference를 통해 stack영역으로 Load한다.
Static Library와 반대로 Compile을 할 필요 없이, 새로운 Library의 기능을 Client 앱에서 사용할 수 있다.
iOS와 macOS의 모든 시스템 Library는 Dynamic Library이다.
이는 필요할 때 Load하여 Linking하는 구조이기 때문에
Runtime은 조금 느릴지 몰라도, App의 시작속도는 더욱 빠르다.
Library의 경우 stand-alone Dynamic Library는 Apple에서 만들 수 없게 만들어 놓았기 때문에,
이를 사용하기 위해선 Framework를 이용해야 한다.
Framework
Framework는 Library와 image file, header file과 같은 Resource를 포함한
Bundle(계층구조의 Directory)이다.
Framework의 Library는 Static, Dynamic에 따라
Static Framework와 Dynamic Framework가 된다.
iOS개발을 하다 보면 UIKit Framework, Foundation Framework 등 다양한 Framework를 접하게 된다.
iOS의 System Library들은 Dynamic Library로 소스코드가 제공되기 때문에,
Heap영역에 Reference만 가지고 있다가 필요할 때 Stack 영역으로 로드하여 사용한다.
Framework를 사용할 때는 반드시 App Bundle안에 포함 시여야 한다.
vs. Library
Framework와 Library는 공통 기능을 모듈화 한다는 공통점이 있지만, 차이점은 아래와 같다.
우선, "Framework"는 앱 개발 시에 필요한 기능들을 위한 뼈대(틀)를 제공한다.
개발자는 만들어진 틀 위에서 코드를 구현하여 원하는 앱을 구현할 수 있다.
반면, "Library"의 경우는 개발에 필요한 것들을 미리 구현해 놓은 도구라 할 수 있다.
또한, "제어의 흐름의 권환이 어디에 있는가"로 Framework와 Library를 구분할 수 있다.
Library의 기능을 사용할 때, 개발자가 필요한 곳에 직접 Library의 기능을 가져다 쓴다.
즉, 개발자가 원할 때 능동적으로 사용하며, 이때 Control 권한은 앱의 코드에 있다.
반면, Framework의 경우 Framework의 뼈대(틀)에서 수동적으로 동작하기에,
Control 권한은 앱의 코드가 아닌 Framework가 가지고 있다.
예를 들어, UIKit Framework의 UIButton의 경우,
Action이 일어났을 때의 구현은 앱의 코드에서 진행하지만,
해당 구현이 어떤 시점에 호출되는 등 제어의 흐름은 Framework가 담당한다.
위와 같은 구조를 "IoC(Inversion of Control)"이라 한다.
마지막으로는 Resource 포함 가능여부이다.
Framework는 Resource를 포함할 수 있지만,
Library는 Resource를 포함할 수 없다.
이외에도 Library는 독자적으로 Dynamic Library가 될 수 없기 때문에,
Dynamic Library의 기능을 사용하고 싶다면 Framework를 사용해야 한다.
Swift Package
마지막으로 Swift에서 Module을 생성할 수 있는 Swift Package는 Source file과 Manifest File로 구성된다.
Manifest file은 Package.swift라는 이름으로 생성되며,
PackageDescription 모듈을 사용하여 Package내의 정보를 정의한다.
해당 정보를 통해 Swift Package Manger는 알아서 Package를 정의하고 Dependency를 링킹하는 과정 등을 진행한다.
Package.swift는 크게 3가지 파트로 구성되어 있다.
- Products
- Targets
- Dependencies
// Package.swift
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "Profile",
platforms: [.iOS(.v14)],
products: [
.library(
name: "ProfileHome",
targets: ["ProfileHome"]
)
],
dependencies: [
.package(name: "ModernRIBs", url: "https://github.com/DevYeom/ModernRIBs", .exact("1.0.1"))
],
targets: [
.target(
name: "ProfileHome",
dependencies: [
"ModernRIBs"
]
),
]
)
Products
앞서 Target의 설명에서 Target은 Product를 정의한다 하였다.
즉, Products는 해당 Package로부터 어떤 Target을 어떠한 노출시키고,
다른 Module에서 사용할 수 있게 해준다.
products: [
.library(
name: "ProfileHome",
targets: ["ProfileHome"]
)
],
Product는 Library로도 Executable로도 지정할 수 있다.
.executable(name: "ProfileHome", targets: ["ProfileHome"])
.library(name: "ProfileHome", targets: ["ProfileHome"])
Library의 경우에는 아래와 같이 Dynamic인지 Static인지 지정해 줄수도 있다.
.library(name: "ProfileHome", type: .static, targets: ["ProfileHome"])
.library(name: "ProfileHome", type: .dynamic ,targets: ["ProfileHome"])
Apple Documentation에 따르면, 지정하지 않는 경우 Swift Package Manager가
해당 Package의 사용자의 선호에 기반하여
Static Linking 혹은 Dynamic Linking을 진행하기 때문에
직접 지정하지 않는 것을 추천한다고 한다.
Dependencies
현재 Package에서 사용하고 있는 다른 Swift Pakcage(모듈)들을 명시한다.
dependencies: [
.package(name: "ModernRIBs", url: "https://github.com/DevYeom/ModernRIBs", .exact("1.0.1"))
],
아래와 같이 같은 Project내의 Swift Package도 추가가 가능하다.
.package(path: "../Finance"),
.package(path: "../Platform"),
Targets
말 그대로 Package내의 Target 즉, 기본 Build 단위이다.
Target에는 Source file이 포함되며, 이는 Product를 정의함을 통해 외부에서 모듈로 사용될 수 있다.
또한, 각 Target에서 같은 Package내의 Target과 혹은 외부 Package의 Dependency가 필요한 경우
Target내의 Dependency를 통해 추가해 준다.
targets: [
.target(
name: "ProfileHome",
dependencies: [
"ModernRIBs"
]
),
]
Reference
Apple Documentation
- Xcode Target
- Xcode Scheme
- Xcode Project
- Xcode Workspace
- Dynamic Library Programming Topics
- Framework Programming Guide
- Framework
- Product
https://fastcampus.co.kr/dev_red_rsj
https://tech.kakao.com/2022/06/02/swift-package-manager/
https://medium.com/delightroom/ios-library-framework-swift-package-알아보자-차이점-1f42c7848771
'iOS > iOS' 카테고리의 다른 글
[iOS] Floating Custom TabBarController (0) | 2023.07.22 |
---|---|
[iOS] Modularization(2) - Loose Coopling (0) | 2023.05.18 |
[iOS] 동시성 프로그래밍(12) - Actors (0) | 2023.02.22 |
[iOS] 동시성 프로그래밍(11) - Unstructured Concurrency (0) | 2023.02.22 |
[iOS] 동시성 프로그래밍(10) - Structured Concurrency (0) | 2023.02.16 |