[TIL]iOS의 모듈식 아키텍처

rbw·2022년 12월 22일
1

TIL

목록 보기
60/98

참조

https://medium.com/@leandromperez/a-modular-architecture-in-swift-aafd9026aa99

위 글을 보고 정리한 글. 자세한 내용은 위 링크 참 JO 바람


프로젝트에서 한 부분의 변경으로 전체 앱을 다시 컴파일하는 경우가 많습니다. 이러한 점을 해결하는 방법을 설명하겠습니다.

화면에 약간의 수정을하고 그 수정사항이 작동하는지 확인하기 위해 빌드해야한다고 가정할 때, 작업중인 앱이 단일 제품 모놀리식으로 구축된 경우 결과를 보려면 전체 코드베이스를 다시 컴파일해야 합니다. 이 프로세스는 시간이 많이 걸리고 지루합니다. 우리는 독립적으로 부품을 컴파일하고 실행할 수 있어야 합니다.

가끔은 앱의 일부가 자체적으로 제품으로 취급될 만큼 복잡하고 큰 경우가 있습니다. 우리는 그것을 모듈이라고 부를 수 있습니다. 모듈성은 시간이 지남에 따라 확장 및 유지 관리할 수 있는 소프트웨어를 달성하는데 핵심입니다.

프로젝트 초기에는 복잡성이 추가될 수 있지만 장기적인 장점이 더 많습니다. 변경사항 적용 후 재컴파일 시 빌드 시간 단축, 개발 영역/책임 명확화, 플레이그라운드를 이용한 UI 빌드 및 테스트 가능 등

외부 종속성

SPM을 사용한 모듈식 아키텍처는 쉽다고 함 여기선 코코아팟을 종속성 관리자로 사용하여 모듈성을 달성해보려고 함

Architecture

접근방식은 여러 프로젝트를 포함하는 단일 작업 공간을 갖는 것으로 구성됩니다. 최종제품(앱)에 대한 프로젝트 하나와 각 모듈에 대한 프로젝트 하나. 앱은 모듈에 의존하고 모듈은 서로 의존이 가능합니다. 마지막으로 코코아팟은 외부 종속성을 제공합니다.

각 모듈은 앱 대상에서 사용되는 프레임워크로 컴파일됩니다.

단일 Podfile에서 외부 종속성을 정의합니다. pods를 설치하면 작업공간이 pods프로젝트로 채워집니다. pods 는 각 모듈과 앱이 의존할 프레임워크를 출력합니다.

Setting up the workspace

  1. 작업 공간을 만듭니다.
  2. 앱용 프로젝트를 만듭니다.
  3. 코코아팟을 초기화하고 앱 종속성의 이름을 지정합니다
  4. 작업 공간 파일이 있는 작업 공간의 루트에 Podfile을 배치합니다
  5. (Optional) PodfilePodfile.lock을 작업공간에 두는 것을 선호한다고 함
  6. (Optional) 각 모듈을 테스트할 플레이그라운드가 있는것을 선호한다고 함
  7. 앱이 컴파일되고 외부 종속성을 사용하는지 확인하세요.

작업 공간은 다음과 같아야 합니다.

Creating a module

  1. 프로젝트를 생성하고 작업 공간 안에 배치합니다.
  2. Podfile에 모듈 종속성을 추가합니다.
  3. 2단계의 종속 항목도 앱의 Pod에 추가하세요
  4. pod install후, 모듈을 컴파일 하여 외부 종속성을 사용할 수 있는지 확인합니다.
  5. 모듈을 프로젝트의 종속성으로 추가합니다

Core 모듈을 추가한 후의 구조

Adding a Module as a Dependency of the App

  1. 폴더가 없는 그룹을 만들고 이름을 Dependencies 또는 아무렇게 지정합니다
  2. 의존하려는 프로젝트를 이전 단계에서 만든 그룹으로 드래그합니다
  3. (처음에만) 빌드 단계 설정에서 Copy Frameworks를 만들어줍니다.
  4. 3단계에서 만든 프레임워크 복사 항목에 프레임워크 제품을 추가합니다

Adding inter-module dependencies

모듈이 다른 내부 모듈에 종속된 경우 이 작업을 수행합니다.

  1. 대상 모듈에서 파일 없이 그룹을 만들고 이름을 Dependencies 또는 아무렇게 지정합니다.
  2. 종속하려는 프로젝트를 이전 단계에서 생성한 그룹으로 드래그합니다.
  3. 프레임워크 프로덕트를 Embedded binary로 드래그(General에 있는거 가틈 이름이 좀 바뀐듯 ?)
  4. 빌드 단계 안에 3단계에서 만든 곳에 프레임워크 제품을 추가합니다.

2부 - 종속성

참조

https://medium.com/@leandromperez/modular-architecture-in-ios-dependencies-1cf9b563aa1d

세부사항을 추가로 작성한 글


이번에 다룰 부분은 다음과 같습니다.

  • 종속성 타입
  • 모듈과 앱 간에 글꼴 공유
  • 이미지 및 에셋
  • 번들 및 스토리보드 작업
  • 모듈의 바운더리

모든 것은 일련의 모듈로 구성된 앱의 컨텍스트에 있습니다. 코어(엔터티와 비즈니스로직), 네트워크 그리고 화면 모듈(로그인 화면, 홈 화면 등) 각 모듈은 독립적으로 실행될 수 있으며 다른 모듈에 의존합니다.

Types of Dependencies

종속성 유형 중 하나는 일반적으로 작성하는 코드와 같은 pure code 또는 SDK와 같이 가져오는 타사 서비스입니다.

완전한 종속성 주입을 달성하기 위해 개체의 복잡한 그래프에서 매개 변수를 전달하는 것과 관련된 많은 상용구 코드를 저장한다는 것을 알았습니다.

다른 유형의 종속성은 사운드, 글꼴 및 이미지와 같은 에셋입니다. 에셋은 해당 모듈 또는 어플리케이션 외부에 있을 수 있으므로 일종의 종속성이라고 생각 할 수 있습니다. 그리고 코드로 에셋을 제공하고 생성이 가능합니다.

Assets as dependencies

모든 애플리케이션은 일반적으로 화면에 사용되는 일련의 글꼴, 아이콘 및 색상과 함께 작동합니다. 예를 들어 사용자 아이콘과 같은 이미지는 로그인 모듈에서 사용될 수 있습니다. 일부 에셋은 앱 내부에서 반복적으로 사용됩니다. 글꼴이 이에 해당합니다.

문제는 어디에서나 에셋을 사용할 수 있도록 정의하는 방법에 있습니다

간단한 접근 방식은 에셋을 사용하는 모든 모듈에서 에셋을 복제하는 것입니다. 하지만 이 방법은 에셋을 업데이트할 때 번들 크기의 증가 및 잠재적 불일치 같은 문제의 위험이 있습니다.

더 나은 접근 방식으로 단일 위치에서 정의하여 모든 곳에서 사용할 수 있도록 하는 것입니다. 하지만 이는 살짝 불완전합니다. 이미지와 글꼴을 다루는 데 약간의 차이가 있습니다.

글꼴

단일 위치에서 글꼴을 정의하고 여러 모듈과 앱 자체에서 사용할 수 있는 방법이 필요합니다.

올바른 번들을 참조하는 한 코드로 글꼴에 액세스하는 것은 매우 간단합니다. 한 가지 옵션은 모든 상위 수준 모듈과 앱 자체에서 사용할 핵심 모듈(Core Module 비즈니스로직, 엔터티가 있는 그 곳)에서 글꼴을 정의 하는 것입니다.

핵심 모듈은 빌더 역할을 하는 공용 형식의 글꼴에 대한 액세스를 제공할 수 있습니다. 아래 코드 참조 !

public enum Fonts :String, CaseIterable {
    case robotoBlackItalic = "Roboto-BlackItalic"
    case robotoBlack = "Roboto-Black"
    case robotoBoldItalic = "Roboto-BoldItalic"
    case robotoBold = "Roboto-Bold"
    
    static var installed = false
}

코드에서 사용하려면

외부 모듈에 정의된 글꼴을 참조하고 사용하려면 해당 글꼴을 앱에서 사용할 수 있도록 등록해야 합니다.

public extension Fonts {
    static func install(from bundles: [Bundle] = [ Environment.bundle() ] ) {
        Fonts.installed = true
        for each in Fonts.allCases {
            for bundle in bundles {
                if let cfURL = bundle.url(forResource:each.rawValue, withExtension: "ttf") {
                    CTFontManagerRegisterFontsForURL(cfURL as CFURL, .process, nil)
                } else {
                    assertionFailure("Could not find font:\(each.rawValue) in bundle:\(bundle)")
                }
            }
        }
    }
}

// 사용 예시 
public extension Fonts {
    func size(_ size : CGFloat) -> UIFont {
        if Fonts.installed == false {
            Fonts.install()
        }
        return UIFont(name: self.rawValue, size:  size)!
    }
}

스토리보드에서 사용하려면

프로젝트를 구성하고 사용할 글꼴을 앱에 지정해야 합니다.

  1. 모듈에서 글꼴을 가져옵니다. 이것은 다른 모듈에서 사용되는 모든 글꼴을 포함할 장소입니다. 글꼴이 타겟안에 있는지 확인하십시오.

  1. 스토리보드에서 해당 글꼴을 볼 수 있도록 Info.plist를 구성합니다.

이미지

이미지는 일반적으로 에셋의 일부, 모듈 또는 앱 내부에 있습니다. 여기서도 코드와 스토리보드에서의 차이가 있습니다.

스토리보드

여기서 이미지를 액세스하는 경우 이미지가 제공될 수 있는 프레임워크나 번들을 지정할 방법이 없습니다. 스토리보드가 포함되어 있는 타겟의 에셋으로만 제한됩니다.

이는 스토리보드의 실망스러운 부분입니다. 따라서 이 경우 에셋을 중복해서 보유할 수 밖에 없습니다. 공유 에셋을 사용하더라도 앱이 빌드되면 모듈마다 에셋이 복제됩니다. 그래도 이 방법은 한 번만 정의하고 실수를 피하는 좋은 방법입니다. 명백한 단점은 앱 크기가 커진다는 것입니다.

코드

스토리보드처럼 제한이 없기때문에 코드에서는 이미지에 액세스하는것이 더 쉽습니다. 모듈에 노출된 이미지를 사용할 수 있으며 모듈이 이미지를 얻기 위해 올바른 번들과 함께 작동하면 문제 없습니다.

올바른 번들 사용

번들을 기본 값으로 받는 메소드가 많이 존재합니다. 기본적으로 해당 값은 nil이며 이는 Bundle.main 이 사용됨을 의미합니다. 앱을 실행중인 경우 해당 번들이 앱 번들이 됩니다. 모듈의 테스트 앱을 실행 중인 경우 해당 번들이 테스트 앱 번들이 됩니다.

예를 들면 UINib(nibName:, bundle:), UIStoryboard(name:, bundle:), UIImage(named:, bundle:), DataAsset(name:, bundle:) 등이 존재합니다.

올바른 번들을 지정하는 것이 정말 중요합니다. 예를 들어 특정 화면에 대한 모듈을 생성하려는 경우 해당 모듈에는 스토리보드, 이미지 및 nib 파일이 포함될 가능성이 높습니다. 올바른 번들을 참조하지 않으면 런타임 오류가 발생합니다.

올바른 번들을 지정하는 방법은 다음과 같이 원하는 대상에 있는 클래스를 사용하여 인스턴스화하는 것입니다.

public var bundle: Bundle? {
    return Bundle(for: DummyModuleClass.self)
}

주로 플레이그라운드에서 테스트를 해본다고 하심

Boundaries

각 모듈은 외부에서 사용하는것과 외부에 노출하는 것에 관련하여 경계를 정의합니다. 모듈을 사용하기 전 구성하는데 필요한 사항을 전달하는 것은 우리의 책임입니다.

명확하게 정의된 경계를 가짐으로써 각 모듈은 모듈을 사용하기 전에 호출해야 하는 구성함수를 노출할 수 있습니다. 이 함수는 모든 값이 정확하다고 주장하고 각 모듈을 구성할 수 있습니다.

인터페이스 노출

모듈은 제한된 클래스, 구조체 및 프로토콜을 노출해야 합니다. 이를 최소한으로 유지하면 중요한 것에 집중할 수 있습니다.

필요한 종속성을 정의하는 모듈은 구체적인 구현에 구애받지 않습니다. 이에 신경쓰지 않고 기능할 수 있는 프로토콜 인스턴스 또는 함수를 참조합니다.

Notification

일부 이벤트는 모듈 내부 일부 오브젝트의 브로드캐스트 상태(?, broadcast state 구독하고 있어야한다는 의미인가 싶기도하고 여튼 알려줘야 한다고 이해했슴다)에 노출되어야 할 수도 있습니다. 구독할 수 있는 알림 열거형을 노출하는것이 좋습니다.

좋은 디자인은 항상 보상을 받습니다 !

이러한 종류의 아키텍처에는 프로젝트가 점점 마이그레이션될 때 이점이 눈에 보입니다. 모놀리식 설계에서 모듈식 설계로의 전환은 힘들수 있습니다. 하지만 우리가 작업을 제대로 수행하고 느슨하게 결합된 추상화를 생성했다면 프로세스가 고통스러울 확률은 적습니다.

명확한 경계를 정의함으로써 모듈이 외부에서 사용할 모든 기능을 지정해야 합니다. 이를 통해 모듈이 호출하는 모든 기능을 단일 공간에서 작성하고 볼 수 있습니다. 이의 결과는 모듈이 어떻게 구성되어 있는지에 대한 명확한 그림입니다.

profile
hi there 👋

0개의 댓글