달력

1

« 2025/1 »

  • 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

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