아직 Swift에서는 DI 라이브러리를 많이 쓰이고 있진 않은 것 같다. github에 DI 라이브러리들이 몇몇 개 보이긴 하는데, Singleton을 지원하지 않거나 class를 key로 쓰고 있어 같은 타입의 object를 등록할 수 없는 등 불편한 점들이 여럿 있었다. 그래서 이런것들을 개선하여 필요한 기능들만 간단하게 만들어 쓰고 있었는데, 여러 프로젝트에 쓰다보니 코드 관리의 필요성이 생겨 라이브러리화 하여 github에 업로드 하였다. 

 

github: https://github.com/ezero9/SwiftInjection

 

Class Summary

Class DIContainer

    func configure()

    func resolve<T>() -> T

    func resolve<T>(key: String) -> T

    func contains(key: String) -> Bool

    func register<T>(_ assemble: @escaping () -> T)

    func register<T>(key: String, _ assemble: @escaping () -> T)

    func registerSingleton<T>(_ assemble: @escaping () -> T)

    func registerSingleton<T>(key: String, _ assemble: @escaping () -> T)

    func registerSingleton<T>(key: String, value: T)

    func destroy()

 

Class DIContainerManager

    func registerContainer<T>(container: T)

    func getObject<T>(key: String) -> T

    func resolve<T>() -> T 

    func destroy()

 

Usage

1.  Register class

  class를 등록하는 방법은 아래 [코드 1]처럼 DIContainer를 상속받아 configure를 override하여 사용하는 방법과 [코드 2]와 같이 DIContainer를 직접 사용하는 방법이 있다.

[코드 1]

 

[코드 2]

2. Get object

  등록된 class는 아래 [코드 3]과 같이 오브젝트를 생성할 수 있다. 

[코드 3]

3. Manage container

  관심사에 따라 여러 컨테이너를 작성하여 물리적으로 분리하고 아래 [코드 4]와 같이 DIContainerManager에 등록하여 사용 할 수 있다. 그리고 상황에 따라 container들을 다르게 생성하여 다른 행동을 하도록 Strategy pattern을 적용할 수 있다.

[코드 4]

 

 

'iOS' 카테고리의 다른 글

Swift에서의 AOP  (0) 2020.08.03
Realm은 thread safe하지 않다.  (0) 2019.09.01
SwiftUI Bata 하루 사용해본 후기  (0) 2019.08.17

  AOP(Aspect-oriented programming)는 횡단 관심과 핵심 관심을 물리적으로 나누고 모듈화하는 것을 말한다. 이는 메소드 호출(Target)을 가로채서 특정 동작(Advice)를 추가해야 하므로 메소드를 Intercept 할 수 있도록 언어에서 기능을 지원해야 한다. 여러 언어에 따라 컴파일 타임 혹은 런타임에 이 기능을 지원하거나 둘 다 지원하기도 하는데, swift는 statically typed language로 메소드 호출을 C++처럼 static/vtable 방식으로 호출한다. 따라서 컴파일 타임에 이를 지원해야 하지만, 안타깝게도 swift 컴파일러는 이를 지원하지 않고 서드파티에서 개발된 관련된 도구도 없음으로 아직 컴파일 타임에 AOP를 사용 할 수 없다.

  Swift 5.1에서 새롭게 공개된 @propertyWrapper를 통해서 변수에 대해 before, after 정도의 AOP를 아래 [코드 1]과 같이 비슷하게 흉내 낼 수 있긴 하지만,  타깃에 어노테이션으로 직접 표시해야 하고 여러 어노테이션을 중첩할 수 없으며(중첩 안되는 건 버그이며 추후 릴리스에 개선 예정이라고 한다.) 메소드에는 아직 적용할 수 없어서 효용성이 크게 있진 않다.

 

[코드 1] Swift5.1의 PropertyWrapper

 

  반면 objective-c는 메시지 기반의 언어이므로 런타임에 메소드 호출을 인터셉트할 수 있다. (실제로 github에 objective-c 용 AOP 라이브러리들이 여러 존재하긴 한다.) 그리고 swift에서는 objective-c API를 호출할 수 있으며, @objc 프로퍼티로 swift class를 objective-c 형태로 초기화하여 사용할 수도 있다. 그리고 UIKit도 objective-c 기반으로 작성되어 있어 UIKit API에도 이 방법으로 AOP를 적용할 수 있다. 

 

  만약 애플리케이션 내에 모든 UIViewController의 viewWillAppear에 google analytics를 적용한다고 한다면, 아래 stackoverflow link에 나와있는 코드처럼 objective-c의 method swizzling을 통해 viewWillAppear에 AOP를 적용해 볼 수 있을 것 같다.

 

https://stackoverflow.com/questions/46417057/want-to-create-a-listener-that-detects-viewwillappear-calls-throughout-the-app

 

  단점은 objective-c이기 때문에 swift보다 메소드 콜 성능이 다소 저하가 발생할 수 있으며, objective-c로 작성된 UIKit과는 달리 Swift로 작성된 SwiftUI에서는 사용할 수 없다. 그리고 기반 코드를 작성하다 보면 배보다 배꼽이 더 커질 수도 있겠다.

 

  Spring에서 AOP를 사용했던 경험이 있어서 그런지 다른 언어로 개발을 하다 보면 AOP에 대한 향수(?)가 굉장히 크다. 객체지향을 좀 더 객체지향답게 코드를 더 깔끔하고 우아하게 만들 수 있는 이 막강한 기능을 사용할 수 없으니 답답한 경우가 굉장히 많다. 하루 빨리 iOS에서도 AOP를 손쉽게 사용할 날이 왔으면 좋겠다.

'iOS' 카테고리의 다른 글

Swift에서의 DI(Dependency Injection)  (0) 2020.08.11
Realm은 thread safe하지 않다.  (0) 2019.09.01
SwiftUI Bata 하루 사용해본 후기  (0) 2019.08.17

  Realm Instance는 thread safe하지 않으며, Realm에서 조회된 object 역시 thread safe하지 않다. Realm은 성능을 위해 무복제 아키텍쳐로 설계 되어 있다. 간단히 말하자면 realm에서 조회된 object는 단순히 database에서 복제된 data가 아니라 core database에 직접 연동되어 있다. 그렇기 때문에 조회된 object를 변경하면 database와 이미 조회된 다른 object들에 라이브로 자동 갱신된다. 

 

  일반적으로 multi threading 환경에서 critical section 보호하기 위해서 Lock 사용하는데이는 병목 현상의 원인이 됨으로 realm에서는 lock 지원하지 않는다. 따라서 조회된 하나의 object 여러 thread들이 접근하게 되면 문제가 발생하게 됨으로 만약 다른 thread에서 같은 데이터가 필요하면 thread에서 realm instance 하나 만들고 object 다시 조회해야 한다.

[그림 1] 다중 버전 동시성 제어(multiversion concurrency control, MVC, MVCC)

  무복제 아키텍쳐인 Realm은 ACID를 보장하기 위해서 Lock이 아닌 MVCC(multiversion concurrency control)를 기반으로 설계되어 있다. [그림 1] 처럼 조회된 object A가 변경이 있을때, realm은 스냅샷을 찍고 v2를 만들어 object가 해당 버전을 바라보게 만든다. 이때 다른 thread들이 A를 접근하게 되면 v1를 조회하게 된다. 서로 다른 버전을 조회하고 변경함으로 ACID를 보장할 수 있게 된다.

'iOS' 카테고리의 다른 글

Swift에서의 DI(Dependency Injection)  (0) 2020.08.11
Swift에서의 AOP  (0) 2020.08.03
SwiftUI Bata 하루 사용해본 후기  (0) 2019.08.17

  주말동안 심심해서 SwiftUI Beta를 설치해서 사용해 봤다. SwiftUI를 통해 간단한 UI를 구성해보았고, ViewController가 없어진 View가 어떤식으로 Logic을 구성하고 어떻게 Data와 Model과 관계를 맺어 동작하는지 살펴보았다. 

Declarative Syntax (선언적 표현)

  SwiftUI는 Declarative syntax를 사용하여 GUI를 구성한다. 예전에 Web나 React Native같은 Declarative 방식을 사용하는 프레임워크로 앱을 개발해 본 경험이 있는데, 개인적으로 GUI를 이러한 선언적 방식을 사용하여 구현하는게 내 취향에는 맞지 않았다. (여러 tool을 사용해봤지만 만족스럽지 못했던 기억이...) 그런데 SwiftUI도 Declarative 기반이라고 하여 첫인상은 썩 좋지 않았다. 

  경험적으로 봤을 때 Declarative 표현방식으로 구현하는 GUI는 보는 것보다 읽는 것에 비중이 크다 보니 iOS Storyboard같이 드래그 앤 드롭 방식으로 UI를 개발하는 것보다 인지적 노력이 더 필요했다. 이는 직관적이지 않음으로 내가 오래전에 구현했거나 다른 사람이 구현한 GUI를 수정하려고 할때 많은 애를 먹곤 했었다. 하지만, SwiftUI는 이러한 문제를 아래와 같이 꽤 괜찮은 GUI Tool을 통해 해결했다.

직관적인 GUI Tool

[그림 1. Inspector로 속성 바꾸기]

  Code Editor 영역 혹은 Canvas 영역에서 Cmd + Click -> Inspector를 선택하면 View가 가지고 있는 속성들을 손쉽게 바꿀 수 있다. 

 

[그림 2. Drag and Drop으로 View 추가하기]

  Drag and Drop으로 View를 직관적이고 쉽게 추가 하고 수정할 수 있다. 이 기능 역시 Editor와 Canvase에서 모두 사용할 수 있다. Declarative syntax 방식의 장점과 Drag and Drop 방식의 장점들이 잘 어울러졌다.

 

[그림 3. Preview]

  코드를 수정하면 거의 실시간으로 Preview 영역만 빠르게 컴파일하여 미리보기가 가능하다. 이는 내가 작성한 GUI의 유사한 화면을 보여주는 것이 아닌 live app으로 실제 빌드된 화면과 동일함을 보장한다.

  [그림 3] 25~31 Line에서 Preview를 설정하는 부분을 볼 수 있는데, 해당 부분(28Line)에서 View가 의존하는 Class들을 Mock으로 대체하여 전달 할 수 있다. 따라서 매번 앱을 Full build하여 실제 데이터와 유저 시나리오 시퀀스대로 따라 가면서 View를 디버깅 할 필요가 없게 되었다. 이로 인해 View의 개발 및 유지보수 시간을 굉장히 절약 할 수 있을 것으로 보인다.

Two-Way Data Binding

[그림 4. State and Data Flow]

  드디어 iOS에서도 Data Binding을 지원한다. SwiftUI에서 제공하는 Property를 사용하여 User의 액션이 데이터로 전달되고 변경된 데이터는 뷰를 갱신한다. 바인딩된 Property의 Memory capture(strong, weak, unowned)도 신경쓸 필요 없게됐다.

 

[그림 5. 기존의 ViewController]

  기존의 방식에서는 유저의 액션으로 데이터를 변경하고 View를 갱신하기 위해서 UIViewController가 필요했다. UIViewController는 여러 View와 Model들을 가지고 있으며, 그 둘의 데이터 상태를 동기화 해주는 코드가 필요했다. 그리고 이런 일련의 동작들을 좀 더 깔끔하게 하기위해 우리는 꽤 많은 수고를 들여 기반코드를 작성했어야 했다.

 

[그림 6. SwiftUI에서의 View]

  SwiftUI에서는 UIViewController가 사라졌다. View는 Struct가 되었고, 그 대신 SwiftUI는 상태를 유지하고 추척할 수 있는 @Binding,  @State, @ObservableObject, @Environment등을 제공한다. 이제 iOS는 MVC가 아닌 MVVM을 사용할 수 있게 되었다.

 

  아래는 아이디와 패스워드를 입력하면 로그인 버튼이 활성화되는 테스트 코드이다. 이제 View와 Data간의 Binding하는 글루코드가 한결 깔끔해질 수 있게 되었다.

 

 

[그림 7. DataBinding]

 

  ObvervableObject protocol을 사용하여 LoginViewModel를 만든 모습니다. @Published를 사용하여 필드를 만들면 해당 값이 변경될때마다 바인딩 된 뷰에 자동으로 값을 갱신 할 수 있다. 그리고 View에서 변경된 값이 ViewModel에도 자동으로 갱신된다.

 

  View에서는 @EnvironmentObject를 사용하여 ViewModel를 선언하고 environmentObject를 통해 외부(22 Line)에서 데이터를 받을 수 있다. 이렇게 전달 받은 ViewModel은 위 코드에서 보이는것과 같이 손쉽게 View와 바인딩되어 사용될 수 있다.

결론

  SwiftUI를 하루 정도 사용해 보았는데, Declarative과 Drag and Drop 방식의 UI구성을 적절히 잘 지원하는 것 같다. 그리고 Data Binding을 지원하게 되어 이제 MVC가 아닌 MVVM을 좀더 쉽게 사용할 수 있게 되었다.

  Beta라 그런지 아직 버그가 많은것 같고, 잘못 사용한 문법 때문에 런타임에 crash가 나는 경우가 종종 있는데 로그로 충분히 crash 정보가 표현되지 못하고 있어서 좀 애를 먹긴 했다. 그리고 Apple Developer 사이트가 튜토리얼을 기가막히게 잘 작성해 놨다. 

 

https://developer.apple.com/tutorials/swiftui/creating-and-combining-views

 

Apple Developer Documentation

 

developer.apple.com

 

'iOS' 카테고리의 다른 글

Swift에서의 DI(Dependency Injection)  (0) 2020.08.11
Swift에서의 AOP  (0) 2020.08.03
Realm은 thread safe하지 않다.  (0) 2019.09.01

+ Recent posts