Finder가 단일 파일인 것처럼 사용자에게 제공하는 디렉토리
가장 쉽게 알아볼 수 있는 것은 우리가 사용하는 Application에 있는 최종 Product! .app 이다. 클릭하면 실행이 되지만, 실제로 우측클릭을 하게되면 Show Package Contents
라는 옵션이 있는 걸 확인할 수 있다. 클릭하면 실행이 된다는 점에서 Package
는 Finder에게 단일 파일인 것처럼 동작한다. (Opague Directory)
이 Package
안에는 Application을 실행하는데 필요한 코드와 리소스 파일이 있다. 하위 설명할 Bundle과 굉장히 유사하지만, 해당 단위는 Finder가 단일 파일처럼 인식하는 것에 중점이 가있는 용어라는 사실을 기억하자.
굳이 이렇게 Package라는 단위를 사용하는 이유는, 일반 사용자들이 Finder를 통해서 Application을 실행할 때, 단일 파일처럼 작동하도록 하여 내부 파일에 부정적인 영향을 미치는 것을 원천차단하기 위해서다. 즉, Package
를 사용하는 이유는, finder를 속이기 위해서, 그리고 그 결과로 사용자 환경을 개선하기 위함이다.
이렇게 finder가 단일 파일로 간주하는 확장자는 다음과 같은 것들이 있다.
.app
.bundle
.framework
.plugin
.kext
실행 가능한 코드(executble code) + 리소스 (Resources)를 가진 계층
어떻게 보면 이 개념은 개발자를 위한 개념이다. 결국 Package내부는 Bundle과 같이 구성되어 있다. 단지, 어떤 사람을 위해서인가 (일반 사용자, 개발자)에 따라 해당 본질이 다른 단어로 불린다고 생각하면 좋을 것 같다. package
라는 단어는 일반 사용자 사용, finder를 속이기 위함이라는 목적성을 가진 단어이고, Bundle은 개발자가 코드를 감싸는 단위로 불리운다.
가장 흔한 예로 Application을 만든다고 생각해보면, 최종 프로덕트는 우리의 소스코드가 컴파일되어 실행 파일로 만들어지고, 그 실행 파일이 가져다가 사용하는 리소스들로 구성된다. 그래서 Application은 Package
이자 Bundle
의 한 예이다. 대부분의 Bundle
은 Package
의 일종이지만, 모두 그렇지는 않다. Framework
의 경우에는 런타임 사용 혹은 링크를 목적으로 사용되는 단일 번들이지만, 내부 디렉토리를 볼 수 없도록 막아둔 단일 패키지는 아니다. (저 위에 있는 .framework는 실제 제품으로 빼는 경우라 다름, Product의 일종임. 여기서 말하는 것은 논리적인 개념인 듯)
Bundle
의 구조는 각각의 유형에 따라 달라진다. 다음 글에서 알아보도록 하자.
맥북을 사서 한글로 언어 설정을 하고 써보고, 영어로 언어 설정을 하고 써보았다. 그러면, 같은 앱이라도 표현되는 언어가 달라지는 것을 볼 수 있다. 이렇게 언어에 상관없이 Finder에 나타나는 방식을 변경할 수 있는데, 그 기능을 가능케 하는 것이 Bundle Display Names
이다.
카카오 톡이 아니고 KaKaoTalk
..
Finder는 이렇게 Bundle이 .app
인 경우 확장자를 숨긴다. 또한 만약 localized display names
를 가질 경우, 현재 언어 설정과 일치하는 이름을 표시한다.
실행 파일과 리소스가 묶인 단위로서 작동하기 때문에, 해당 디렉토리를 옮기는 것 만으로도 설치 및 재배치, 제거가 가능하다. 그래서 맥의 경우 드래깅해서 설치하는 것을 많이 보았을 것이다.
또 묶음 단위로 작용하기 때문에 사용자 수정의 영향을 덜 받는다. 이는 Bundle
이자 Package
인 Application에 관한 설명인 것 같다.
Bundle로 묶이게 되면서 다양한 CPU 아키텍쳐와 주소 공간 요구사항을 맞출 수 있다. 추후에 알아보겠지만 실제로 Apple은 AppStore에서 BitCode를 기반으로 다양한 디바이스에 최적화된 Bundle(여기서는 .ipa)을 만든다. (App Thinning)
디스크에 있는 Bundle directory의 Resource와 Code에 대한 표현
즉, Bundle
에 쉽게 접근할 수 있도록 제공되는 객체이다. 생각을 해보자. 일단 Bundle
은 유형에 따라 구조가 다르다고 했다. 그럼 이렇게 Bundle
로 제공되는 것에 접근해서 작업을 하려면 상당히 복잡할 것이다. 이런 부분을 NSBundle
이 해결해준다.
NS
키워드를 보면 알겠지만 Objective C로 만들어진 Foundation Class이다. 그럼 Objective C에서만 쓸 수 있나? 그렇다. Swift도 이를 지원하는 객체가 있다. 그런데 문제는 여기서 발생한다. 이녀석의 이름이 바뀌었다는 것이다!!!! swift 3부터!
ㅠㅠ 하필 이름이 헷갈리게 똑같이 바뀌어 버렸다.. 여튼, Bundle
은 다음과 같은 목적으로 사용된다.
잘 와닿지 않지만, 사실 우리가 image에서 이미지를 init(named:)
로 가져오는 행위가 Bundle
로 가져오는 거다. 물론 명시적으로 Bundle을 사용하지는 않았지만. 결국 내부 Asset Catalog라는 번들에서 가져오는 거니까.
현재 실행중인 코드가 포함하는 Bundle directory에 접근할 수 있게 도와주는 bundle
말이 참 이상하다.. 앞의 경우 directory의 의미를 가지고, 뒤의 경우 code level에서 제공하는 API라 이해하면 되겠다. 보통은 이러한 용도로 Bundle
을 많이 사용하게 될 것이다.
open class Bundle : NSObject {
/* Methods for creating or retrieving bundle instances. */
open class var main: Bundle { get }
}
// Usage
Bundle.main
그럼 일단 Bundle을 사용하기 위해서는 내 프로젝트에 Resource를 넣어야 할 것이다. 이전에도 말했지만, image 같은 것은 이미 알게모르게 Bundle을 사용하고 있다고 했다. 그럼 Resource중에 아주 흔하게 사용하는 image부터 추가해보자.
이미지를 드래그해서 프로젝트에 올려두면 다음과 같은 창이 뜬다. 확인을 누르게 되면 프로젝트에 리소스가 추가되었고, 앱 전역에서 Main Bundle을 통해 이미지에 접근할 수 있다.
Bundle.main.url(forResource: "imageName", withExtension: ".png")
그냥 사용하기 보다는, Bundle.main
이라는 녀석이 정말 내 Bundle(or Package.. 디게 헷갈리네)를 가리키고 있는지 확인해보자! Bundle.main.bundleURL
을 출력하면 알아볼 수 있다. 출력하게 되면 현재 실행하고 있는 시뮬레이터의 Hash String의 폴더 내부를 가리키고 있다는 것을 알 수 있다. 해당 String은 다음 경로에서 확인 가능하다.
/Users/userName/Library/Developer/CoreSimulator/Devices/...Hash String.../data/Containers/Bundle/Application/...Application Hash.../test.app
찾았다 요놈! 결국 Main Bundle은 내가 실행하고 있는 Target Bundle을 가리키고 있었다. 여기서 최종적으로 생성된 test.app
은 Package
이자 Bundle
인 디렉토리이다.
그럼 이제 알았다. "아 프로젝트에 각종 리소스를 다 때려넣고, 이걸가지고서 최종 프로덕트를 만들어 내는 거구나!" 맞다. 그러면 Resources들은 언제 Bundle화 될까? 답은 Build할 때이다.
Build Phase에 가면, Copy Bundle Resources
라는 탭이 있다. 여길 보면, 내가 방금 추가한 이미지의 주소가 Reference되어 있는걸 확인할 수 있다. 보면 Launch Screen하고 Asset Catalog 등등이 들어 있다. 응? Storyboard도 Resource인가? 이건 다음 글에 알아보자..
그럼 반대로 말하면, 여기에 추가가 안되어 있으면 Project Navigator(왼쪽 파일들 나열된 거)에 추가되어 있어도 Bundle화가 안된다는 얘기인가? 그렇다. 그래서 접근했을 때 값이 nil
이 뜨게 된다.
만약에 image를 방금 나같은 방식으로 그냥 최상위에 추가하지 않고, 폴더로 묶인 위와 같은 친구를 추가했다고 생각해보자.
그 다음에 이 그룹안에 있는 리소스에 접근해야 한다고 생각해서 다음과 같은 API를 활용했다.
Bundle.main.url(forResource: "wired_egg", withExtension: "jpg", subdirectory: "groupName", localization: nil)
하지만 아무것도 뜨지 않았다. 오히려 폴더가 다 풀려서 Bundle에 들어가 있었다! 해당 코드는 nil
을 리턴했다.
리소스 파일이 어떻게 들어가는지 보았더니 다 풀려서 들어갔다! 내가 원하는 건 이게 아니다.
이럴 때, Create Group 옵션 말고, Create Folder Reference 옵션을 선택하면 된다.
그럼 폴더 채로 들어가면서, Reference로 Copy Bundle Resources에 들어간다.
패키지 폴더에도 내 자식들이 예쁘게 들어갔다.
이제 Package, Bundle, 코드에서의 Bundle 세개의 차이를 이해했다. 다음은 그래서 Bundle에 관련된 것을 어떻게 설정할 수 있는지 구조에 대해서 알아볼 생각이다. 끝.
정리 너무 감사합니다.