아직 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

1. binary 사용하는 방법

GoogleMaps.json파일 만든 후 아래와 같이 추가

{

    "3.1.0" : "https://dl.google.com/dl/cpdc/d308af63f78a5a1a/GoogleMaps-3.1.0.tar.gz"

}

 

Cartfile에 위에서 만든 GoogleMaps.json을 추가

binary "GoogleMaps.json" ~> 3.1.0i

 

2. 누군가 올려놓은 github 사용하는 방법

Cartfile에 아래 github 추가

github "leoneparise/GoogleMaps-Carthage" ~> 2.7

 

https://github.com/leoneparise/GoogleMaps-Carthage

'Log' 카테고리의 다른 글

NodeJS Typescript Jest와 Puppeteer로 UI Test 자동화 하기  (1) 2023.08.16
Flutter vs. React Native  (2) 2019.09.28
심심해서 Flutter로 만든 TODO앱  (0) 2019.09.23
cocoapods cache 삭제  (0) 2019.09.22
Flutter sqflite에서 like 사용하기  (0) 2019.09.22

[그림1. Swift로 만든 날씨 앱]
[그림2. React Native로 만든 날씨 앱]
[그림3. Flutter로 만든 TODO 앱]

  React Native는 작년에 해봤고, 몇 주전엔 Flutter로도 앱을 만들어 보았다. React Naitve는 Web(html, css, js)과 거의 유사한 구조로 개발을 할 수 있게 되어 있어서 러닝 커브가 매우 적었다. 동일한 앱을 만드는데 [그림 1] 익숙한 Swift로는 반나절 정도 걸렸고 [그림 2] react native로는 공부 포함 하루 이틀 정도 걸렸다. flutter는 Dart라는 새로운 언어를 학습해야 하지만, 이전에 java나 C++, C#, swift등 oop언어를 다뤄본적 있다면 쉽게 익힐 수 있다. [그림 3] Flutter로 만든 TODO앱도 공부 포함 이틀 정도 걸린 것 같다. 

 

  개인적인 취향으로 js를 별로 선호하지 않으며, open source 기반으로 서드 파티들에게 디팬던시가 강한게 싫어서 react native보단 flutter가 더 마음에 든다. 앞으로 개인적인 프로젝트는 flutter로 진행할 것 같다. 

 

  Flutter React Native
만든 회사

Google

카메라, 네비게이션 기능 같은것을 구글이 직접 모든것을 지원함

Facebook

페이스북이 모든것을 지원하지 않으며, open source 기반으로 외부 라이브러리에 의존해야함.

따라서, 플랫폼이 뭔가 큰 변경사항이 있을때 각 서드파티들이 호환성을 맞출때까지 기다려야함.

공식 출시 2018 년 12 월 Google I / O 2015 년 3 월, F8 컨퍼런스
프로그래밍 언어 Dart (Object Oriented Programming) Javascript (Functional Programming)
핫 리로드 지원 지원
생태계

아직 성숙하지 않음

커뮤니티가 별로 없고 인터넷에 자료가 얼마 없음

성숙함

커뮤니티 활발하고 자료도 많고 open source도 많음

성능 AOT를 지원하며 Native로 배포됨, 성능 좋음 JIT로 컴파일 되며, VM에서 동작하기 때문에 Flutter 보단 느림 (하지만, 일반적인 동작엔 체감 못느낌)
UI

Indicator, Button, Label 같은 기본 위젯들이 iOS, Android와 UI가 동일하게 표시됨.

UI는 Dart를 사용하여 declarative syntax 방식으로 표현함.

각 위젯들이 Host 기반으로 각각 ios, android UI에 맞게 표현됨.

UI는 Javascrip로 declarative syntax 방식으로 표현하며, color같은 style은 웹과 유사한 css로 디자인함

결론  기존에 android나 ios같은 native programming을 했던 사람이라면 react native보단 flutter를 더 잘 맞을 듯. 웹을 했던 사람이라면 html, css로 UI를 작성하고 로직을 javascript로 작성하는 것이 익숙할테니, 이와 비슷한 react native가 잘 맞을 듯.

 

 

  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

iOS Clean Architecture를 디자인하였고 해당 architecture를 통해 TODO list를 저장하는 샘플앱을 만들어 보았다.

github : https://github.com/ezero9/iOSCleanArchitecture

1. Architecture 

[그림 1. Architecture]

  Architecture는 일반적으로 많이 사용되고 있는 3 Layer Architecture로 구성한다. 본 Architecture[그림 1]는 명확하게 정의된 레이어를 가지고 있으며, 각각의 레이어는 잘 정의되고, 통제되는 인터페이스를 통해 응집성 있는 서비스의 집합을 제공한다. 

 

  Object간의 dependency 관계는 IoC Container를 통해 injection받는다. 각 class들의 동작에대한 구성은 아래와 같다. [그림 2] 하위 레이어에서 상위 레이어로의 change propagation은 RxSwift나 delegation pattern, callback등을 통해 notify하는 방식으로 active하게 디자인한다.

 

[그림 2. 동작 구성]

2. IoC Container

[그림 3. IoC Container Class Diagram]

  일반적인 IoC Container 라이브러리들의 무거운 기능들은 전부 제외하고 DI, Singleton지원과 같은 필수적인 기능들만 구현하여 심플하게 제공한다. (Swift IoC 라이브러리가 없어서 직접 구현함)

 

Swift에서의 DI(Dependency Injection): https://develogs.tistory.com/21

2.1 Dependency Injection

  Object를 생성하고 각 object들간의 관계를 wiring하는 책임은 모두 IoC가 갖는다. client는 자신이 사용할 object instance를 IoC를 통해 DI받는다. 따라서 client에서는 IoC가 주는 object가 어떤 concrete class로 부터 생성이 되었는지 알필요 없으며, 정의된 interface를 통해 동작하기만 하면된다. 이는 object들간의 관계가 변경 전파 없이 같은 interface로 구현된 mock나 다른 class의 object instance로 언제든 대체될 수 있음을 의미한다. IoC를 사용하여 object들과의 관계를 설정할때 되도록이면 constructor injection을 사용하는게 좋다.

 

[Post기능과 관련된 object들의 의존성을 구성하는 부분]

 

  IoC(Inversion of Control, 제어의 역전)과 DI(Dependency Injection, 의존성 주입)에 대한 자세한 설명은 아래 링크에서 확인 할 수 있다.

제어의 역전(Inversion of Control, IoC) 이란?: https://develogs.tistory.com/19

2.2 Singleton 지원

  Singleton이 필요할때는 Singleton Pattern으로 class로 디자인하지 않고 IoC에 singleton으로 register한다. clinet에서는 IoC를 통해 필요한 object가 injection되면 해당 object가 singleton인지 일반 object인지 모르는 상태에서 사용되어야 한다. 

 

Singleton Pattern으로 디자인 하지 않은 이유: https://develogs.tistory.com/8

3.  Presentation Layer

[그림 4. BaseViewController와 BaseViewModel Class Diagram]

  Presentation Layer는 [그림 2]에서 보았듯이 MVVM Design Pattern을 사용한다. BaseViewController는 toast, popup, progress같은 ViewController의 공통 로직이 구현되어 있으며, BaseViewModel도 해당 ViewController와 interaction을 위한 공통 로직이 구현되어 있다. ViewController는 controller의 역할보다는 하나의 View로 보고 있으며 거기에 더해 View와 ViewModel과의 glue code가 존재하는 곳이다.

(재사용 되는 뷰가 없으면 오히려 mvvm의 기반코드를 작성하는게 이점대비 비용이 더 클수도 있다. View들의 도메인 요구사항에 따라 MVVM, MVP, MVC, VIPER등 무엇을  쓸지 design을 잘 결정하자.)

3.1 Data Binding

[그림 5. DataBinding]

  View와 ViewModel은 RxSwift, RxCocoa 라이브러리를 사용하여 two way binding을 한다. ViewModel은 binding해야 될 데이터를 정의해야하며, DataBinding Interface를 통해 바인딩 전략을 구현해야한다.

 

[AllPostViewModel에서 DataBinding 전략을 구현한 부분]

 

[AllPostViewController에서 AllPostViewModel과 binding하는 부분]

3.2 Routing Mechanism

[그림 6. Navigator Class Diagram]

  MVVM에서는 controller가 없기 때문에 Routing Mechanism이 필요한데, 그 역할은 Navigator가 담당하게 된다. Navigator는 자신이 이동해야 될 ViewController 혹은 Navigator를 IoC를 통해 전달 받아 정해진 interface를 통해 동작하게 된다. 

 

[BaseNavigator에서 View를 받아서 present하는 부분]

4. Domain Layer

[그림 7. 상위 레이어에서 requirement를 정의하고 하위 레이어에서 concrete를 구현]

  Application Logic을 담당하는 Layer임으로 Domain Layer는 반드시 다른 레이어들과 독립성을 유지해야한다. 다른 레이어들의 dependency가 존재하면 안되기 때문에 DIP(Dependency Inversion Principle)가 지켜져야 하는 레이어이다. Domain Layer에서는 필요한 requirements를 정의하고 Psersistent Layer에서는 이 정의된 requirement interface를 이용해 concrete class를 구현한다.

 

  여기서 다루는 내용중에 Domain Layer의 isolation이 가장 중요하다고 할 수 있다. 위에서 말했듯이 Presentation Layer에서의 view design pattern은 View 요구사항에 따라 바뀌겠지만 domain layer의 isolation은 반드시 지키는게 좋다.

5. Persistent Layer

[그림 8. Dependency Inversion 되어 구현되고 있는 Persistent Layer]

  Persistent Layer는 data source에 대한 decoupling을 위한 layer이며 Domain Layer에서 정의된 requirement interface를 통해 concrete class를 구현한다. Persistent Layer의 object들도 마찬가지로 IoC를 통해 Domain Layer에 injection되며, 여기서 injection된 object는 다른 구현체로 변경된다 하더라도 절대로 상위 레이어로 변경 전파가 이루어지지 않아야 한다.

6. Error Handling

[그림 8. TraceError Class Diagram]

  error stack trace를 확인하기 위해 ErrorTrace라는 최상위 error를 정의했다. ErrorTrace를 상속받아 명확한 이름을 가진 Error를 정의하고 예외는 do try catch 구문으로 처리한다. 

 

[그림 9. Error Wrapping]

  error는 하위 레이어에서 상위 레이어로 thorw될 때, 상위 레이어에서 requirement로 정의된 좀 더 구체적인 이름을 가진 error로 wrapping하여 올려 준다.

 

getStackTrace()을 찍어보면 아래와 같이 layer별로 명확한 이름으로 정의된 error의 stack trace를 확인 할 수 있다.

🔶[ERROR TRACE]🔶

🔶 iOSCleanArchitecture/Scenes/AllPost/AllPostViewModel.swift 50: viewDidLoad() - PostInterface.LoadFailError: not found Post Data. 

    🔶iOSCleanArchitecture/Domain/Model/AllPostModel.swift 42: parsing() - PostInterface.ParseFailError: parsing failed. 

        🔶 iOSCleanArchitecture/Repository/PostNetworkDAO.swift 35: network() - PostNetworkDAO.NetworkError: 404 error   

 

Error handling - 우아하게 실패하는 방법 : https://develogs.tistory.com/9

'Architecture & Design Pattern' 카테고리의 다른 글

제어의 역전(Inversion of Control, IoC) 이란?  (16) 2019.12.08
if문을 없애는 디자인  (0) 2019.11.17
Data Access Object Pattern  (0) 2019.09.07
Singleton Pattern의 함정  (0) 2019.07.14

+ Recent posts