달력

4

« 2024/4 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

 이런저런 이유로, 현재는 회사에서 iOS Platform에서의 업무를 하지 않고 있다. 언제까지 iOS Platform 업무를 하지 않을지는 모르지만, 한동안은 개인 프로젝트를 하면서 iOS 관련 포스트를 작성하려고 한다.

 

 

1. Intro

: alert을 나타낼 때, TextField가 포함되어 있으면 [LayoutConstraints] warning이 console에 노출되는 것을 확인하였다. SwiftUI에서 .alert에 TextField를 추가한 경우와, UIAlertController에서 addTextField를 호출하여 TextField를 추가한 경우 모두 warning이 노출되었다.

 

그림1. TextField가 있는 Alert

 

# console에 노출된 warning

[LayoutConstraints] Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing. View: <_UIAlertControllerTextFieldViewCollectionCell: 0x7fbb22d44700; frame = (0 0; 270 24); gestureRecognizers = <NSArray: 0x60000366b6c0>; layer = <CALayer: 0x60000383fa60>>

 

[LayoutConstraints] Changing the translatesAutoresizingMaskIntoConstraints property of a UICollectionViewCell that is managed by a UICollectionView is not supported, and will result in incorrect self-sizing. View: <_UIAlertControllerTextFieldViewCollectionCell: 0x7fbb22b36de0; frame = (0 30.6667; 270 30.6667); gestureRecognizers = <NSArray: 0x60000368d1a0>; layer = <CALayer: 0x60000387ebe0>>

 

 

2. Impact

1) 아직까진 UI가 흐트러지거나 찌그러지는 등의 문제는 발견하지 못했다.

2) iOS 15부터 발생하는 것으로 추측된다.

 

 

3. How to solve?

: 구글링도 해보고, 다양한 시도를 해보았지만 명확한 원인을 찾지 못했다. 별도로 Constraints를 조작하는 코드들을 작성하지 않고, SwiftUI에서 .alert에 TextField만 추가하거나 UIAlertController의 extension에서 addTextField를 호출하여 UITextField를 추가하기만 해도 alert을 나타낼 때 console에 warning이 표시된다. UIKit 차원에서의 버그일까? (더 좋은 해결 방법을 알게되면 게시글을 수정 하겠다)

 

 해결하고 싶다면, 아직까진 UIAlertController를 커스터마이징 하는 방법밖에 모르겠다. UIAlertController 인스턴스가 setValue(viewController, forKey: "contentViewController")를 호출하여 커스터마이징을 하돼, UITextField의 layout을 직접 추가하고 설정하자. 예제 코드를 작성해보면 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension UIAlertController {
    convenience init(title: String, message: String) {
        self.init(title: title, message: message, preferredStyle: .alert)
        
        //...
 
        let contentViewController = UIViewController()
        contentViewController.loadViewIfNeeded()
        if let view = contentViewController.view {
            // frame은 각자 상황에 맞게
            let textField = UITextField(frame: CGRect(x: 10, y: 10, width: 240, height: 38))
            view.addSubview(textField)
        }
        
        self.setValue(contentViewController, forKey: "contentViewController")
 
        //...
    }
}
cs

 

 이때 주의할 점이 있는데, UIAlertController를 상속하지 않아야 한다는 것이다. Apple의 문서(Link)에 따르면... UIAlertController는 그대로 쓰이도록 의도된 클래스이며, 이 클래스의 View Hierarchy를 변경해선 안된다고 명시되어 있다.

 

Important

The UIAlertController class is intended to be used as-is and doesn’t support subclassing. The view hierarchy for this class is private and must not be modified.

 

:
Posted by syjdev

1. Intro

: 예전에 MVC의 원문을 읽고 요약했던 것에 이어서, 이번에는 MVP의 원문을 읽고 요약해보았다. MVC가 제안된 후로, 시간이 꽤 지나면서 Application의 요구사항이 복잡해지다보니... MVC의 Component를 좀 더 세분화 시킨 것이 눈에 띈다. MVP가 등장하던 시기에는 Unit Test의 중요성이 부각되진 않았는지 Testability의 장점에 대해선 언급되어 있지 않았다. Kent Beck의 XP 책이 1999년도에 등장했으니 그럴수도 있을 것 같다.

 

 MVP의 원문에서 이야기하는 핵심 내용을 잘 전달하려고 노력했지만, 개인 판단상 필요없다고 생각한 부분은 빼기도 했다. 맨 하단에 원문의 Link를 남겨두었으니, 부족한 부분이 있다면 찾아볼 수 있을 것이다.

 

 IBM의 자회사인 Taligent는 C++ & Java 언어로 개발하기 위한 차세대 Programming Model을 만들었는데, 그것에 MVP(Model View Presenter)이다. MVP는 Smalltalk의 고전적인 MVC Programming Model을 일반화한 것에 베이스를 둔다.

 MVP는 다양한 Application, Component 개발을 위한 강력하고, 이해하기 쉬운 설계(Design) 방법론을 제공한다. 또 MVP는, Multiple client / server 나 Multi-tier application architecture에도 적용할 수 있다. MVP는 주요 객체지향 언어 환경에서, 활용가능한 통합된 개념적 Programming model을 제공해줄 것이다.

 

 

 

2. Taligent에서 새로 제안한 Programming Model

2-1. Smalltalk Programming Model

: 1970년대, Smalltalk의 MVC model이 제안되었다. MVC는 Smalltalk에서 GUI 개발하기 위한 Fundamental design pattern으로 시작해서, 지금까지 다양한 Library들과 Application에서 재사용되고 채택되어 왔다.

 

 Smalltalk로 개발하던 개발자들은, Model, View, Controller를 추상화하기 위한 Base class(Smalltalk Class Library에서 제공함)들을 Customizing하거나 상속하여... GUI Object들을 만들었다. 개발자들은 GUI object에 있는, MVC에서 정의한 Model, View, Controller간 타이트한 결합을 이어받아, 개발할 때 편의를 누릴 수 있었다. 좀 복잡한 GUI object는 여러 GUI object들을 조합하여 구성할 수 있다. 궁극적으로는 모든 Graphical application은 MVC를 이용하여 구성할 수 있는 것이다.

 

 

2-2. Building the Taligent / Open Class Progamming model

: IBM을 위한 Open Class Library에서 Taligent는 다양한 Application의 전반적인 구조를 나타내기 위해, MVC Programming Model을 채택하고 일반화했다.

 

 Taligent는 전반적으로 기본적인 MVC Concept을 더 세분화한다. 우선, Model이랑 View와 Controller를 분리하고, View와 Controller를 합쳐서 Presentation이라 부른다. 이것은 Programming Problem을 Data Management와 User Interface라는 두 Fundamental Contept으로 분리하는 것이다.

 

그림 1. Model과 View, Controller를 분리

 

 

 

3. Model

3-1. Model Enable Encapsulation

: Model Concept을 일반화하면 몇몇 이점이 있는데... 우선 Model과 Presentation이 깨끗하게 분리될 수 있다. 예를 들어, 전화번호 목록 Component를 구현한다고 가정해보자. Model은 이름과 전화번호에 대한 Data를 캡슐화(Encapsulate)한다. 그리고 Model에서, Data에 대해 Query하거나 Data를 수정하기 위한 Method를 제공한다. Presentation은 전화번호 목록을 표현하는 전화번호부가 될 수 있다.

 

 깨끗하게 분리하고 캡슐화함으로써 이점을 얻을 수 있다. Model이 Data Structure를 바꾸는 것처럼 크게 수정하더라도, Presentation은 아무런 수정없이 재사용할 수 있다. 비슷하게, UI 변경과 같이 Presentation을 크게 수정하더라도, Model은 아무런 수정없이 재사용할 수 있다.

 

 

3-2. Model enable Persistence

: Model내에 Data가 저장되기도 하지만, Model이 Data를 저장하지 않고 Persistent Data Store에 접근해서 저장된 Data를 사용하기도 한다. Model은 접근 제어나 인증 관련 Logic을 구현할 수도 있고, 회계 또는 과금 Logic을 구현할 수도 있고, 성능 향상을 위해 Cache를 구현할 수도 있다.

 

 

3-3. Model Enable Sharing

: Model을 추상화하면, 여러 사용자 간의 Data 사용을 유연하게 할 수 있다. 다른 Program의 Model이 동일한 Remote Data를 캡슐화하는 것은, 여러 사용자가 Data를 공유할 수 있게 해준다. 아래의 그림을 참고하면, 이해가 쉬울 것이다.

 

그림 2. 다른 Program의 Model이 Data를 공유해서 쓰고 있음

 

3-4. Three Data Management Questions

: Taligent는 Data Management와 UI를 분리하는 다양한 개발 경험을 바탕으로, Data Management의 문제를 정리했다. 그들은, 아래의 그림처럼 Data Management의 문제를 3개의 질문을 바탕으로 3 부분으로 나누었다.

 

그림 3. Data Management 문제

 

1) What is my data?

: 위의 그림에서 Model에 해당하며, 우리가 알고있는 기존의 Model과 비슷하고 Data를 캡슐화하고 읽고, 쓸 수 있는 Method를 제공한다.

 

2) How do I specify my data?

: Model의 Data에서, 다른 부분집합들을 명시하기 위한 추상화. 위의 사진에서 Selections에 해당한다.

 

3) How do I change my data?

: Selections에서 수행될 수 있는 연산들을 표현하기 위한 추상화. 위의 사진에서 Commands에 해당한다.

 

 

 Model, Selections, Commands에 대한 이해를 돕기 위해, 예시를 들어 설명해보겠다. 아래의 그림을 보자. Model은 2차원 Integer Array라고 가정한다. View는 막대 그래프이다. Selections은 막대 그래프에 표시된 Data의 부분을 명시(또는 구분)할 수 있는 수단을 제공한다. Commands는 Data로 할 수 있는 동작들이다.

 

그림 4. Model, Selections, Commands, View 각각의 예시

 

 

 

4. Presentation

4-1. Three User Interface Questions

: Application에서 UI를 어떻게 설계할 수 있을까? Taligent는 Data Management를 나눴던 것과 비슷하게, 3개의 질문을 바탕으로 3 부분으로 나누었다. 아래의 그림을 보자.

 

그림 5. UI 설계를 위한 질문들

 

1) How do I display my data?

: 위의 그림에서 View에 해당한다. View는, 여러 개의 다른 View들로 구성될 수 있고 각 View들은 꼭 시각적으로 표시되지 않아도 된다.

 

2) How do events map into changes in my data?

: 위의 그림에서 Interactor에 해당한다.

 

3) How do I put it all together?

: 위의 그림에서 Presenter에 해당한다. Presenter는 MVC의 Controller와 유사하지만... 좀 더 높은, 추상화된 Application Level에 위치하고 Command와 Interactor랑 상호작용한다. 이 Presenter의 약자를 따서, Taligent가 제안한 Programming Model을 MVP(Model View Presenter)라고 이름을 지었다.

 Presenter의 역할을 좀 더 직관적으로 요약하면, Event를 받아서 적절한 Command에 연결해주는 것이라고 볼 수 있다(본 MVP를 소개하는 원문에서는, 이것을 Business Logic을 제공한다고 표현했다).

 

 

 이해를 돕기 위해 예시를 들어 설명해보겠다. 아래의 그림에서, Interactor에게 'Mouse Cursor의 이동'이나 'Menu의 선택'과 같은 행위를 적절한 Event로 정의할 수 있다. Interactor가 행위에 대한 Event를 전달하면, Presenter는 전통적인 Application의 'Main' 또는 'Event Loop'처럼 동작하여... 적절한 Model, Selection, Command, View, Interactor를 만들거나 상호작용한다(그림 5에는 방향성이 있는 것처럼 그려져 있으나, MVP의 원문에는 단방향성을 지켜야 한다같은 제약은 없었다).

 

그림 6. UI 구성요소를 설명하기 위한 예시

 

 

 

5. Programming Model Frameworks

: Taligent에서 MVP Programming Model을 고안했을때, 그들은 Object-Oriented Frameworks로 설계 및 구현했고... 개발자들이 자신의 Application에 Customizing할 수 있게 했다(이후, Taligent에서 몇번의 Release를 거쳐 Framework를 제공했었고, Update 내역에 대해 적혀있었는데, 중요한 내용이 아니라고 생각하여 생략했다).

 

그림 7. Taligent가 제공했던 Framework의 구조

 

 

 

6. Programming Model Classes

: 아래 그림은 MVP Programming Model에 대한 Class Diagram으로, 앞서 이야기했던 Concept이나 Structure들을 잘 나타내고 있다.

 

그림 8. MVP Programming Model에 대한 Class Diagram

 

 IModel, IView, ISelection, ICommand, IInteractor, IPresenter와 같은 MVP의 기본적인 Class들이 있다. 개발자들은 이 기본적인 Class들의 Subclass를 만들어서, 개발하면 된다(옛날에는 Interface 역할을 하는 Type의 이름 앞에 I를 붙였었다고 하고, 이 원문이 쓰여진지는 20년도 더 지났다. 당연히, 사용하는 언어의 Spec에 따라, Protocol이나 Interface 같은 것을 써도 상관없다).

 

 이 당시, 어지간한 Application들은 Interactor를 직접 구현할 필요가 없었다. IMenuInteractor, IButtonInteractor 같은 많이들 쓸법한 Interactor들이 이미 구현된 Open Class Library가 있었기 때문이었다.

 

 

 

7. Building An Application - Client / Server

: MVP Programming Model로, Client / Server Application을 구현하면 어떤 형태일까? MVP의 각 구성요소들이, Client와 Server중에 어디에 구현되어야 할지 결정해야 할 것이다. 보통, 전통적인 Client와 Server는 Presenter에 따라 나눌 수 있다. Model, Selection, Command는 Server에 속할 것이다. View, Interactor는 Client에 속할 것이다. 그러면, Presenter는 Client와 Server를 연결하는 역할이 될 것이다. 즉, Client와 Server 모두를 위한, 하나의 개념적인 Presenter가 존재하게 된다. 하지만 Presenter는 하나이므로 Presenter에 Client 관련 Code가 Server 관련 Code보다 많아지거나, 그 반대의 상황이 생길 수 있다. 

 

 Client, Server 각각을 MVP로 구현해보면 아래의 그림과 같은 형태가 될 것이다. Client에서 Model은 Server에서 가져와서 사용하는 Surrogate 또는 Proxy 형태가 될 수 있다. Server는 따로 View가 없고, Client에 있는 View를 갱신하는 형태가 될 수 있다. 여기서의 핵심은, Client / Server 모두를 MVP Programming Model로 구현할 수 있다는 것이다.

 

 

 

그림 9. Both Side MVP

 

 

 

8. Benefits of Abstractions

: 원문이 쓰여지던 당시, 다양한 Application을 개발하는 데에 MVP Programming Model을 활용할 수 있었다. MVP Programming Model의 구성요소대로, Logic들을 구분하고 나누는 것은 어떤 이점이 있는 걸까?

 

 Model과 View를 구분하는 것은, View의 독립시킬 수 있다는 이점이 있다. 간단한 예를 들면... 서로 다른 계산기의 UI가 있더라도, 동일한 계산기 핵심 Logic을 사용할 수 있는 상황을 생각해볼 수 있다.

 

 Selection과 Model을 구분하는 것은, Model을 독립시킬 수 있다는 이점이 있다. Model을 독립하면... Data의 구조나 File Format을 변경하더라도, Application의 나머지 부분에서 Data를 어떻게 보여주고 다룰 것인지를 변경하지 않아도 된다.

 

 언급한 2가지 이점들은 역할을 잘 구분하여 얻을 수 있는 일반적인 장점들이다. 이 2가지 이점 외에도 MVP Programming Model에 있는 구성요소들 중, 인접한 구성요소들간에는 구분하여 얻을 수 있는 장점들이 더 있다.

 

 궁극적으로는 MVP Programming Model로 추상화하여 구현하면... Platform이나 표준이 다르더라도 많은 Code들을 추가적인 수정없이 사용할 수 있는, 호환성(Portability)을 높일 수 있다는 장점이 있을 것이다.

 

 

 

9. 읽은 후 생각 정리

: MVC 대비, Model은 Data와 Data Management로 나누어 졌다. View와 Controller(MVP에선 Presenter) 사이에, Event를 전달 및 변환과 관련된 역할을 하는 Interactor가 추가되었다. Model과 Controller 사이에도 Selection과 Command가 추가되었다.

 

 하지만 원문의 내용과 달리, 지금도 iOS MVP Architecture로 검색해보면... 구현하려는 기능, 해결하려는 문제를 오직 Model, View, Presenter 세개의 Component에 맞추어서 설명하는 글들이 많다. Selection, Command, Interactor에 대해서는 언급이 없다. 심지어 MVC Architecture에서는 View에 UIView만 속했는데, MVP Architecture에서는 View에 UIView, UIViewController 모두 속하는 것이 차이라고 설명하는 글들도 있다. 당연히 잘못된 내용이다.

 

 바로 위의 문단(8. Benefits of Abstractions)에 Platform간의 호환성 이야기가 나오는데, 이걸 MVC에도 적용해보면... Controller에선 View가 어떤 형태인지 함부로 가정하거나 제한을 두지 않아야 한다. View는 UIKit에 속하는 Class로 구현할 수도 있고, AppKit에 속하는 Class로 구현할 수도 있다. 심지어 Metal로 구현할 수도 있다. 그렇다면 당연히, Controller는 UIKit, AppKit 같은 것에 의존적이지 않는 것이 바람직하다. 구현 편의상 UIViewController에 Controller의 역할을 부여하는 것은 이해가 되고 그것을 비판할 생각은 없지만, 그건 이상적인 Code가 아니라고 말하고 싶다.

 

 MVC의 이야기를 하며 글을 마무리 짓게 되었다. 빠른 시일내에, MVVM에 대해서도 정리해서 올려야지...

 

 

 

10. Reference

1) Mike Potel: MVP: Model-View-Presenter The Taligent Programming Model for C++ and Java, Taligent Inc, 1996

 

'Software Engineering > Architecture' 카테고리의 다른 글

The original MVC Reports 요약  (0) 2020.08.31
VIP를 읽어보고 고민한 것들  (0) 2019.09.08
:
Posted by syjdev

1. Intro

: 올해 초, 개발한 Library(*.framework 파일)를 다른 팀에 전달했는데 정상적으로 Build가 되지 않아서 고생을 했던 적이 있었다. 우리 팀에서 개발했던 Library를 사용하는 Project에서, Build Setting 항목 중 Other Linker Flags가 -ObjC인 경우 symbol 충돌이 발생하는 것이었다. symbol이 충돌하지 않도록 노가다...를 해서 해결했는데, -ObjC는 대체 왜 넣은 것일까? -ObjC는 대체 어떤 역할을 하는 것일까?

 

 

2. Objective-C로 작성된 Library

: -ObjC를 Other Linker Flags에 넣는 것은, Objective-C로 개발된 특별한? *.framework 파일을 사용할 때 발생하는 문제를 해결하기 위함이다. 문제를 발생시키기 위해 Sample Project를 만들었고, Sample Project의 구조는 다음과 같다.

 

그림1. Sample Project

 

 그림1에 명시된 Sample Project의 주요 특징을 정리해보자.

  1. LinkingTest라는 iOS Application은 SampleKit.framework를 link해서 쓰고 있다.
  2. SampleKit에는 구현한 코드가 Sample.m와 Sample+AdditionalFeature.m(Category에 선언된 Method를 구현함)로 나누어져 있다.
  3. SampleKit은 Static Library이고, Objective-C로 작성된 코드들이 있다.

 

  SampleKit의 헤더와 코드도 살펴보면 다음과 같다.

 

그림2. SampleKit.h
그림3. Sample.m
그림4. Sample+AdditionalFeature.m

 

 Objective-C로 개발된 특별한? *.framework 파일을 사용할 때, 발생할 수 있는 문제를 확인하는 것이 주 목적이므로 작성한 코드는 아주 간단하다. SampleKit을 LinkingTest에서 다음과 같이 사용했다.

 

그림4, Application에서 SampleKit을 사용하는 Code

 

 정말 단순해보이는 그림4의 코드를 실행시키면, Runtime에 Crash가 발생한다. Console에는 다음과 같은 메세지가 출력된다.

=> LinkingTest[49929:5165739] -[Sample outputDescription]: unrecognized selector sent to instance 0x600003d341a0

 

 outputDescription은 Sample+AdditionalFeature.m(category)에 선언된 코드인데 왜 unrecognized selector로 취급받는 것일까?

 

 

3. Crash가 발생하는 이유

: Crash가 발생하는 이유를 찾아본 결과, UNIX static library와 Objective-C의 Dynamic한 특성간 충돌이 있어서 static library에 있는 category method들이 Application에 Link가 되지 않는 이슈가 있다고 한다. 그래서 Runtime에 method를 찾지 못해서, unrecognized selector라는 메세지와 함께 Crash가 발생하는 것이다.

 

 Dynamic한 특성을 제공하기 위해, Method가 호출되기 전까지 method를 구현한 코드가 결정되지 않는다(symbol로만 표시해두고, 나중에 Runtime에 실행할 코드가 결정된다). 그리고 Objective-C는 method를 위한 linker symbol은 정의하지 않고 class를 위한 linker symbol만 정의한다. 예를 들어보면, sample.outputDescription()에서 Sample이라는 class에 대한 symbol은 있지만, outputDescription이라는 method에 대한 symbol은 없는 것이다.

 

 Objective-C의 category는 method들을 모아놓은 것이므로, category의 method들은 symbol을 생성하지 않는다. 그래서 class가 이미 정의된 경우(class는 정의되어 있고, 해당 class에 대한 category를 구현한 코드가 다른 파일인 경우), linker에서는 category에 정의된 것들을 모른다(load하지 않는다)는 것이다.

 

 -ObjC라는 Linker Flag는 linker가 static library에 있는 모든 Objective-C로 작성된 class와 category를 load하도록 한다. 그래서 이 Flag를 사용하면, 이 문제를 해결할 수 있다. 다음과 같이 Flag를 추가할 수 있고, 실행시켜보면 정상동작 하는 것을 확인할 수 있다.

 

그림5. static library를 사용하는 Project에서 Linker Flag에 -ObjC를 추가함

 

 static library에서만 발생하는 문제인 만큼, SampleKit의 Mach-O Type을 Dynamic Library로 변경하면 -ObjC Flag를 추가하지 않아도 문제가 해결된다. 아니면 Sample+AdditionalFeature.m에 구현된 코드들을 Sample.m으로 옮겨도(그러니까 category가 아닌 Sample class에 대한 implementation이 있는 파일과 category가 한 곳에 있을 때) 문제가 해결되지만, 한 파일에 너무 많은 코드가 담겨지게 될 것이다.

 

 

4. Reference

1) https://developer.apple.com/library/archive/qa/qa1490/_index.html

 

:
Posted by syjdev

1. Intro

: iOS 관련 개발 블로그나 세미나/행사에서 Architecture 주제를 다룰 때, 종종 하는 이야기가 있다. MVC의 C가 비대해져서 MVVM을 도입해보고, VM이 비대해져서 VIP를 도입했다는 것. 예전에는 이런 이야기를 의심없이 받아들였으나, 개발을 할수록 의문이 쌓여갔다. C가 비대해서 도입한 MVVM에서, VM이 비대해졌다면... MVC로 개발하면서 발생한 문제를 잘못 짚은게 아닐까.

 

 개발을 하다보면 Model을 단순한 Data Structure로 작성한 코드를 많이 보았다. 그런 코드에서 Business Logic은 Controller나 ViewModel에 작성되어 있었다. 그러니까, Controller나 ViewModel에서 HTTP 통신을 하고, UserDefault나 내부 DB에서 Data를 가져오거나 저장하는 것이다. 당연히 Controller나 ViewModel은 비대해질 수 밖에 없어보이는데, 이상하지 않은가?

 

 마틴(Robert C Martin)은, 여러 공학에서 Model을 만들어서 검증하는 사례를 이야기하며 Model은 '추상화한 것'이라고 이야기한다(UML 책 참고). 나는 이 이야기에 크게 공감하며, ViewModel은 View를 추상화해야 하고 Model은 Business를 추상화해야 한다고 생각한다. 그래서 Business Logic은 Controller나 ViewModel에 작성하는 것이 잘못됬다고 생각한다. 

 

 Model을 단순한 Data Structure로 작성할 수도 있다. 문제는 왜 Business Logic을 Controller나 ViewModel에 작성하냐는 것이다. 이런 고민을 하면서, MVC나 MVVM을 처음 이야기한 사람은 어떻게 생각했는지 궁금해졌다. 그래서 이번 포스트에서는 MVC를 처음 고안한 사람의 Report를 읽은 후 요약해보고, 향후 다른 포스트에서는 다른 Architecture에 대해서도 정리해보고자 한다.

 

 

 

2. The original MVC reports - Trygve Reenskaug

 

2.1. Intro

: 옛날 1978-79년에, Tregve Reenskaug는 Xerox Palo Alto Research Laboratory (PARC)에서 연구원으로 재직하던 때에, MVC reports를 작성하고 구현했었다. MVC는, 여러 관점에서 사용자들이 Data를 통제할 수 있게 하는 일반적인 문제에 대한 솔루션으로 만들어졌었다. MVC에서 가장 어려웠던 문제 중 하나는, Architecture를 구성하는 Component들의 이름을 정하는 것이다. 처음에는 Thing-Model-View-Editor라고 이름을 지었었다(1979년 5월 12일). 하지만 긴 논의 끝에, Model-View-Controller로 이름을 바꾸게 되었다(1979년 12월 10일).

 

 

2.2 Thing-Model-View-Editor (1979년 5월 12일)

=> Thing-Model-View-Editor는 중요한 부분이 아니라고 생각해서, 아주 간략하게 각 컴포넌트의 정의부분만 정리하고자 한다.

 

- Thing

: 사용자가 관심을 가질만한 것.

 

- Model

: Computing System에서, Model은 Data의 형태로 추상화의 표현. Data를 다루는 방법도 함께 제공되는 Collection으로 보자.

 

- View

: View는 Model을 표시하는 역할을 한다. Thing-Model-View-Editor를 고안해낸 당시, 저자는 Model을 하나 이상의 View에 부착(Attach)시키고 싶었던 것 같다.

 

- Editor

: 사용자와 View간 인터페이스 역할을 한다.

 

 

2.3 Model-View-Controller (1979년 12월 10일)

- Model

: Model은 지식(Knowledge)을 나타낸다. Model은 단일 객체일 수 있고, 객체들의 구조체일 수 있다. Model의 Node는 문제에서 식별가능한 부분으로 나타날 수 있어야 한다. => 말이 좀 어려운데, '문제'가 의미하는 것은 개발로서 해결해야 하는 것으로 생각하면 될 것 같다.

 Model의 Node는 모두 같은 문제 레벨에 있어야 한다. => 이것도 역시 말이 어렵다. 나는, 개발할려는 시스템 또는 시스템의 부분을 여러 Layer로 나눈다고 가정할 때 'Model들은 같은 Layer에 있어야 한다'정도로 이해했다(Robert C Martin의 Clean Archtecture의 Entity Layer에 대한 설명을 보고, 이렇게 생각하게 됬다).

 

- View

: Model의 시각적 표현. View는 View의 Model에 부착(Attach)되고, Model을 표현하기 위해 Model로부터 Data를 얻는다.

 

- Controller

: Controller는 사용자와 System을 잇는 역할을 한다. 사용자에게 출력할 것을 적절한 Message로 바꾸어서 하나 이상의 View에 전달한다던지, 화면에 View들을 적절하게 배치하여 사용자에게 입력을 위한 수단을 제공한다던지 해서, Controller는 사용자에게 필요한 입출력 인터페이스를 제공한다.

 Controller는 View를 보완하지 않는 것이 좋다(원 글에선, Controller가 서로 다른 View 끼리 연결을 하지 말라고 하였음). 그리고 View는 사용자의 입력(마우스 동작, 키보드 입력)을 몰라야 한다. 이를 위해, Message를 View로 보내는 Method는 Controller에 구현한다.

 

- Editor

: Controller는 모든 View에 연결되어 있다. 어떤 View는 Editor라는 특별한 Controller를 제공하는데, Editor는 View에 표시된 정보를 사용자가 수정할 수 있게 한다.

 

 

 

3. 읽은 후, 생각 정리

: The Original은 1979년대에 Trygve Reenskaug가 Xerox에서 사용자를 위한 시스템을 개발할 때, 작성된 글이다. 그래서 The Original MVC에서 제안하는 내용 중 일부는 공감되지 않을 수 있는데, 지금과 그 당시의 시대적 차이를 고려해보자(지금 개발중인 앱과, 당시에 개발중인 시스템의 복잡도는...).

 

 View와 Model이 부착(Attach)된다고 표현한 것이 조금 아쉬웠다. 요즘은, View가 Model의 존재를 몰라야 한다는 것이 일반적으로 받아들여지기 때문이다.

 

 Thing-Model-View-Editor에서의 Model과 Model-View-Controller에서 Model에 대한 역할이나 설명이 크게 달라지지 않았는데, 문서에서 설명한 것처럼 Model에서 Data를 다루는 방법도 제공한다면, 이 당시의 Controller는 역할이 방대하지 않았을 것으로 추측된다.

 

 여담이지만, 일하다보면 ViewController(많은 iOS 개발자들이 MVC의 Controller 역할을 부여함)에서 Network나 Local Database를 통해 Data를 가져오는 코드를 많이 보게 된다. 하지만 The Original MVC에 설명된 내용에 따르면 Data를 다루는 Model의 역할을 Controller에게 넘긴 것으로, Controller의 역할 분배를 잘못한 것으로 볼 수 있지 않을까? 바람직하지 않은 것 같다.

 

 

 

4. Reference

1)Trygve Reenskaug: The original MVC Reports, Oslo, February 12, 2007

:
Posted by syjdev

이전 포스트에 이어서, 작성되는 포스트이다.

 

 

1. Swift String에서의 Unicode

: é문자를 통해, Swift String에서 주의할 점들을 설명하고자 한다. é는 e에 acute accent를 붙인 것인데, 이를 표현하는 방법은 한가지 이상이다. 한 문자를 표현하는 방법이 한가지 이상이면, 비교는 어떻게 할 수 있을까? 표현하는 방법마다 문자의 크기는 같을까? 아래의 코드를 보고 하나씩 알아보자.

 

1
2
3
4
5
6
7
8
9
10
11
let firstString = "\u{00E9}lite"
print("1) output: \(firstString)") // => élite
print("1) count: \(firstString.count)") // => 5
print("1) utf16.count: \(firstString.utf16.count)") // => 5
        
let secondString = "e\u{0301}lite"
print("2) output: \(secondString)") // => élite
print("2) count: \(secondString.count)") // => 5
print("2) utf16.count: \(secondString.utf16.count)") // => 6
 
print("isSame: \(firstString == secondString)") // => true
cs

 

 firstString과 secondString은 lite를 제외한, 나머지 부분이 다르다. 그런데 출력시켜 보면, 둘 다 élite가 출력된다. count도 동일하다. 두 String을 비교한 결과는 true다. 하지만, code unit을 16bits로 하고 count를 보면 firstString과 secondString이 다르다.

 

 firstString과 secondString의 *.utf16.count는 다른데, 어째서 비교한 결과는 같은 것일까? Swift Language Guide에서 이유가 설명되어 있었다(링크).

 

 "Two String values (or two Character values) are considered equal if their extended grapheme clusters are canonically equivalent. Extended grapheme clusters are canonically equivalent if they have the same linguistic meaning and appearance, even if they’re composed from different Unicode scalars behind the scenes."

 

 대략 다음과 같이 해석할 수 있다.

 

 "Swift에서 두 String을 비교할 때 두 String의 Extended Grapheme Clusters가 canonically equivalent하면, 두 String은 equal하다. Extended Grapheme Clusters는 언어학적으로(linguistic) 의미와 형태가 같으면, Canonically Equivalent하다."

 

 Swift Language Guide에 나와있듯이, firstString과 secondString은 다른 Unicode Scalar들로 이루어져 있더라도 언어학적으로 의미와 형태가 같아서 비교한 결과가 True인 것이다.

 

 

 

2. NSString에서의 String 처리

: NSString은 조금 동작이 다르다. 확인을 위해 다음과 같은 코드를 작성했다.

 

1
2
3
4
5
6
7
8
let typecastedFirstString = firstString as NSString
print("1) count: \(typecastedFirstString.length)") // => 5
         
let typecastedSecondString = secondString as NSString
print("2) count: \(typecastedSecondString.length)") // => 6
        
let isSameTypecastedStrings = typecastedFirstString == typecastedSecondString 
// => false
 
cs

 

 Swift Strig에서는 firstString과 secondString의 count는 같았는데, NSString으로 타입 캐스팅한 typecastedFirstString과 typecastedSecondString의 length는 왜 다른 것일까? NSString을 설명하는 Apple 문서에 이유가 설명되어 있었다(링크).

 

 "An NSString object encodes a Unicode-compliant text string, represented as a sequence of UTF-16 code units. All lengths, character indexes, and ranges are expressed in terms of 16-bit platform-endian values, with index values starting at 0."

 

 음... 그러니까 NSString은 UTF-16로 Text를 인코딩한다. NSString의 length와 index, range도 16bit로 인코딩한 값에 따라 계산이 된다.

 

 NSString을 비교하는 'isEqual(to aString: String) -> Bool' 메서드에 대한 설명에서도, 두 String의 Unicode가 동일한지를 비교한다고 설명되어 있다(링크).

 

 

 

3. 마무리 및 참고자료

: 이번 글에서는 String을 비교할 때 주의할 점과, Swift String과 NSString이 어떻게 다른지를 설명했다. 글 중간중간에 삽입한 링크들을 정리하는 것을 끝으로, 이번 글을 마무리하겠다.

 

- Swift 문서의 String Compare

https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html#ID298

 

- NSString

https://developer.apple.com/documentation/foundation/nsstring

 

- NSString의 isEqual(to aString: String) -> Bool 메서드

https://developer.apple.com/documentation/foundation/nsstring/1407803-isequal

:
Posted by syjdev

1. Intro

: C로 개발을 처음 공부했을때, 처음 접해본 문자열은 char *message = "hello world";와 같은 형태였다. message는 char들이 저장된 곳의 시작주소이고, 문자열의 맨 마지막은 null이었다. char charArray[4] = { 'a', 'b', 'c', '\0' }; 와 같이, char들의 배열을 선언하고 배열의 마지막 요소를 '\0'로 설정해서 문자열을 나타낼 수도 있었다.

 ASCII Code로 나타낼 수 없는 문자를 표현하기 위해 Unicode가 생겨났고 UTF-8, UTF-16, UTF-32 등의 Encoding이 있다는 것, 문자열에 대한 나의 이해는 이정도였다.

 어느 날 책에서 이런 내용을 보았다. 

"String의 n번째 Character에 접근하는 연산은 time complexity가 o(n)이다. 
String의 n번째 Character에 접근하는 것이 Random Access가 아니기 때문이다".

 부끄럽게도, String을 구성하는 Character들이 배열같은 Collection에 순서대로 저장되어 있을 것이라 생각해서 Array의 요소에 접근하는 것처럼, String의 Character에 접근하는 것도 Random Access로 접근할 것이라고 생각했었다.

 그동안 컴퓨터에서 문자를 어떻게 다루는지에 대한 별다른 고민없이 사용했다는게 조금은 부끄럽기도 해서, 다양한 문서들을 읽어보았다. 이런저런 문서들을 읽어보니 생각보다 알아야 할 것들이 많았고, 익힌 내용을 정리할 필요가 있다고 생각하여 글을 적어보고자 한다.

 

 

2. ASCII의 한계와 그것을 극복하기 위해 등장한 표준들

: ASCII는 매우 단순하다. ASCII 문자로 이루어진 문자열은, 구성하는 Character들이 모두 8bits로 크기가 일정하다. 그래서, 문자열의 Character에 Random Access로 접근할 수 있지만, 미국에서 쓰는 알파벳이 아닌 문자를 표현하는 데에 한계가 있다. 그래서 ISO/IEC 8859 표준이 등장했는데...

 

2-1. ISO/IEC 8859의 등장

: 컴퓨터에서 문자를 8bits로 나타내는 위한 표준. 8번째 비트를 이용해서, 기존의 ASCII보다 더 많은 문자를 표현하여 ASCII의 한계를 극복하려 하였다. ISO 8859-1,2,3, ..., 16까지 있다. 하지만 ISO/IEC 8859 표준도 문자의 크기가 고정되어 있어서 동아시아 국가의 문자를 표현하는 데에는 한계가 있었다. 그래서 등장한 표준이...

 

2-2. Unicode의 등장

: 초기의 Unicode도 2bytes로 크기가 고정되어 있었고, UCS-2 라고 불렀었다. 하지만 2bytes로도 많은 문자들을 표현하는 데에 한계가 있었고, 오늘날에는 Unicode는 문자의 크기가 고정되지 않고 각각 다른 크기를 가질 수 있게 되었다.
=> 이런 이유로, String의 Character에 접근하는 것은 Random Access가 될 수 없다. n번째 Character를 알기 위해선,
n-1까지의 Character들을 확인해야 하기 때문이다.

 

3. Unicode란 무엇일까?

※ 이제부터 문자는 Character, 문자열은 String이라고 표현하겠음.

 

: Charactrer를 Encoding하는 표준을 일컫는 Unicode Standard와 그를 주도하는 협회 Unicode Consortium 모두를 Unicode라고 불리지만, 이 글에서는 Unicode Standard만을 이야기하고자 한다.

 

3-1. Unicode가 등장하기 전 Character 처리

: 기본적으로 컴퓨터는 숫자를 다룬다. 컴퓨터는 각 Character마다 다른 숫자를 할당하여 저장한다. Unicode Standard가 탄생하기 전, 다양한 Character Encoding 방법들이 있었으나 전 세계의 모든 언어를 표현하기엔 한계가 있었다. 특히 동아시아 언어를 표현하는 데에 한계가 있었다(뭐, 한글이라던지).

 초기의 Character Encoding들은 서로간 충돌(Conflict)이 있었다(같은 숫자를 다른 Character로 처리하거나 한 Character를 각기 다른 숫자로 처리하는 것). 컴퓨터들은 다양한 Character Encoding들을 지원했지만, 컴퓨터간 통신시 Character Encoding이 다를 경우, 문제가 발생할 수 있었다.

3-2. Unicode 등장

: 바로 위에서 이런 문제들을 해결하기 위해, Unicode가 만들어졌다.
=> 간단한 Character Encoding을 제공하는 것을 넘어서, Unicode Consortium은 'locale data'에 대한 표준에도 관심을 두고 있다. 

 

3-3. Unicode와 관련된 용어 정리

1) Code Point - 표준문서의 D10
: Unicode code space에 속하는 값. 값의 범위는 0에서 0x10FFFF(10진수로 나타내면 1,114,111)까지이다. Code Point들은 하나의 Character로 표시될 수 있지만, 여러 Code Point들이 모여서 하나의 Character를 표시할 수도 있다.

2) Unicode Scalar - 표준문서의 D76

: Code Point중, High/Low Surrogate를 제외한 것.

3) Surrogate(High Surrogate, Low Surrogate) - 표준문서의 3.8
: Surrogate는, 미래를 위해 Code Point에서 따로 남겨둔 부분이다(Surrogate 개개별로는 Character로 나타내지 않는다). 범위에 따라 High Surrogate와 Low Surrogate로 나뉘어 진다. High Surrogate와 Low Surrogate는 범위가 다른데, 표로 나타내면 다음과 같다.

 

그림1. High Surrogate
그림2. Low Surrogate

 

4) Surrogate Pair

: High Surrogate에 속하는 Code Point 하나와 Low Surrogate에 속하는 Code Point 하나의 Pair로 구성하여 Single Character를 나타내는 것. UTF-16에서만 사용된다.


5) Code Unit

: Encoded Text의 Unit을 나타낼 수 있는 최소 bit 조합. Unicode Standard는 UTF-8에서는 8bits Code Unit들을 사용하고, UTF-16에서는 16bits Code Unit들을 사용하고, UTF-32에서는 32bits Code Unit들을 사용한다.

 

※ 개인적으로 Code Unit과 Code Point가 조금 헷갈렸다. 비교해서 정리해보면,

" Single Code Point는 Single Code Unit일 수 있고, 여러 Code Unit들로 이루어질 수도 있다. "
=> ☃ 는 3개의 UTF-8 Code Unit들로 이루어질 수 있고, 1개의 UTF-16 Code Unit으로 이루어질 수 있다.


6) Code Unit Sequence
: 하나 이상의 Code Unit들로 구성된 Ordered Sequence.


 Programming Language에서 String Data의 값은 기본적으로 Code Unit Sequence로 구성된다. 공식적이진 않지만, Code Unit Sequence 자체를 String이라고 하기도 하고, Byte Sequence를 Byte String이라고 하기도 한다.
=> 공식적으로는 Programming Language에 따라, String은 요구사항이나 복잡성이 추가되기도 한다. 예를 들자면, C Language에서 String은 NULL Character로 끝나야 한다던지. 

 Unicode Encoding Form은 Unicode Scalar 값을 Unicode Code Unit Sequence에 할당한다. 역사적인 이유로, Unicode Encoding Form은 Unicode(또는 UCS) Transformation Format, UTF로 불리기도 한다.

 Unicode Encoding Form에서, Unicode Scalar 값의 Set을 Code Unit Sequence의 Set으로 Mapping할 때, One-To-One으로 Mapping된다.
=> Reverse Mapping시, Mapping되기 전의 값을 추론할 수 있도록 보장한다.

하지만, Onto로 Mapping되는 것은 아니라서 Code Unit Sequence와 연관된 Unicode Scalar 값이 없을 수도 있다.

Unicode Encoding Form이 One-To-One으로 Mapping하는 것을 보장하기 위해, 모든 Unicode Scalar 값은 반드시 Unicode Code Unit Sequence로 Mapping되어야 한다.
=> Surrogate는 예외이다.

7) Unicode String

: 특정한 Unicode Encoding Form의 Code Unit들을 포함하는 Code Unit Sequence. 가장 Raw한 형태의 Unicode String은 적절한 정수들의 배열로, 간단하게 구현될 수 있다.
 단일 Unicode String은 단일 Unicode Encoding Form의 Code Unit만 포함해야 한다. String 내에서 Form을 혼합하는 것은 허용되지 않는다. 따라서...

- Unicode 8bits String, UTF-8 Code Unit들로 이루어진다.
- Unicode 16bits String, UTF-16 Code Unit들로 이루어진다.
- Unicode 32bits String, UTF-32 Code Unit들로 이루어진다.

 

 

4. 마무리 및 참고자료

: 이번 글에서는 Unicode와 관련된 용어를 정리하는 것에 중점을 두었다. Unicode Scalar, Code Point, Code Unit 등 여러 용어가 나왔고 각각에 대해서 간략하게 정리했는데, 더 알고 싶은 분들을 위해 참고했던 자료의 링크를 소개하는 것을 끝으로 이번 글을 마무리하겠다.

 

- Unicode 

https://home.unicode.org/basic-info/

 

- Unicode 12.0 Standard Documents

https://www.unicode.org/versions/Unicode12.0.0/

https://www.unicode.org/versions/Unicode12.0.0/UnicodeStandard-12.0.pdf

 

- Programming with Unicode

https://unicodebook.readthedocs.io/index.html

 

:
Posted by syjdev
2019. 12. 7. 17:12

UIView bounds vs frame iOS Development/ETC2019. 12. 7. 17:12

1. Intro

: UIView의 bounds와 frame의 차이가 무엇이냐고 하면, 좌표계(Coordinate System)의 차이를 이야기하며 설명하는 분들이 많다. 그렇다면 좌표계가 다르면 어떤 차이가 있을까? origin만 다를까? 이번 포스트에서는 이에 대해 이야기를 해보고자 한다.

 

 

2. Apple 문서에 소개된 bounds과 frame

 

* bounds - Apple 문서 링크

: 본인 관점의 좌표계(Coordinate System) 기준으로 View의 크기와 위치를 나타낸다. default bounds는 origin 값이 (0, 0)이고 size 값은 frame의 것과 같다. Rectangle 일부의 size를 변경하면, View의 center를 기준으로 변경된다. Rectangle의 size가 변경되면, frame의 size도 변경된다. (이하 생략)

 

* frame - Apple 문서 링크

: 부모 뷰(Superview) 관점의 좌표계 기준으로 View의 크기와 위치를 나타낸다. 그 외에는 bounds에서 Rectangle의 size가 변경될때 frame의 size을 바꿧던 것과 같이, bounds의 size을 바꾼다. 대부분의 특성이 bounds와 비슷한데, 조금 특이한 경고문이 Apple 문서에 다음과 같이 적혀있는데...

 

Warning

: view의 transform이 identity(CGAffineTransform.identity)가 아니면, frame의 값은 정의되지 않는다. 따라서 이때의 frame 값은 무시하는게 좋다(should).

 

 frame의 값이 도대체 어떻게 되길래, 정의되지 않고 무시하는게 좋다고 적혀있는 것일까? 궁금하다.

 

 

3. view에 transform을 해보면서 비교하기

: 직사각형의 frame이 (x: 60, y: 60, width: 40, height: 100)인 초록색(UIColor.green, alpha: 0.7) view를 추가해보면 다음과 같다.

 

그림1. 직사각형 View를 하나 추가하였음.

 

이 상황에서, 직사각형의 frame이 (x: 60, y: 60, width: 40, height: 100)인 파란색(UIColor.blue, alpha: 0.7) view를 추가하고, 파란색 View의 transform을 CGFloat.pi * 0.25만큼 회전(rotation)시키면 다음과 같다.

 

1
blueView.transform = CGAffineTransform(rotationAngle: .pi * 0.25)
cs

 

그림2. rotation시킨 파란색 view 추가

 

그림 2의 초록색 view와 파란색 view의 frame과 bounds를 출력시켜 보았다.

 

* 초록색 view와 파란색 view의 frame

초록색 view의 frame : (x: 60, y: 60, width: 40, height: 100)

파란색 view의 frame : (x: 30.502525316941686, y: 60.50252531694167, width: 98.99494936611664, height: 98.99494936611666)

 

파란색 view의 frame 값이 다르다. origin(x, y)은 물론이고, size(width, height)도 초록색 view와 많이 다르다.

 

 

* 초록색 view와 파란색 view의 bounds

초록색 view의 bounds : (x: 0, y: 0, width: 40, height: 100)

파란색 view의 bounds : (x: 0, y: 0, width: 40, height: 100)

 

이번에는 두 view의 bounds가 동일하다. 사실 생각해보면, bounds가 본인 관점의 좌표계 기준으로 보기 때문에... 회전 여부와 관계없이 같아야 하는게 당연해 보인다.

 

 

 

초록색 view와 파란색 view의 frame이 다른 것을 보고, 파란색 view의 frame가 어떤 값인지 궁금하였다. 그래서 파란색 view의 frame과 동일한 다른 view를 그려보았다.

 

* 파란색 view의 frame과 동일한 view

파란색 view의 frame과 동일한(x: 30.502525316941686, y: 60.50252531694167, width: 98.99494936611664, height: 98.99494936611666) 회색(UIColor.gray, alpha: 0.7) view를 그려보면 다음과 같다.

 

그림3. 파란색 view의 frame이 그리는 영역

 

회색 view가 그려진 영역을 보고, frame을 다음과 같이 추론해보았다...

 

"frame은, transform을 한 다음의 (x: minX, y: minY, width: maxX - minX, height: maxY - minY)으로 추정된다."

 

 

* transform된 view에 CGAffineTransform.identity로 transform해보기

파란색 view는 이미 transform이 되었다. 이 상태에서 transform에 CGAffineTransform.identity를 넣어주면 어떻게 될까? 그림2의 파란색 view의 transform에 CGAffineTransform.identity을 넣어주면 다음과 같다.

 

그림4. transform된 view에 .identity로 transform하기

 

 아무런 transform하기 전인, 초록색 view를 완전히 덮었다. CGAffineTransform.identity는 CGAffineTransform(rotationAngle: .pi * 0.25)의 Inverse Matrix가 아니다. 여기서 추론해보면...

 

"transform되기 전 view의 frame을 알 수 있도록, vector 같은게 저장되어 있을 것."

 

 

4. Conclusion

: view에 transform을 해보며 bounds와 frame의 특징과 관계를 추론해볼 수 있었다. 정리해보면 다음과 같다.

 

1. CGAffineTransform.identity으로 transform된 view의 frame을 알 수 있도록,

   vector 같은 것이 저장되어 있을 것.

2. bounds는 아마도, CGAffineTransform.identity으로 transform된 frame의 size를 출력해줄듯.

3. 우리가 접근하는 frame은 transform한 다음의

   (x: minX, y: minY, width: maxX - minX, height: maxY - minY)일 듯.

 

어디까지나 나의 추론이므로, 어쩌면 잘못된 부분이 있을지도 모른다. 그러니까 UIKit, CoreGraphic 코드좀 보여주세요 애플님.

 

 

여담이지만, view의 transform을 적용할 때... 조심할 점이 있다. CGAffineTransform문서를 보면, 3x3 matrix가 있는데... 여기에 UIView의 좌표를 이용하여 계산을 하면 무엇인가 계산결과가 이상하다는 것을 알 수 있다. Quartz 2D Programming Guide를 보면 그 이유를 알 수 있는데, Quartz가 CGAffineTransform의 matrix를 이용해서 계산을 한다. 하지만 Quartz와 UIKit은 Coordinate System이 안맞으니, 계산결과가 이상할 수 밖에.

 

아 그리고, 이런 고민을 할 수 있도록 소재를 던져주신 Sadarm님 감사합니다.

:
Posted by syjdev

Intro

 최근에, 동료와 함께 Demo App을 개편하는 일을 하게 되었다. Demo App의 주 목적은, 우리가 개발 및 배포하는 SDK에 대한 가이드와 간단한 동작을 보여주는 것이다. 그 외에 다른 복잡한 비즈니스적 요구사항이 없었고 시간상 여유가 있어서, 새로운 Architecture로 개발하기로 했다. 

 

 선택한 Architecture는 VIP인데, VIP를 설명하는 글에 따르면 Uncle Bob의 Clean Architecture를 참고해서 iOS 개발에 적합한 Component들로 구성한 Architecture라고 한다. 나는 VIP Architecture에 맞게 개발하기 위해 관련 글들(친절하게도, Sample Project도 공개되어 있다)을 읽었고, 생각한 것들을 여기에 정리해보고자 한다.

 

 

Vip의 Components 훑어보기

Clean Swift (VIP) Flow Diagram - 출처 https://hackernoon.com/introducing-clean-swift-architecture-vip-770a639ad7bf

 위의 Flow Diagram은, VIP를 구성하는 Component들을 보여주고, 어떻게 상호작용하는지를 보여준다. 역할이 애매하여 ViewController에 많은 코드를 넣는 실수를 줄이고자 Interactor와 Presenter를 두었고, Interactor에서도 Data를 가져오는 역할을 Worker가 담당하도록 하였다.

 

 Interactor는 View단의 요청을 받아서 자신의 역할을 수행하고, Presenter는 View단을 위한 Presentation Logic 처리를 담당한다. Router는 ViewController간 Transition 처리를 담당한다. Worker는 여러가지 Business Logic에 따라 다양한 처리를 하게 된다.

 

 

 

고민되는 것들

 

1) Router는 ViewController와 의존관계를 맺어야 하는가?

 Transition을 하기위해, 다음 화면을 노출하기 위한 정보가 필요할 수 있다. 예를들어, 쇼핑몰의 상품 목록에서 상품 상세화면으로 넘어가는 시나리오가 있다면 상세하게 보여줄 상품에 대한 ID가 필요할 것이다. Router의 역할중에 Transition시 Data를 전달하는 것도 있는데, Sample Project에서는 이를 위해 Router가 DataStore(Protocol)라는 것을 갖고 Interactor가 이 Protocol을 구현한다. 여기서 어색하다는 느낌이 들었는데, VIP Flow상 Router와 소통하는 Component는 ViewController 뿐이다. Sample Project는 VIP Flow와 맞지 않다.

 

 코드를 수정하여 ViewController에서 Transition에 필요한 정보가 있는 Model을 Router로 넘겨주게끔 수정하면 어떨까? VIP에서 ViewController에 Model을 넘겨주는 Component는 없다. 그리고 View가 Model을 직접 보는 것은, 일반적으로 좋지 않다.

 

 그렇다면 Presenter에서 'Transition에 필요한 추상화된 정보'를 ViewController로 넘겨주고, ViewController는 그 정보를 Router로 넘겨주면 어떨까. 그리고 ViewController는 'Transition에 필요한 추상화된 정보'를 그대로 Router로 넘기는 것이다. 그럴꺼면 애초에 Router가 ViewController가 아니라 Presenter와 상호작용 하는게 어떨까?

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protocol Presenter {
    func presentSampleDetail(data: Data?)
}
 
protocol Router {
    func transition(from viewController: UIViewController, data: Data?)
}
 
protocol SampleRepresentable { }
 
class SamplePresenter: Presenter {
    var router: Router?
    var sampleRepresenter: SampleRepresentable?
    
    // data는 Interactor가 넘겨줄 것이다.
    func presentSampleDetail(data: Data?) {
        guard let viewController = sampleRepresenter as? UIViewController else { return }
        router?.transition(from: viewController, data: data)
    }
}
cs

 

 Router를 Presenter와 상호작용을 하도록, 위와같이 예제를 작성해보았다. 위의 간단한 예제를 작성하기 전에 Router와 상호작용 해야하는 Component가 Presenter 여야 할지, Interactor 여야 할지 고민이 되었다.

 

 Interactor는 이름이 온갖 역할을 다 해도 될것 같은 느낌을 준다(당연히 그러면 안된다). VIP Flow상 ViewController가 Interactor 무언가 요청하면, Interactor가 동작하고 필요에 따라 Presenter를 통해 ViewController가 갱신된다. 이때, Interactor는 presenter에 '무언가를 나타내줘'라고 요청하는 게 자연스럽다. 어떻게 보여줄지는 Interactor의 관심사가 아니다. 따라서 Presenter가 UILabel에 노출시킬 수 있는 단순한 문자열을 ViewController에 넘겨줄지, 다른 ViewController로 Transition하여 보여줄지 Interactor는 관심을 가져선 안된다고 판단했다.

 

 

2) Worker는 이해가 잘 안된다

: Worker같은 이름은 조심해서 쓸 필요가 있다고 생각한다. 'Manager라는 이름을 쓰지말자, 자제하자'와 비슷한 이유에서다. 아 그리고, hackernoon에 게시된 VIP 소개글을 읽을 때는 주의해서 읽는게 좋을 것 같다. Worker의 역할을, 마치 'Network나 내부 저장소에서 Data를 가져오는 Component'라고 오해하게끔 글을 적어놓았기 때문이다. 이건 조금만 생각해봐도 말이 안되는데, 성능상의 이유로 Network를 통해 가져온 Data를 내부 저장소에 Caching 해두는 시나리오를 생각해보자. 이때 Interactor가 Data를 요청한다면, Cached Data인지 확인해서 적절한 Worker에 요청해야 하는가? 당연히 그렇지 않을 것이다. Interactor는 Data가 필요할 뿐이지, 그게 Cached Data인지 Network로 요청해야 하는지에 대해서는 관심사가 아니기 때문이다(clean-swift에서 소개하는 VIP에서는 이렇게 적혀있지 않다).

 처음에는 Business Logic을 처리하는 Component를 Worker라고 굳이 명시해야 했는지 의문이 들었지만, VIP가 Clean Architecture를 참고하여 만든 Architecture라는 것을 떠올렸다. 아마도 VIP를 창조해낸 사람은, Interactor가 Model에 직접 의존하는 것을 가능한 막고 싶었던 것이겠지.

 

 

 

마무리하며

 VIP를 설명하는 글을 읽고, VIP는 현실의 일부 복잡한 문제들에 적용하기 어렵다는 생각이 들었다. 그냥 어떤 문제에서 잘 쓰일 수 있는 Solution인 것이다. 고민한 내용을 동료에게 공유하여, 현재 작업중인 Demo App 개편을 어떻게 할지 이야기를 나눠보아야 겠다. 아직 경력와 역량이 부족한 내가, 잘못 생각한 걸지도 모르기 때문이다.

 

 

 

Reference

- https://clean-swift.com/clean-swift-ios-architecture/

https://hackernoon.com/introducing-clean-swift-architecture-vip-770a639ad7bf

https://github.com/Clean-Swift/CleanStore

:
Posted by syjdev

 함수내에서, 값(Value)으로 전달받은 인자(Argument)는 참조(Reference)로 전달받는 인자와 차이가 있다. 참조로 전달받은 인자는 변경이 일어났을 때 본래의 객체도 같이 변경되지만 값으로 전달받은 인자는 그렇지 않다.

 

 

1
2
3
4
5
var integer = 5
func multiply(integer: Int, multiplier: Int) {
    //error, integer is defined as a let.
    integer = integer * multiplier
}
cs

 
 애초에 값으로 전달받은 인자는 상수라서 변경조차 불가능하다. 값으로 전달받은 인자를 함수 내에서 수정하려면, 함수의 매개변수(Parameter)에 'inout'이라는 키워드를 붙이면 가능하다.

 

 

1
2
3
4
5
6
7
var integer = 5
func multiply(integer: inout Int, multiplier: Int) {
    integer = integer * multiplier
}
multiply(integer: &integer, multiplier: 2)
print(integer) //10
 
cs



 매개변수에 inout 키워드가 붙었으니, multiply에는 integer의 참조가 전달된 것일까? 

inout Parameter에 대한 Apple의 설명을 보면 이야기가 좀 다르다.



In-Out Parameters: In-out parameters는 다음과 같이 전달됩니다.
1) 함수가 호출됬을 때, 값으로 전달받은 인자는 복사(Copy)됩니다.

2) 함수 내에서는 복사본이 변경됩니다.

3) 함수가 반환(Return)될 때, 복사본의 값이 본래의 값에 할당(Assign)됩니다.

 

 

 Apple의 설명을 확인하기 위해, 간단한 코드를 작성하면...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var number: Int = 2 {
    didSet {
        print("number was assigned.")
    }
}
 
func function(arg: inout Int) {
    print(number) //2
    arg = 5
    print(number) //2
    arg = 10
}
 
function(arg: &number) //"number was assigned."
 
cs

  

 function 내에서 전달받은 인자를 5로 바꾸어도, number는 변하지 않는다. function이 호출된 다음 number의 didSet이 호출되는 것을 볼 수 있는데, 이것을 통해 function 반환 후 본래의 값에 새 값이 할당된다는 것을 확인할 수 있다.

 

 이것은 참조가 전달된 Call-By-Reference와 다르며, Copy-In Copy-Out 또는 Call-By-Value Result라고 불려진다.

 

 

※ 2023.01.24에 추가된 내용

 Swift Docs에 적혀있는, In-Out Parameters의 Optimization에 따르면 Physical Address에 위치한 변수를 In-Out Parameter의 arg로 전달하면 Call-By-Reference로 동작한다고 적혀있다(Call-By-Reference는 Copy로 인한 Overhead는 없지만 Copy-In Copy-Out의 결과와 동일하다).

 

 Compilier의 Optimization Level도 바꾸어 보고, 코드도 이리저리 수정해가며 테스트를 해보았지만... In-Out Parameter를 정의한 함수에서 Call-By-Reference로 동작하는 것을 확인하는 데에 실패했다. Compiler Optimization 결과에 따라 In-Out Parameter의 동작이 달라질 수 있는 것 같으니, 인위적으로 상황을 재현하는 것은 어려울 것 같다.

 

 Swift Docs에 적힌대로, In-Out Parameter를 사용한 함수를 작성할 때, Call-By-Reference를 고려하지 말고 Copy-In Copy-Out로 동작한다고 생각하고 작성하자.

:
Posted by syjdev

 몇년전 코드 리뷰 날, 선배 개발자 한분이 새로운 것을 보여주셨었습니다.

Objective-C의 코드를 두 개 이상의 파일로 나누는 것이었는데요. 

지금은 Swift로만 개발해서 활용하고 있지 않지만, 기록 차원에서 글로 작성해보았습니다.



 샘플로 만든 프로젝트의 'Project Navigator'는 아래와 같습니다.



 특이하게도 ViewController+DiviedFile.m이라는 파일이 있는데요, ViewController.m에 

넣으려던 코드를 별도의 파일로 분리한 것입니다. 



어떤식으로 분리한 것인지 보기 위해 ViewController.m의 코드를 살펴보면 아래와 같습니다.



 FILE_DIVIDE_FLAG라는 상수가 선언되어 있고, 

마치 헤더 파일을 추가한 것처럼 ViewController+DiviedFile.m을 추가했습니다.



 ViewController+DiviedFile.m의 코드도 볼께요.



 매크로를 사용해서, 상수 존재 여부에 따라 @implementation과 @end를 해당위치에 삽입하는 코드가 있습니다. @implementation과 @end가 있어야 ViewController+DiviedFile.m을 정상적인 형태를 갖춘 코드로 인식하고, 컴파일이 될때는 @implementation과 @end가 없어야해서 지시자(Directive)를 저런 형태로 작성한 것입니다.


 여기까지 작업하고 빌드를 하면, 심볼이 중복된다는 에러를 볼 수 있습니다. 해결하려면 ViewController+DiviedFile.m를 컴파일 대상이 되지 않게 하면 됩니다(전처리 단계에서 ViewController.m에서 ViewController+DiviedFile.m의 내용을 해당 위치에 추가하기 

때문에 괜찮습니다.).



 Xcode에서는 Target -> Build Phases -> Compile Sources에서

ViewController+DiviedFile.m를 지워주면 됩니다.



:
Posted by syjdev