본문 바로가기

iOS

[iOS] iOS App의 Modularization (1편 - Bundle이란?)

iOS App의 모듈화(Modularization)

최근 iOS 앱의 구조 및 의존성 관리, Framework에 대해 공부하면서 모듈화에도 자연스레 관심이 생겼다. 또한 진행중인 프로젝트에 디자인 시스템을 적용할 계획을 가지고 있기 때문에 근본부터 공부해나가기로 결정했다. 일단 가장 기초적인 부분이라고 생각하는 Bundle과 Package에 대해 간단히 살펴보기 전에, 모듈화가 주는 장점에 대해 나열해보았다.

 

1. 코드 아키텍쳐가 향상된다. 이는 UI Test 및 Unit Test를 조금 더 편하게 만들어준다.

2. 전체적인 소프트웨어가 분리되기 때문에 단위별로 이해하고 유지보수가 가능하게 된다.

3. 기능을 분리하여 책임이 확실해진다.

4. 다른 프로젝트에 재사용하기 편리해진다.

5. 오류의 범위 또한 특정 모듈에 국한된다.

 

아직까지 모듈화는 커녕 프레임워크도 만들어본 경험이 없지만, 프로젝트를 진행하는 과정에서 기본적인 이론과 느낀점을 정리해 나가려고 한다.

Bundle이란 무엇일까? 개발자 문서

개발자 문서에서 애플은 Bundle을 디스크 상에 있는 bundle directory에 저장된 코드와 리소스들의 표현체(representation)이라고 설명하고 있다.

class Bundle : NSObject

 번들이 기본적으로 NSObject를 상속받고 있다는 점을 확인할 수 있다.

애플은 app, 프레임워크, 플러그인 외에 다른 콘텐츠의 특정 타입을 표현하기 위해 번들을 사용한다고 한다. 번들은 자신이 가진 리소스들을 잘 정렬된 서브디렉토리로 조직화하면서도, 번들의 종류와 플랫폼의 종류에 따라 번들의 구조가 달라질 수 있다. 번들을 사용함으로써 번들의 구조에 대해 알지 못해도 번들 속에 정리되어있는 리소스들에 접근하여 사용할 수 있다. 종합적으로, 번들의 역할은 새로운 요소들의 위치를 변경하거나 번들 구조에 대한 설정, user preferences, available localizations에 대한 단일화된 인터페이스를 제공하는 것이다.

 

어느 excutable이든 번들을 사용하여 app의 번들 내부 또는 알고 있는 다른 번들의 내부에 리소스를 locating 시킬 수 있다.

Bundle의 사용 패턴

일반적인 번들의 사용 패턴은 다음과 같다.

  • 원하는 bundle directory에 bundle 객체를 생성한다
  • 필요한 리소스를 로드하거나 locating하기 위해서 번들 객체가 가진 메서드를 사용한다.
  • 해당 리소스와 상호작용하기 위해서 다른 시스템 API를 사용한다.

그런데 몇몇 빈번하게 사용되는 리소스들의 경우에는 bundle이 없이도 접근할 수 있는데, 예를 들어 images를 불러 오 때에 UIImage(named:)와 같은 메서드를 사용하는 것이다.

사용 예시

아래와 같이 bundle의 resourcePath에 접근하여 번들에 존재하는 audio파일들을 불러올 수 있다.

let fm = FileManager.default
        let path = Bundle.main.resourcePath!
        do {
            let items = try fm.contentsOfDirectory(atPath: path)
            
            for item in items {
                if item.contains(".mp3") {
                    let value = item.replacingOccurrences(of: ".mp3", with: "")
                    if item.contains("BGM") {
                        bgm_VO = setAudioVO(audioVO: bgm_VO, title: value)
                        bgm_Array.append(bgm_VO)
                    } 
                }
            }
        } catch {
            // failed to read directory – bad permissions, perhaps?
        }

또는 앱 버전을 확인하기 위해서 bundle에 존재하는 infoDictionary의 값을 가져올 수도 있다.

        let version = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String)!
        print("app version = \(version)")

Custom Xib를 사용하여 UIView를 불러올 때에도 사용한다.

override init(frame: CGRect) {
    super.init(frame: frame)
    let name = String(describing: type(of: self))
    guard let loadedNib = Bundle.main.loadNibNamed(name, owner: self, options: nil) else { return }
    guard let view = loadedNib.first as? UIView else { return }
    view.frame = self.bounds
    self.addSubview(view)
}

override init(frame: CGRect) {
    super.init(frame: frame)
    let name = String(describing: type(of: self)
    let nib = UINib(nibName: name, bundle: Bundle.main)
    guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else {
        return }
    view.frame = self.bounds
    self.addSubview(view)
}

번들을 찾고 열기

 앞서 번들에 접근하여 특정 resources를 확인할 수 있다고 했는데, 리소스에 접근하기 위해서는 그 리소스가 속한 번들을 알아야 한다. 일단, 현재 code가 실행중인 컨텍스트의 bundle인, app의 main bundle에는 다음과 같이 쉽게 접근할 수 있다.

let mainBundle = Bundle.main

 그러나 리소스가 필요하고 변형되어야 할 맥락은 아주 다양하기 때문에 apple에서는 특정 목적, 특정 위치, 특정 패턴에 부합하는 번들을 찾거나 열 수 있는 메서드를 제공한다. 그리고 path, url, string을 통해서 특정 번들을 찾는 등의 다양한 생성자도 존재한다. 대부분의 경우 url을 통해서 원하는 자원의 위치를 특정할 수 있다.

 

 각설하고, 사용자가 앱을 실행하면 위의 메인 번들에서 필요한 코드와 리소스를 찾아서 메모리에 로드한다.
이러한 main bundle을 print해보면 Path가 나오는데, 여기로 이동해보면 Bundle 디렉토리가 존재한다. 오픈하면 이미지 어셋, info.plist 등의 리소스들이 들어있음을 확인할 수 있다.

번들과 패키지

 번들과 패키지는 혼동할 수 있는 모호한 개념이기에 한번 짚어보고 가려고 한다. 번들과 패키지는 여러 구성요소로 이루어진 디렉토리라는 점에서 동일하지만, 몇 가지 특징에 의해 구분될 수 있다. 우선 가장 기본적인 정의는 아래와 같다.

 

번들 : 실행 가능한 코드와 관련된 자원을 포함하는 디렉토리
패키지 : 파인더에서 하나의 파일처럼 보이는 디렉토리

 

 우선 번들은 지금까지 살펴봤듯, 개발자 경험을 향상시키는 데에 초점을 두고 있다. 번들에는 실행 가능한 코드가 포함되기 때문에 framework도 번들에 포함된다. 반면 패키지는 관련 자원들과 파일들을 압축시키고 연결시키는 것을 통해 사용자 경험을 향상시키는 것이 주 목적이다. .app, .plugin 등의 특정한 확장자를 가지고 있다. 패키지의 내용물을 보기 위해서는 파인더에서 우리에게 익숙한 '패키지 내용 보기'를 이용하면 된다. 추후에 File System에 대해서도 다룰 예정인데, 디렉토리나 파일에 접근하기 위해서는 FileManager, FileWrapper 등을 사용하면 된다.

 

참고

번들과 패키지
번들 프로그래밍 가이드