달력

3

« 2024/3 »

  • 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
  • 31

 이런저런 이유로, 현재는 회사에서 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