본문 바로가기
iOS/트러블 슈팅

[iOS] Test 파일을 넣을 Asset 생성시 오류_ Bundle 사용

by MINT09 2024. 1. 28.

테스트용 JSON 파일은 이후에는 사용하지 않을 것이기에 test 폴더에 따로 Asset catalog를 만들어서 그곳에서만 넣어두고 사용하고 싶었다. 때문에 폴더를 하나 만들어서 이름을 Json.xcassets 으로 선언하고 그 안에 파일을 넣었다. 그런데 asset catalog의 모양이 기존의 것과는 달랐다. 확인해보니 asset catalog를 만들때는 단순하게 폴더를 만드는 것이 아니라 File → New → File을 타고 들어가 Resource의 Asset Catalog를 선택하여 만들어야 했다.

그 후 test code를 작성하여 돌리는데 이번에는 NSDataAsset을 가져오는 guard문에서 계속 retrun으로 빠지며 종료되었다.

guard let json = NSDataAsset(name: "box_office_sample") else {
    return
}

직접 생성했던 Json asset 말고 main asset에 파일을 넣어보니 이번에는 제대로 binding 되었다. 이를 해결하기 위해서 bundle을 만들어서 설정해주었다.

❄️Bundle

번들은 무엇일까? 공식문서의 정의에서 번들은 다음과 같다.

💡 A representation of the code and resources stored in a bundle directory on disk.
디스크의 번들 디렉토리에 저장된 코드 및 resource의 표현

그동안 우리가 UIImage를 Asset에서 찾아서 가져올 때 항상 이 번들을 이용했었다. 기본으로 설정되어 있는 main에서 가져왔기에 드러나지 않았을 뿐! 필요한 resource를 찾으려면, 먼저 해당 resource를 포함하는 번들을 지정해야한다.

main Bundle

평상시에 main으로 지정되어 있을 뿐이다. 기본 번들인 main은 현재 실행 중인 코드를 포함한다. 따라서 앱에서 기본 번들은 앱과 함께 제공되는 resource에 대한 접속 권한을 제공한다. 앱이 main 번들과 직접 상호 작용하는 경우는 따로 생성할 필요가 없지만 필요한 경우가 있다. 우리의 프로젝트에서와 같은 상황이다. 보통 번들은 앱 프로젝트 파일을 만들면 기본으로 생성된다.

파일 설명
myApp (필수)application code가 포함된 실행 파일
Application Icon (필수/권장) application을 나타내기 위한 아이콘.
Info.plist (필수) 번들 ID, 버전 번호, 표시 이름 등 application에 대한 구성 정보
MainWindow.nib (권장) 앱의 기본 nib 파일. 시작 시 로드할 기본 interface 개체
Settings.bundle 설정 application에 추가하려는 앱 별 기본 설정을 포함하는 특수한 유형의 플러그인
Custom resource 파일 localize resource X: 최상위 디렉토리 배치
localize resource O: 번들의 언어별 하위 디렉토리 배치
Resource = nib files, images, sound files, configuration files, strings files, other custom data files

init(for:)

우리의 테스트 코드에서 nil로 반환되었던 것도 이 기본 생성된 번들에 새로 만든 custom resource 파일이 올라가 있지 않기 때문이었다. 때문에 이 resource 파일을 포함하는 단위를 번들 디렉토리로 저장해 정보를 가져와야 했다.

let bundle = Bundle(for: JsonDecodeTests.self)
guard let json = NSDataAsset(name: "box_office_sample", bundle: bundle) else {
    return
}
// Get the app's main bundle
let mainBundle = Bundle.main


// Get the bundle containing the specified private class.
let myBundle = Bundle(for: NSClassFromString("MyPrivateClass")!)

https://developer.apple.com/documentation/foundation/bundle/1417717-init

 

init(for:) | Apple Developer Documentation

Returns the object with which the specified class is associated.

developer.apple.com

이때 init(for:)을 사용했다. 이를 이용하면 지정한 class가 연결된 NSBundle 객체를 반환한다.

init(for aClass: AnyClass)

 

❄️Locating Resources in a Bundle

번들을 사용하면 번들 내의 특정 resource의 위치를 가져온다. 이를 통해 resource를 찾는데, 특정 하위 디렉토리에 있는 resource의 경우 해당 디렉토리를 지정할 수도 있다. resource를 찾고 나면 번들은 파일을 열 수 있는 경로 문자열이나 URL을 반환한다.

번들은 디스크의 resource를 찾을 때 특정한 순서를 따라 간다.

  1. Global (nonlocalized) resources
  2. Region-specific localized resources (based on the user’s region preferences)
  3. Language-specific localized resources (based on the user’s language preferences)
  4. Development language resources (as specified by the CFBundleDevelopmentRegion key in the bundle’s Info.plist file)

번들은 이 순서대로 resource를 찾는다. 이때 1번에서 찾고 나면 2번, 3번, 4번에서는 resource를 찾지 않는다. → 시간 낭비 없애서 성능을 올리기 위해.

 

 ❄️Understanding Bundle Structures

번들 구조는 플랫폼과 번들 유형에 따라 다르다. 대부분 기본 구조를 숨기고, 보통 번들에서 resource는 자동으로 시작 디렉토리를 찾고 알려진 위치에서 찾아져서 load 된다. 또한 번들 클래스의 메서드와 속성을 사용하여 알려진 번들 디렉토리의 위치를 가져오고 해당 디렉토리에서 구체적으로 resource를 검색할 수 있다.

→ 우리 프로젝트의 경우도 이를 활용한 것이다. 테스트 클래스가 있는 디렉토리의 위치를 가져오면 이 디렉토리 내부에 에셋으로 만들어져 있는 json 파일을 가져올 수 있었다.