달력

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

: 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