HCN DEV

Package, Bundle, NSBundle

Package, Bundle, NSBundle feature image

Package

  • PackageFinder가 사용자에게 단일 파일로 보여주는 디렉토리를 의미합니다.

A package is any directory that the Finder presents to the user as if it were a single file.

Package는 macOS에서 디렉토리를 추상화하는 기본적인 방법 중 하나입니다. Package는 디렉토리이지만 Opaque Directory로, Finder에서는 이를 단일한 파일로 인식합니다. 그래서 일반적인 디렉토리를 Finder에서 더블 클릭하여 열면 디렉토리 안으로 이동하지만, Package는 파일이 실행됩니다.

2018-12-15 4 58 06

위와 같이 Finder에서 아이콘을 오른쪽 클릭으로 확인할 때, 패키지 내용 보기라는 옵션이 있는 것을 Package라고 보면 됩니다.

Finder에서 응용 프로그램 탭으로 이동한 후 아무 앱 아이콘을 오른쪽 클릭으로 확인하면 놀랍게도 패키지 내용 보기 옵션이 존재합니다.

2018-12-15 5 04 15

이처럼 application도 하나의 Package 종류 중 하나입니다. MacOS에서는 하나의 Application을 .app이라는 확장자가 붙는 Package로 취급합니다. 시스템(Finder)은 .app, .bundle, .framework, .plugin 와 같은 확장자를 가진 디렉토리를 Package로 인식합니다.

Package는 Finder로 바로 열 수 있는 디렉토리가 아닙니다. 그래서 앞서서 본 것처럼 패키지 내용 보기를 통해서 해당 디렉토리에 들어가거나, 터미널로 이동해서 들어갈 수 있습니다. 이런 방식으로 구성되어 있는 이유는 Package 안에는 해당 Package를 실행하는 코드와 리소스들이 들어가 있고, 이를 사용자가 임의로 바꾸는 것을 방지하기 위해서입니다.

Bundle

  • Bundle - 실행 가능한 코드와 코드에 의해 사용되는 리소스를 가진 디렉토리

A Bundle is a directory with a standardized hierarchical structure that holds executable code and the resources used by that code.

BundlePackage처럼 디렉토리의 한 종류를 지칭하지만, 실행 가능한 코드와 리소스를 포함하는 디렉토리입니다. Bundle은 코드와 리소스의 구조를 정의합니다. 이 때, Bundle의 구조는 application, framework, plugin의 종류에 따라 차이가 있습니다. Bundle은 개발자와 OS가 코드를 좀 더 수월하게 다룰 수 있도록 합니다.

BundlePackage 개념을 서로 혼용해서 쓰는 경우가 많은데, 많은 BundlePackage이기도 하기 때문입니다. application 같은 경우는 Finder에서 사용자에게 단일한 파일로 노출되는 Package이면서, 실행 코드와 리소스를 포함하여 Bundle이기도 한 대표적인 예입니다.

Bundle Display Name

Bundle은 사용자에게 보여지는 이름과 실제 Bundle이 사용하는 File System의 이름을 따로 관리합니다. 그래서 Finder에서 이름을 변경하는 것은 Display Name만 변경하는 것입니다.

일반적으로 Application은 확장자인 .app이 생략된 Display Name을 사용합니다. 그래서 Finder에서 응용 프로그램의 확장자가 노출되지 않습니다. 또한 사용자의 언어에 맞춰서 Bundle의 Display name이 해당 언어에 맞춰서 나오도록 설정할 수도 있습니다.

NSBundle

  • Bundle에 포함된 코드와 리소스에 대한 표현

A representation of the code and resources stored in a bundle directory on disk.

NSBundle은 ObjectiveC를 통해 만들어진 Foundation Class로 Bundle 디렉토리 안에 포함된 리소스에 쉽게(Bundle 디렉토리의 구조를 알 필요 없이) 접근할 수 있도록 제공되는 객체입니다.

@interface NSBundle : NSObject

Note: NSBundle과 위에서 언급한 디렉토리 상의 Bundle은 다른 개념입니다. NSBundle은 NSObject를 상속하는 클래스이고, 위에서 언급한 Bundle은 디렉토리의 한 종류입니다. Swift 3에서 NSBundle의 명칭이 Bundle로 변경되었으니, 이 부분을 유의할 필요가 있습니다.

NSBundle은 다음과 같은 목적으로 주로 사용됩니다.

  1. 특정 Bundle 디렉토리를 위한 NSBundle 객체 사용(ex: main bundle)
  2. NSBundle의 메소드를 통해 필요한 리소스를 저장하거나 불러오기(ex: Bundle.main.url(resource:extension:))
  3. 다른 시스템 API와 리소스를 통한 인터렉션

Main Bundle

Bundle은 여러가지가 있지만, 그 중 가장 흔히 사용하게 되는 것은 mainBundle입니다. mainBundle은 앱이 실행되는 코드가 있는 Bundle 디렉토리에 접근할 수 있게 도와주는 bundle입니다. mainBundle은 아래와 같이 정의되어 있습니다.

The main bundle represents the bundle directory that contains the currently executing code.

  • Objective C
@interface NSBundle : NSObject {
...
/* Methods for creating or retrieving bundle instances. */
@property (class, readonly, strong) NSBundle *mainBundle;
}

// Usage
[NSBundle mainBundle]
  • Swift
open class Bundle : NSObject {
    /* Methods for creating or retrieving bundle instances. */
    open class var main: Bundle { get }
}

// Usage
Bundle.main

Main Bundle의 사용

Main Bundle은 외부 리소스를 프로젝트에 추가하고 접근할 때 가장 흔히 사용됩니다. 예를 들어서 이미지를 프로젝트에서 사용하기 위해 프로젝트에 추가하려면 아래와 같은 창이 뜹니다.

2018-12-19 12 41 06

여기서 확인을 누르면 앱 전역에서 다음과 같은 형태로 이미지에 접근할 수 있습니다.

Bundle.main.url(forResource: "myImageName", withExtension: "jpg")

Note: 이미지 같은 경우에는 Xcode에서 특별하게 취급하여, Bundle 사용 없이 바로 이미지 리터럴을 사용하거나 UIImage 형태로 접근할 수 있도록 제공됩니다. 이 부분은 별도로 다룹니다.

Main Bundle의 위치와 관련 사항

Main Bundle의 위치

Main Bundle의 위치는 Bundle.main.bundleURL을 출력해보면 쉽게 알 수 있습니다. 이를 출력하면 아래와 같은 형태의 경로가 나옵니다.

/Users/userName/Library/Developer/CoreSimulator/Devices/...Some Hash.../AweSomeProject.app

2018-12-19 12 38 32

앞서 언급한 것처럼 Bundle.main.urlAweSomeProject.app의 경로를 가리키고 있는 것을 확인할 수 있습니다. AweSomeProject.app은 Bundle이면서 Package인 디렉토리입니다. 그래서 터미널로 이동하거나, Finder의 패키지 내용 보기를 통해서 확인할 수 있습니다. 그리고 내부에 들어가보면 프로젝트에 드래그 앤 드랍으로 끌어놓은 이미지 파일들을 확인할 수 있습니다.

Copy Bundle Resources

이제 약간 궁금증이 생기는 부분이 mainBundle에 들어가 있는 리소스 파일들이 언제 들어 가는 것인가에 대해서입니다. 답은 빌드할 때입니다.

프로젝트의 Build Phases(SwiftLint 적용시 스크립트를 추가하는 위치이기도 합니다.)에는 빌드시 실행되는 여러가지 스크립트 혹은 항목들이 있습니다. 그 중 Copy Bundle Resources라는 항목이 있습니다.

2018-12-19 12 47 02

Copy Bundle Resources을 통해 Xcode는 빌드시 앱에 추가할 리소스를 결정합니다. 바꿔 말하면, 이 항목에 없는 리소스는 Project Navigator에 추가되어 있어도 실제로 Bundle에서는 접근할 수 없습니다.(리소스 URL이 nil이 됩니다.)

Main Bundle의 Sub Directory

mainBundle을 사용하면서 다소 의아할 수 있는 부분이 Sub Directory입니다. Xcode에서 Project Navigator 에서 Group을 생성하고 그 안에 리소스를 넣으면 다음과 같은 형태로 리소스에 접근하려고 시도할 수 있습니다.

Bundle.main.url(forResource: "myImageName", withExtension: "png", subdirectory: "groupName", localization: nil)

그런데 이렇게 접근하면 URL은 nil을 출력합니다. 이유는 Xcode에서는 개별 리소스를 추가할 때 Create Groups으로 추가한 것들은 모두 Copy Bundle Resources 항목에 별도 디렉토리 없이 바로 파일만 추가되기 때문입니다.

2018-12-19 12 41 06

그래서 위와 같은 옵션을 주고, 리소스를 추가하면 모든 리소스는 mainBundle의 root에 복사됩니다.

Bundle.main.url(forResource: "myImageName", withExtension: "png")

그렇다면 sub directory 옵션은 어떻게 활용하는 것일까요? 가장 간단한 방법은 project navigator에 리소스가 담긴 디렉토리를 Create Folder References 옵션을 주고 추가하는 것입니다. 이렇게 추가하면, 일반적인 노란색 디렉토리 모양이 project navigator에 나타나는 것이 아니라, 파란색 디렉토리가 추가됩니다.

copy type

이렇게 추가된 디렉토리는 Copy Bundle Resources에서도 디렉토리로 나타나고, 접근시에도 sub directory 파라미터를 사용해야 합니다.

Bundle.main.url(forResource: "img", withExtension: "png", subdirectory: "imgbox", localization: nil)

참고자료