Xcode에서 Project를 생성하면, 하나의 App Target이 자동으로 생성된다. 

이때의 App Target은 하나의 Module이 된다.

 

별도의 Framework를 추가하지 않는 이상,

모든 코드는 Single Target에 추가되게 된다. 

위와 같이 하나의 Module로 이루어진 App을 "Monolitic App"이라 부른다. 

 

이러한 Monolitic App은 여러 가지 단점을 가지고 있다.

하나의 모듈에 모든 소스코드가 들어있기 때문에 internalpublic을 구분할 수 없다. 

이로 인해, 객체들은 무분별하게 참조될 가능성이 있으며, 

이는 코드 변경의 영향 범위를 파악하기 어려워진다. 

 

또한, 특정 기능만 테스트하고 싶다 해도 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에 병합하여 사용해야 한다. 

 

이렇게 Library를 App Target과 병합하는 것을 Linking이라고 하는데, 

Linking시점이 Compile time이냐 Runtime이냐에 따라 나눌 수 있다.

  • Static Library
  • Dynamic Library

 

Static Library

Static Library는 Compile타임에 Linking이 이루어진다.

Compiler에 의해 Object file로 변한 Source File은 Static Library와 함께 

Static Linker의 Input으로 들어가, Executable File이 생성된다.

Object file : Compiler에 의해 생성된 기계어로 구성된 파일
Executable file : Cpu에서 수행할 수 있는 바이너리(기계어)로 이루어진 파일

Source File과 Static Library의 소스코드들은

Executable File에 복사되어 병합된다.

위의 과정들은 모두 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

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

https://medium.com/@paulwall_21/understand-how-xcode-workspaces-projects-schemes-and-targets-work-together-1832f22863f3

https://younggyun.tistory.com 의 설명

복사했습니다!