2019년 12월 31일 화요일

Complex UI/Animations on Android 정리

https://proandroiddev.com/complex-ui-animation-on-android-8f7a46f4aec4

1. 스크롤 할 때 툴바에 애니메이션 효과 주기

AppBarLayout의 behavior에 내가 사용하려는 애니메이션 효과를 가지는 커스텀 CoordinatorLayout behavior을 지정해준다.

아래와 같은 클래스를 만들고 onStartNestedScroll(스크롤이 시작할 때 호출되는 함수)과 onNestedScroll(스크롤중일 때 호출되는 함수)을 구현한다.

class ToolbarBehavior : CoordinatorLayout behavior<AppBaLayoutr>() { ... }

- onStartNestedScroll

위 아래로의 스크롤(vertical)일 때 true를 리턴한다.

- onNestedScroll

dyConsumed의 값이 때라 scroll up인지 down인지 파악한다. dyConsumed가 양수이면 scroll down이고, dyConsumed가 음수이면 scroll up임을 의미한다.

height를 변경하고 나면 requestLayout을 호출해 주어야 한다.
위치를 바꿀 때 translationX와 translationY를 사용한다.
scale을 적용할 때 scaleX와 scaleY를 사용한다.

추가로 ToolbarBehavior의 효과를 적용할 때 관련 뷰들을 참조하고 있어야 한다. 따라서, onStartNestedScroll이나 onNestedScroll이 호출되기 전에 이러한 관련 값(Views, height, 최소 height등)들을 미리 설정해 놓고 있어야 한다.

2. RecyclerView의 item의 height 변경할 때 애니메이션 효과 주기

아이템의 평상시 height와 커졌을 때의 height를 계산해놓고 있다가 사용자가 아이템을 클릭할 때 이에 맞추어 애니메이션 효과를 주는 것이 필요하다.

- Adapter의 onViewAttachedToWindow에서 height를 계산한다.

onViewAttachedToWindow는 Adapter에서 만든 뷰가 RecyclerView에 연결될 때 호출되는 함수이다.

- 평상시 height와 커졌을 때의 height를 계산하는 방법은 다음과 같다.

doOnLayout에서 평상시 뷰의 height를 계사한다. -> expanded view를 visible로 설정한다. -> doOnNextLayout에서 expanded view의 height를 계산한다. -> expanded view를 hide한다.

위와 같이 하면 사용자에게 뷰가 커졌다 작아졌다 하는 것을 보여주지 않고 각각의 height를 알 수 있게 된다.(이유는? : 아마도 onDraw가 불리기 전에 관련 layout이 실행되는 동안 height를 알아내서 사용자에게는 안보이게 되는 것이 아닐까 추측. 정확하게는 모르겠네요.) 여기서 한가지 더 주의할 것은 expanded view의 hide를 호출하는 부분을 expandedView.post { ... } 안(또는 doOnPreDraw에서)에서 해주어야 한다는 점이다.(expandedView.visible = false는 layout이 일어나도록 하지 않는다.)(post나 doOnPreDraw를 사용하면 onDraw가 불리기 전에 layout이 다시 일어나게 되는 걸까요? 이부분도 잘 모르겠군요.)

아이템을 클릭하면 이전에 커진 뷰가 있다면 원래 크기로 돌려야 하고 새로 클릭된 뷰를 크기를 키워야 한다. RecyclerView의 findViewHolderForAdapterPosition를 통해 이전에 커진 뷰를 찾을 수 있다.

ValueAnimator를 사용해서 점진적으로 커지는 애니메이션 효과를 줄 수 있다.

3. ViewPager2의 위치가 변경될 때 관련 탭의 위치도 같이 변경하기

ViewPager의 onPageScrolled에서 position과 positionOffset을 받아서 탭의 위치를 변경한다.(이 문서에서는 RecyclerView의 scrollBy를 사용해서 탭의 위치를 변경했는데 RecyclerView의 layout manager가 LinearLayoutManager라면 LinearLayoutManager의 scrollToPositionWithOffset를 사용하는 방법도 있다. 이게 더 간단한 방법인듯 하다.)

4. Filter sheet animation

네 단계로 나누어진 애니메이션 효과를 준다. 각각의 애니메이션이 동작하는 순서를 위해 AnimationSet을 사용한다.

a. Fab 버튼의 path animation
b. RecyclerView의 아이템들을 shrink하는 scale down animation
c. Fab 버튼이 Filter sheet로 변경되는 animation
d. 최종 뷰들 보여주기

각각을 자세히 살펴보자.

a. Fab 버튼의 path animation

현재 위치에서 화면의 중간으로 이동한다.
애니메이션을 위해 ValueAnimator를 사용하는데 이때의 progress를 ArcMetric를 사용해서 변경된 형태로 뷰의 x와 y에 적용한다.

b. RecyclerView의 아이템들을 shrink하는 scale down animation

ValueAnimator를 써서 현재 화면에 보이는 아이템들을 scale down 한다.

c. Fab 버튼이 Filter sheet로 변경되는 animation

Fab 버튼의 크기를 최종 뷰의 크기로 변경한다. 이때 0 ~ 0.8 까지는 원의 형태로 커지다가 0.8 ~ 1 로는 사각형으로 커지게 한다.

d. 최종 뷰들 보여주기

개별 뷰들을 화면에 보여준다.

5. Filtering animation

여섯 단계로 나누어진 애니메이션이다.

a. Fab Collapse Animation
b. Bottom Bar Animation
c. Tabs and ViewPager fade out animation
d. Close Icon rotate animation to simulate loading

rotation값을 변경한다.

e. Fab Arc Path animation
f. RecyclerView items scale down animation

Crafting Interpreters - 8. Statements and State 를 읽고

http://craftinginterpreters.com/statements-and-state.html

Statement는 값을 내는 것이 아닌 다른 무언가(side effect - output을 내거나 state를 변경하거나 하는 것)를 하는 것이다.

변수 선언을 하면 어딘가에 저장이 되어 있어야 나중에 이 변수의 값을 사용할 수 있습니다. 이를 위해 Environment라고 하는 것이 필요하게 됩니다.
Environment는 HashMap으로 (변수 이름, 값)을 내부에 저장하고 있습니다. 현재로서는 값을 저장할때는 define, 값을 꺼내올때는 get, 값을 재할당할때는 assign정도의 함수만 가지고 있으면 되겠습니다.

변수가 선언되고 사용되는 위치에 따라 scope가 지정됩니다. 이것은 block({})으로 지정합니다.  scope에는 lexical scope과 dynamic scope이 있는데 우리는 lexical scope를 사용합니다.
가장 상위를 global scope라고 하고 이후의 코드에서 별도의 scope를 가지고 싶으면 block을 사용합니다. 이러한 경우 block 내부의 변수가 외부의 변수를 가릴 수도 있고 값을 변경할 수도 있습니다. 이에 대한 처리를 위해 Environment에 parent Environment를 추가합니다. 어떤 값을 찾을 때 자기 자신이 가지고 있지 않으면 바로 에러를 발생시키지 않고 parent Environment에게 위임하는 거지요. parent에게서 변수가 찾아지면 그 값을 사용하면 되고 여전히 찾아지지 않으면 그의 parent에게 위임합니다. 이렇게 global environment까지 찾아 보게 합니다.

2019년 12월 26일 목요일

Crafting Interpreters - 7. Evaluating Expressions 를 읽고

http://craftinginterpreters.com/evaluating-expressions.html

앞장에서 파서를 만들어 봤다. 이번 장에서는 expression의 값을 구해보자.

Lox는 dynamically typed language이므로 런타임에 타입을 체크한다. 그러면 타입은 어떻게 알 수 있을까? JVM에서 Lox를 구현하고 있으므로 JVM의 도움으로 쉽게 체크할 수 있다.(자바는 instanceOf로 코틀린은 is로 타입을 확인한다.)

또한 Lox에서 사용하는 primitive type들은 자바에서의 타입과 쉽게 매치할 수 있다.

Lox typeJava representation
nilnull
BooleanBoolean
numberDouble
stringString

나중에 함수, 클래스, 인스턴스 등을 추가하면 좀 더 복잡해질 것이다.

런타임에 에러가 발생하면 어떻게 할까?(자바라면 ClassCastException을 던지고 stack trace를 보게 될 것이다.) 에러가 발생하면 에러의 위치(line number)와 에러 메시지를 화면에 보여주자.

2019년 12월 13일 금요일

Crafting Interpreters - 6. Parsing Expressions 를 읽고

http://craftinginterpreters.com/parsing-expressions.html

앞장에서 문법을 어떻게 표현할 것인지를 살펴보고 간단한 문법을 Context-free grammar로 표시해 봤습니다.

이러한 문법 표현에서 모호함(ambiguity)이 있을 수 있습니다. 예를 들면, 1 + 2 * 3 의 경우 결과가 어떻게 나와야 할까요? 앞에서 부터 계산을 하면 결과는 9가 될 것이고, 곱하기를 먼저 한다면(우리가 수학에서 배웠듯이) 결과가 7이 될 겁니다.

이러한 모호함을 우선 순위(precedence)와 결합 법칙(associativity)을 정해서 해결할 수 있습니다.(또는 먼저 계산되어야 하는 값에 괄호를 사용할 수도 있을 겁니다.)

토큰을 분석(parsing)하는 방법에는 여러가지 방법이 있는데 여기서는 Recursive descent parsing을 살펴볼 겁니다. 파서를 만드는 가장 간단한 방법이지만 그렇다고 만만하게 볼 파서는 아닙니다. 실제로 GCC나 V8(JavaScript)이나 Roslyn(C#)등에서 사용되고 있거든요.

토큰을 분석하다 에러를 만나면 어떻게 하는 것이 좋을까요? 처음으로 만나는 에러에서 멈추고 그 부분을 표시해주는게 좋을까요? 이게 가장 간단한 방법이기도 하지요. 하지만, 사용자한테는 매번 에러가 나타날 때마다 바로바로 표시해 주는 것보다는 전체에서 발생할 수 있는 에러를 한번에 표시해주는게 좋을겁니다. 이걸 error recovery라고 부릅니다.

에러가 발생해도 계속 진행하면서 이후의 에러도 확인하려면 어떻게 해야 할까요? 에러가 발생한 지점부터 특정 지점까지는 무시했다가 그 다음부터 토큰 분석을 다시 시작해야 할겁니다. 안그러면 엄청나게 쓸데없는 에러가 표시되겠지요. 에러가 발생하면 파서는 바로 panic mode로 들어가고 다시 정상적으로 파싱을 시작할 수 있는 지점을 만나면 panic mode를 벗어나게 됩니다. 이러한 과정을 synchronizaion이라고 합니다.

2019년 12월 7일 토요일

Crafting Interpreters - 5. Representing Code 를 읽고

http://craftinginterpreters.com/representing-code.html

앞장에서 살펴본 내용은 단순한 토큰의 나열이었죠.

예를 들어 1 + 2 * 3 - 4 가 있다고 하면 1, +, 2, *, 3, -, 4 이렇게 개별 토큰의 나열을 만드는 것을 해봤습니다. 근데 이것만으로는 아무 의미가 없습니다. 계산을 한다고 할 때 앞에서 부터 계산을 하는데 중간에 곱하기가 있으면 곱하기를 먼저 한다던지 하는 어떤 법칙이 필요하죠. 이걸 표현할 필요가 있습니다. => 문법

문법을 Context-Free Grammar로 표현할 수 있고 이 문법을 통해 어떤 형태가 옳은지를 표현할 수 있습니다. 이에 맞지 않으면 틀린 표현이 되는 것이겠지요.

아래와 같이 문법에 맞는 rule들을 표시하는데 각각의 rule은 head와 body로 표시합니다.

A -> a
B -> b
(A는 a로 변환할 수 있고, B는 b로 변환할 수 있다는 의미입니다.)

컴퓨터 과학에서는 이것을 아래와 같이 Backus-Naur form의 형태로 많이 사용합니다.

expression  literal
           | unary
           | binary
           | grouping ;

literal     NUMBER | STRING | "true" | "false" | "nil" ;
grouping    "(" expression ")" ;
unary       ( "-" | "!" ) expression ;
binary      expression operator expression ;
operator    "==" | "!=" | "<" | "<=" | ">" | ">=" 
| "+" | "-" | "*" | "/" ;

오른쪽에 있는 값들 중 "true"나 "==" 등은 더이상 쪼개 질 수 없기 때문에 terminal이라 부르고, expression이나 operator등은 다른 rule로 더 쪼개질 수 있기 때문에 nonterminal이라 부릅니다.

이 문법에 대한 data structure는 tree로 표현할 수 있습니다. 이것을 syntax tree라고 합니다.
(이처럼 보통 트리를 많이 사용합니다. 하지만, 다르게 표현할 수도 있는데요. 예를 들면, 바이트코드로 표현하는 방법이 있습니다.)

트리를 순회하면서 작업을 하려면 어떻게 하는게 좋을까요? 이와 관련하여 Expression problem이라는 것이 있습니다.
(이와 관련한 내용은 다음의 링크를 참고: http://www.haruair.com/blog/3338)

syntax tree를 보기 좋게 표현할 수 있으면 좋겠죠?
트리를 텍스트로 바꾸어 보기 좋게 표현해 주는 것을 pretty printer라고 부릅니다.
(이와 관련한 내용은 다음의 링크를 참고: https://jooyunghan.gitbooks.io/a-prettier-printer-kr/content/chapter1.html#sec1)



Crafting Interpreters - 4. Scanning 을 읽고

http://craftinginterpreters.com/scanning.html

인터프리터의 간단한 동작 단계는 scanning -> parsing -> evaluating code 로 말 할 수 있습니다. 4장에서는 이중 scanning에 대해 설명합니다.

다음과 같은 코드가 있다고 하면,

var language = "lox";

이 코드는 다음의 5개의 토큰으로 분할됩니다.

var, language, =, "lox", ;

이러한 개별 토큰들의 타입을 TokenType으로 분류합니다. 분류된 토큰들은 이후에 parsing 단계에서 사용되어야 하는데 그 때 필요한 정보들을 저장하고 있어야 하겠죠.

그래서,  Token class를 만듭니다. 이 class는 다음의 정보들을 저장하고 있습니다.

type: 토큰의 타입(TokenType)
lexeme: 토큰 string
literal: 토큰의 값
line: 토큰이 있는 소스의 라인값으로 에러 표시에 사용합니다.

lexeme과 literal의 차이에 대한 예

"lox"의 경우 lexeme은 "lox"이고, literal은 "를 제외한 lox 이다.
1234 의 경우 lexeme은 1234이고, literal은 double값인 1234.0이다.

토큰을 나눌 때 주의사항

orchid의 경우 or과 chid로 분리할 것인가 orchid로 분리할 것인가: 긴 토큰으로 분리하는 형태로 한다. - maximal munch

2019년 11월 25일 월요일

The Mystery of Mutable Kotlin Collections 를 읽고

https://proandroiddev.com/the-mystery-of-mutable-kotlin-collections-e82cbf5d781

Kotlin에서 MutableList는 List는 아래와 같다.

public interface Mutable:ist<E> : List<E>, MutableCollection<E> {}
public interface List<out E> : Collection<E> {}

List: read-only access
MutableList: read/write access

- MutableListOf(...)나 listOf(...)를 통해서 만들어진 리스트는 MutableList로 인식된다.
- List 인터페이스를 Kotlin으로 직접 구현하면 MutableList로 인식하지 않는다.

위처럼 되는 이유는?
Kotlin에서의 List는 mock interface이기 때문이다.
Kotlin에서의 List는 컴파일시 사라지고 Java에서의 List로 변환된다.

2019년 11월 22일 금요일

Public API challenges in Kotlin 요약

https://jakewharton.com/public-api-challenges-in-kotlin/

코틀린의 data class 만이들 사용하실 겁니다. 자바에서 일일이 코딩해야 되는 내용을 매우 간단하게 만들어 주니까요. 그런데
이 data class를 쓰는 경우 바이너리 호환에 주의해야 합니다. 클래스의 내부 필드에 변경이 있는 경우 바이너리 호환이 안될
수가 있기 때문입니다.

1. 새로운 필드를 중간에 추가하는 경우 코틀린에서 제공하는 ComponentN()
함수가 맞지 않게 됩니다. 따라서, 새로운 필드를 추가하는 경우에는 항상 마지막에 추가해야 합니다.
2. data class의 경우 copy 함수를 자동으로 생성해 줍니다. 그런데 필드가 새로 추가되는 경우 copy()의 signature가 바뀌어
버리는 문제가 발생할 수 있습니다. 결국 바이너리 호환이 필요한 경우라면 data class를 쓰기보다는 직접 관련 함수 구현을 다
해주는 것이 좋겠습니다.
3. class 생성시 자유도를 주기위해 builder 패턴을 많이 사용합니다. 이 경우 필드에 @set:JvmSynthetic 를 사용해서 get은
노출하지만 set은 노출하지 않을 수 있습니다.
코틀린의 경우에는 top-level function이나 DSL을 통해 인스턴스를 생성하는 방법을 많이 사용합니다. 이 경우 자바에서는
바이너리 호환 문제가 발생할 수 있으므로 @JvmSynthetic을 사용해서 자바쪽에 노출이 되지 않도록 해줍니다.

2019년 11월 21일 목요일

Composition over Inheritance: Adding a Material Speed Dial to a Floating Action Button 요약

https://proandroiddev.com/composition-over-inheritance-adding-a-material-speed-dial-to-a-floating-action-button-1e646995ab49

FAB 버튼을 누르면 추가 버튼이 나오는 UI를 생각해보자. 어떻게 구현하는 것이 좋을까? Custom View를 만드는 방법을 생각해 볼
수 있다. 이렇게 구현된 많은 오픈소르 라이브러리들이 있다. 그런데 이 라이브러리들의 동작이 내가 원하는 것과 다르면 어떻게
해야 할까? 클래스를 상속받아서 내가 원하는 형태로 변경하면 될까? 이렇게 하려면 상속이 가능해야 하고 또한 내가 원하는
함수를 override 해서 처리할 수 있게 되어 있어야 한다. 그렇지 않다면 힘들것이다.

View와 Behavior를 분리해서 작성하는 건 어떨까?(Composition over inheritance)

1. FAB를 클릭했을 때 보여주는 PopupWindow 작성

1.1. anchor view의 위치에 따라 PopupWindow의 위치 지정: doOnLayout
1.2. PopupWindow는 FragmeLayout으로 구성하고 addView를 통해 내부에 필요한 view를 넣는다 -> LinearLayout으로 구성
1.3. LinearLayout의 아래에서 부터 item을 넣어야 하므로 MIRROR를 사용
1.4. LayoutAnimationController를 사용하여 item을 보여줄 때 애니메이션을 보여준다.
1.5. ripple 효과를 위해 RippleDrawable을 만들고 button의 background에 설정

2. FAB를 클릭시 동작 지정

2.1 버튼을 회전시키는 spring animation
2.2 1에서 만든 PopupWindow 보이기
2.3 PopupWindow의 dismiss시 동작 설정

위처럼 1과 2의 형태로 분리하면 어떤 View를 사용해서라도 추가 버튼이 나오는 형태를 구현할 수 있다.


2019년 11월 14일 목요일

Injection into Android Component’s Constructors is real 요약

https://proandroiddev.com/inject-into-android-component-constructor-4f5ddd27d06

안드로이드는 Activity와 Fragment를 직접 생성하지 않고 OS가 생성을 해줍니다. 그러다보니 생성시 필요한 값이 있을 때
생성자에 넣어줄 수는 없고 Activity.onCreate()나 Fragment.onAttach()에 넣어주게 되죠. 그러다보니 lateinit을 사용하게
됩니다.
그렇게 큰 불편함은 없지만 이와이면 생성자에서 값을 생성할 수 있으면 좋을텐데요. Activity의 경우에는
AppComponentFactory를 사용해서, Fragment의 경우에는 FragmentFactory를 사용해서 생성자에 값을 넣어줄 수 있습니다.
다만, AppComponentFactory는 안드로이드 9 부터 사용이 가능하다는 단점이 있네요. 하지만 요즘 추세는 Single activity이니까
큰 문제 없겠죠?

1. FragmentFactory를 상속한 InjectFragmentFactory를 만든다.
2. Application의 registerActivityLifecycleCallbacks에 SetFragmentFactoryActivityCallbac를 등록한다.
3. SetFragmentFactoryActivityCallback는 EmptyActivityLifecycleCallbacks를 상속해서 만드는데 onActivityCreated에서
fragmentManager에 SetFragmentFactoryFragmentCallback를 등록하고 FragmentFactory를 설정한다.
4. SetFragmentFactoryFragmentCallback는 FragmentManager.FragmentLifecycleCallbacks를 상속해서 만드는데 child
fragment에도 FragmentFactory를 설정해준다.

여기까지가 FragmentFactory를 적용하기 위한 설정이고 다음에는 Dagger를 통해 FragmentFactory에 대한 DI 적용을 한다.

5. Component와 Module 및 Multi bind를 생성한다: AppComponent, AppModule, ComponentProvidersModule, FragmentBindsModule, FragmentKey

Dagger를 통한 생성은 multi bind를 공부해 봐야 할듯 하군요.

https://dagger.dev/multibindings

2019년 11월 4일 월요일

libp2p - Publish/Subscribe 를 읽고

https://docs.libp2p.io/concepts/publish-subscribe/

- Discovery

Distributed hash tables
Local network broadcasts
Exchanging peer lists with existing peers
Centralized trackers or rendezvous points
Lists of bootstrap peers

peer와의 연결에 full-message와 metadata-only 방식이 있다. full-message peering은 메시지를 주고 받을 때 사용하는 연결로서, 최대 연결 가능한 peer의 수를 제한(6정도)하여 연결을 맺는다. metadata-only peering은 full-message로 연결되어 있지 않은 peer들을 더 많이 연결을 맺어 관련 여러 정보들을 주고 받는다.(예를 들면 '난 이런 토픽들에 subscribe하고 있다', '난 이런 토픽에 unscribe 한다.' 등등)

peer는 메시지를 받으면 full-message로 연결된 peer에 메시지를 포워딩한다. 또한, 매 1초마다 metadata-only로 연결된 peer들 중 6개를 랜덤으로 골라서 최근에 받은 메시지를 보낸다. 이렇게 서로서로 받은 메시지가 무었인지를 주고 받음으로서 내가 어떤 메시지를 받았는지 못 받았는지를 확인하고 이에 따라 full-message로 연결될 peer를 조정한다.(기존에 full-message로 연결된 peer를 metadata-only로 조정하고, metadata-only로 연결된 peer를 full-message로 바꾼다던지)

내가 subscribe 하고 있지 않은 topic에도 메시지를 보낼 수 있다. 이를 Fan-out이라 한다. 

libp2p - Stream Multiplexing 을 읽고

https://docs.libp2p.io/concepts/stream-multiplexing/

Stream multiplexing: Transport 단에서 하나의 connection을 맺고 그 위로 application 단에서 필요에 따라 stream을 여러개 열어서 사용

libp2p는 peer와의 연결에 stream multiplexing을 사용한다.

Stream multiplexing을 하는 다양한 프로토콜이 있다.

: mplex, yamux, quic, spdy

libp2p - Addressing 를 읽고

https://docs.libp2p.io/concepts/addressing/

libp2p는 다른 peer와의 연결시 사용하는 프로토콜이 매우 자유롭게 설계되어 있다. 따라서, 어떤 프로토콜을 사용할지를 연결하려는 peer에게 알려주어야 한다.

/ip4/7.7.7.7/udp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

=> ip 주소는 7.7.7.7이고 udp를 사용하고 udp 포트는 4242이고 내 peer id 는 QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N 임을 알리는 multiaddr 표현

NAT 안에 있어서 릴레이가 필요한 경우는 다음과 같이 표시할 수 있다.

/ip4/7.7.7.7/tcp/4242/p2p/QmRelay/p2p-circuit/p2p/QmRelayedPeer

=> 릴레이를 해주는 peer의 id 는 QmRelay이고 이 peer의 ip 주소는 7.7.7.7이고 tcp 포트는 4242이다. 내 peer id는 QmRelayedPeer이다.

libp2p - Peer Identity 를 읽고

https://docs.libp2p.io/concepts/peer-id/

- PeerId

a cryptographic hash of a peer's public key
multihash 포맷을 사용해서 인코딩
보통 base58로 인코딩해서 표시

=> QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

multiaddrs에 표시할 때는 /p2p로 표시한다.

=> /p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

transport 주소와 같이 사용되는 경우에는 다음과 같이 된다.

/ip4/7.7.7.7/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

- PeerInfo

개별 peer에 대한 정보를 담고 있다. 나중에 특정 peer에 접속하려 할 때 사용할 수 있다.


2019년 10월 30일 수요일

Swift's Sequence Inside The Compiler: How for loops work internally 를 읽고

https://swiftrocks.com/swift-sequence-inside-the-compiler-how-for-loops-work.html

Swift 에서 for loop는 어떻게 동작하는 것일까?
초기 버전에서는 c-style for loop 형태였지만 현재는 protocol을 사용하는 syntactic sugar의 형태로 되어 있다.

IteratorProtocol : associatedtype으로 동작할 타입을 지정하고 next() 함수로 값을 가져올 수 있게 한다.
Sequence : Iterator를 지정하고 makeIterator()로 사용할 iterator를 만든다.
Collection : Sequence를 상속. Sequence에 다양한 기능을 추가

for loop를 컴파일하면 어떻게 되는지 살펴보면 makeIterator() -> while -> next() 의 형태로 변경되어 구성되는 것을 볼 수 있다.


2019년 10월 27일 일요일

SwiftUI layout system 을 읽고

https://kean.github.io/post/swiftui-layout-system

1. Layout Basics

safearea

Layout Process

parent가 child에게 '너의 크기 이렇게 할까'라고 제안 -> child는 자신의 크기를 지정 -> parent는 child가 지정한 크기로 child를 자신의 coordinate space 안에서 위치시킨다.

Frame

2.Stack

HStack, VStack, ZStack

Stack Process

stack은 내부의 spacing을 계산하고 자신의 size에서 이 값을 제외 -> child들을 균등하게 나눌 경우의 size를 계산. 이 값은 우선 least flexible child에게 제안. 이 child로부터 지정된 size를 자신의 size에서 제외. 남은 size를 남은 child들을 가지고 size 제안 반복  -> 모든 child는 size를 가지고, stack은 spacing과 align에 따라 child들을 위치시킨다.

3. Environment

libp2p - Protocols 를 읽고

https://docs.libp2p.io/concepts/protocols/

libp2p는 특정한 하나의 프로토콜을 지원하는 형태가 아니라 여러 프로토콜을 지원하고 이 중 원하는 프로토콜을 선택해서 사용할 수 있도록 하고 있습니다.

libp2p는 원하는 프로토콜을 선택하기 위해 크게 다음의 세가지 특징을 사용합니다.

- Protocol Ids

protocol negotiation에 사용하는 unique한 id(예: /my-app/amazing-protocol/1.0.0).
맨 마지막에 버전 정보가 따라옵니다.

- Handler functions

지정된 protocol id를 통한 연결 요청이 들어오는 경우 실행되는 함수
연결을 맺을 때 꼭 버전이 정확하게 맞아야만 handler function이 호출되도록 하지 않아도 됩니다. 이를 위해 match function을 사용합니다. match function이 true를 리턴하면 이에 연결된 handler function이 호출되도록 합니다.

- Binary streams

Bidirectional, reliable delivery of binary data
Supports backpressure

* Protocol Negotiation

연결을 맺고자 하는 peer는 자신이 사용하고자 하는 protocol id를 연결하고자 하는 peer에게 보냅니다. 요청을 받은 peer는 이에 대한 응답으로 연결이 가능한지 아닌지를 표현합니다.
연결을 위해 protocol id를 보낼 때 하나가 아닌 여러개를 보낼 수도 있습니다. 이 경우 요청을 받은 peer는 여러개 중 하나를 선택해서 응답하면 됩니다.

* libp2p가 사용하는 프로토콜

아래의 프로토콜들 모두 protocol buffer를 사용합니다.

- Ping

상대방이 살아있는지 아닌지를 체크합니다.
32 byte의 랜덤 데이터를 보내고 응답을 받습니다.
응답을 받으면 stream을 닫습니다.

/ipfs/ping/1.0.0

- Identify

서로서로에 대한 정보를 교환합니다.(public key, observedAddr)
(public key는 PeerId를 만들어 내는데 사용할 수 있고, observedAddr은 NAT 정보를 알아내기 위해 필요한 정보입니다.)

/ipfs/id/1.0.0

identify/push

Identify에서 약간 변형된 프로토콜로서 request가 와야만 response를 주는 Identify와는 다르게 상대 peer에게 Identify message를 보낼 수 있는 프로토콜입니다.
(relay peer인 경우 연결된 peer의 정보를 다른 peer들에 알려줄 수 있습니다.)
 
/ipfs/id/push/1.0.0

- secio

secure input/output 로서 TLS1.2에 유사한 프로토콜입니다. 현재 TLS1.3으로 변경이 작업중입니다.

/secio/1.0.0

- kad-dht

peer routing와 content routing에 사용하는 프로토콜
/ipfs/kad/1.0.0

- Circuit-Relay

peer들 사이를 relay해주는 프로토콜
/libp2p/circuit/relay/1.0.0

2019년 10월 26일 토요일

Bezier Curve 공부

Drawing Bezier Curves like in Google Material Rally

How I drew custom shapes in bottom bar

How I make CustomBottomSheet

Drawing bezier curves

Cubic Bezier curve : start point와 end point 그리고 두개의 control point로 구성된다.

Bezier curve를 그리기 위해 아래의 세단계를 취한다.

1. data point 지정
2. connection point 지정
3. point들을 연결하는 path를 정하고 canvas에 그린다. 이때 사용하는 함수는 cubicTo이다.

2019년 10월 25일 금요일

uCrop, the Image Cropping Library for Android: Look under the Hood of Our Masterpiece 를 읽고

https://yalantis.com/blog/how-we-created-ucrop-our-own-image-cropping-library-for-android/

[세 파트로 나누어서 구현]

- TransformImageView

ImageView를 상속
소스로부터 이미지 로드
matrix transform(translate, scale, rotate)하기

- CropImageView

crop boundary와 grid 그리기
이미지 이동시 crop boundary에 빈 영역이 생기지 않도록 이미지 위치시키기
지정한 룰(minimum scale, maximum scale 등)에 따라 matrix transform하는 함수 추가
zoom in/out
이미지 crop 하기

- GestureImageView

사용자 제스쳐(zoom, scroll, rotate gestures)

1. TransformImageView

크기가 큰 이미지의 경우 그대로 폰에 로드하면 메모리 문제가 발생할 수 있으므로 이미지를 샘플링해서 로드하는 것이 필요하다. 샘플링에 필요한 값은 screen diagonal로 정한다.
(BitmapFactory.options의 inJustDecodeBounds 를 사용해서 이미지를 메모리로 로드하지 않고 이미지의 정보만을 가져올 수 있다.)

2. CropImageView

이미지가 crop boundary를 전부 채우고 있는지 아닌지 확인 : crop bound의 네 코너가 이미지 안에 들어오는지 아닌지 확인
이미지가 crop boundary 안에 들어오도록 transform 하기 : 이미지의 센터와 crop bound의 센터 사이의 거리 계산. 그리고 이미지가 crop boundary 를 채우고 있지 못하면 scale을 한다.
이미지 crop 하기 :

3. GestureImageView

Android SDK에서 제공하는 GestureDetector와 ScaleGestureDetector를 사용하여 제스쳐 구현.
Rotation Gesture는 제공하지 않기 때문에 직접 구현




2019년 10월 23일 수요일

Testing Camera and Gallery Intents with Espresso 를 읽고

https://proandroiddev.com/testing-camera-and-galley-intents-with-espresso-218eb9f59da9

사진을 찍거나 갤러리에 접근하려면 권한이 필요하기 때문에 관련 작업을 하기전에 안드로이드에 권한을 요청해야 합니다. 앱의 코드에서는 권한을 요청하면 권한이 없는 경우 다이얼로그가 뜨고 여기서 OK를 하면 다음으로 진행이 되게 됩니다.
그러면, 테스트 코드에서는 어떻게 하면 될까요? 아래와 같은 코드를 테스트쪽에 넣어주면 테스트시 권한을 자동으로 부여합니다.

@get:Rule
var mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)

Activity parameter는 다음의 코드로 가져올 수 있습니다.

@get:Rule
var mActivityTestRule = IntentsTestRule(MyActivity::class.java)

- 갤러리로부터 이미지 가져와서 ImageView에 넣는 코드 테스트하기

a. 가장 먼저 image를 로컬에 저장합니다.

b. 그리고 Intent.ACTION_CHOOSER action이 발생하면 응답으로 줄 ActivityResult를 등록해 줍니다.

intending(hasAction(Intent.ACTION_CHOOSER)).respondWith(imgGalleryResult)

c. ActivityResult는 아래와 같은 코드로 만들 수 있습니다.

Instrumentation.ActivityResult(Activity.RESULT_OK, resultData)

d. View에서의 동작을 지정해줍니다.

onView(withId(R.id.gallery)).perform(click())

onView(withId(R.id.image_viewer)).check(matches(hasImageSet()))

R.id.gallery에 click 이벤트가 발생하면 image_viewer에 hasImageSet에 일치하는지 확인하는 코드입니다.

hasImageSet은 BoundedMatcher를 상속해서 matchesSafely에 확인하고 싶은 것을 체크합니다.

관련 구글 코드 : https://github.com/android/testing-samples/blob/master/ui/espresso/IntentsAdvancedSample/app/src/androidTest/java/com/example/android/testing/espresso/intents/AdvancedSample/ImageViewerActivityTest.java

2019년 10월 20일 일요일

libp2p - Circuit Relay 를 읽고

https://docs.libp2p.io/concepts/circuit-relay/

Circuit Relay : 별도의 peer를 통해 통신하고자 하는 두 peer를 연결하는 transport protocol

보통 NAT 내부에 있는 peer의 경우 다른 peer와 쉽게 통신 할 수 없다. 이러한 경우 통신이 가능하도록 하기 위해 libp2p는 p2p-circuit이라는 프로토콜을 사용한다.

중간에 relay해주는 peer에게 연결은 어떻게 할 수 있을까? 기본적으로는 내가 연결이 가능한 relay peer의 주소를 peer에게 알려주면 될 것이다. 주소는 multiaddr을 사용한다.
또는 libp2p의 content routing interface를 사용해서 연결이 가능한 relay peer를 찾고 연결을 한 다음에 peer routing을 사용해서 연결한 relay peer의 주소를 다른 peer에게 알린다.(Autorelay)

2019년 10월 18일 금요일

libp2p : NAT Traversal 을 읽고

https://docs.libp2p.io/concepts/nat/

사용하는 컴퓨터가 많아지면서 IPv4에서 쓸 수 있는 IP 만으로는 전체 기기를 감당할 수가 없습니다. 그래서 NAT라고 하는 것이 나왔습니다. 내부 네트워크에서는 내부에서만 알아볼 수 있는 IP 대역(private)을 사용하고 외부로 나갈 때 외부에서 인식 가능한 IP(public)로 바꾸어서 내보내는 거지요. 외부에서 내부로 들어올 때는 public ip를 private ip로 바꾸는 변환이 일어나서 내부 기기로 데이타가 전달되게 됩니다.

NAT를 쓰는 경우에는 내부에서 외부로 나가는 건 간단하다고 할 수 있지만 외부에서 내부로 들어오는 건 쉽지 않습니다. 내부의 기기는 private ip로 연결되어 있기 때문에 외부에서 인식할 수 없기 때문이지요.

이러한 점이 p2p에서는 문제가 됩니다. route등의 설정을 바꾸다던지 해서 어떻게 외부에서 내부로 들어오도록 할 수도 있겠지만 이렇게 불편하면 아무도 p2p를 사용하지 않겠지요. 그래서 p2p에서 사용하는 여러 방법이 있습니다.

- Automatic route configuration

많은 라우터들은 UPnp나 nat-pmp같은 port forwarding 기능을 제공합니다. 따라서, libp2p는 자동으로 이러한 설정을 하는 기능을 제공합니다.

- Hole-punching(STUN)

내부에서 외부로 연결을 맺으려 하면 라우터는 이에 대한 기록(private ip <-> public ip)을 남겨서 내부와 외부가 서로 연결될 수 있도록 해줍니다. 이 경우 외부에서 이 연결고리인 public ip/port를 안다면 외부에서 내부로 연결이 가능하게 됩니다.(좀 더 정확하게는 NAT의 종류에 따라서 이것이 가능하지 않을 수도 있습니다. 참고) 그런데 보통의 경우에는 내부의 기기는 라우터를 통할 때 자신의 ip:port가 무었이 될지 알 수 없습니다. 하지만! 외부의 커넥션이 맺어진 기기는 알 수 있겠죠. 이것을 이용한 것이 STUN입니다.

내부의 기기에서 이 STUN 서버에 접속을 하면 STUN 서버는 연결이 들어온 기기의 ip와 port 정보를 응답으로 보내줍니다. 그러면 나는 내가 외부로 나갈 때의 ip와 port를 알 수 있게 되고 다른 외부의 기기들에게 '내 ip와 port는 이거야'라고 알려주면 외부의 기기들은 나한테 접속할 수 있게 되는 거죠.

libp2p의 경우는 identity protocol이라고 하는 것을 통해서 이와 유사한 기능을 구현하고 있습니다.

- AutoNAT

앞에서 말했듯이 NAT에 따라서 STUN이 제대로 동작하지 않을 수 있습니다. 그러므로 외부의 기기가 접속을 해봐서 접속이 가능한지 아닌지를 알려주면 좋을 겁니다. 이 정보를 얻은 후 접속이 잘 되는 경우에만 외부에 '내 ip와 port는 이거야'라고 알려주는 것이 좋을테니까요.

- Circuit Relay(TURN)

대상이 되는 두 기기가 직접 통신하지 않고 중간에 relay를 해주는 기기를 두고 이 기기를 통해서 통신을 하도록 하는 방법입니다.

2019년 10월 17일 목요일

Convenient and idiomatic conversions in Rust 를 읽고

https://ricardomartins.cc/2016/08/03/convenient_and_idiomatic_conversions_in_rust

Rust에서 타입을 conversion하는데에 사용되는 trait가 있습니다.

From<T>, Into<U>, TryFrom<T>, TryInto<U>, AsRef<U>, AsMut<U>

=> T가 원본 타입이고 U가 타겟이 되는 타입이라고 보면 된다.

From<T> for U

T라는 타입을 U으로 변경하는 trait
reflexive trait임. From<T> for T 의 경우 자기자신을 그대로 리턴한다.
실패하지 않는다.
From<T> for U를 구현하면 Into<U> for T도 같이 자동으로 구현된다.

Into<U> for T

T라는 타입을 U로 변경하는 trait

TryFrom<T>

From<T>와 같은데 변환에 에러가 있을 수 있는 경우 사용한다. 따라서, 결과가 Result<Self, Self::Error>이다.

TryInto<U>

TryFrom<T>와 같다고 보면 된다. 결과가 Result<T, Self::Error> 이다.

AsRef<U>

immutable reference를 다른 타입의 immutable reference로 변환한다.

AsMut<U>

mutable reference를 다른 타입의 mutable reference로 변환한다.

AsRef<U>와 AsMut<U>에는 generic implementation이 있다.

=> AsRef<U>나 AsMut<U>를 구현하고 있는 type에의 reference에 대한 구현으로서
&&&&vec이나 &&&& mut vec 같은 multiple-level deep reference를 &vec와 같게 만들어 준다.(왜 이렇게 되는건지 어떤 사용 예가 있는 건지 이해 못함ㅠㅠ)

2019년 10월 16일 수요일

Testing two consecutive LiveData emissions in Coroutines 를 읽고

https://medium.com/androiddevelopers/testing-two-consecutive-livedata-emissions-in-coroutines-5680b693cbf8

LiveData에서의 데이타 발생을 테스트할 때 발생했던 문제를 이야기 합니다.

예를 들어 네트워크로부터 데이타를 가져오는데 시간이 걸리는 경우 우선은 화면에 더미? 또는 간단한 데이타를 보여주고 실제 데이타를 얻어오면 다시 화면을 업데이트 하게 할 수 있습니다.
이에 대한 테스트 코드를 만들어서 테스트를 한다면 하나의 LiveData를 만들어서 emit이 두번 일어나는지를 확인해 보게 될겁니다. 그런데 LiveData는 최종 데이타만을 가지고 있기 때문에 Dispatcher.UnConfined의 코루틴으로 테스트를 했더니 항상 첫번째 emit은 일어나지 않고 두번째 emit만 일어나서 테스트가 실패했다고 합니다.

이에 대한 해결책으로 TestCoroutineDispatcher의 pauseDispatcher와 resumeDispatcher를 사용해서 두번 emit이 일어날 수 있도록 변경했다고 합니다. TestCoroutineDispatcher에 코루틴의 동작을 제어(중단 및 다시 실행)할 수 있는 함수가 있어서 이를 통해 해결이 가능했네요.

이외에 liveData coroutines builder를 사용해서도 해결할 수 있다고 합니다. 이 builder를 쓰면 데이타를 전달할 때 emit() 함수를 사용하게 되는데 이 경우 데이타 전달이 확실히 일어나게 되는거 같네요.

Good Practices

- 코루틴을 사용할 때 Dispatcher는 꼭 Dependency Injection의 형태로 사용해라.

- Dispatchers.UnConfined 대신에 TestCoroutineDispatcher를 사용해라.


ViewModel with SavedStateHandle

ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders

Saving UI state with ViewModel SavedState and Dagger

ViewModels: State persistence — SavedState

A Deep Dive into Extensible State Saving

요즘 안드로이드 개발에서는 ViewModel을 많이 사용합니다. Configuration 변경에 대한 처리를 위해 사용하기도 하겠지만 저같은 경우는 MVVM 아키텍쳐 적용에 따라 ViewModel을 사용하고 있습니다.
안드로이드에서는 보통은 view에 관련된 data를 저장하고 싶을 때 onSaveInstanceState를 통해 저장합니다. 그리고 나서 나중에 Activity나 Fragment가 생성될 때 savedInstanceState를 확인해서 필요한 값이 있으면 꺼내서 사용하죠.
그런데 MVVM을 사용하게 되면서 data를 ViewModel쪽으로 이동하게 되고 이럴경우 data를 어떻게 유지할 것인가가 이슈가 됩니다.
이에따라 구글에서는 이에 관련된 lifecycle-viewmodel-savedstate 라는 모듈을 내놓았습니다. 이 모듈에서는 어떻게 ViewModel과 Activity/Fragment 사이에서 state(data)를 유지하는지 살펴보겠습니다.

크게는 Activity/Fragment에서의 처리와 ViewModel에서의 처리 이렇게 둘로 나누어서 볼 수 있습니다.

Activity/Fragment의 경우는 기본 컨셉은 간단합니다. 종료시 ViewModel에서 제공하는 state를 저장하고 다시 시작할 때 이렇게 저장된 state를 꺼내와서 ViewModel에 전달해 줍니다.

Activity/Fragment -> SavedStateRegistryController -> SavedStateRegistry

위의 관계로 SavedStateRegistry의 performSave/performRestore 함수에서 state를 저장하고 꺼내오는 역할을 해줍니다.(코드로 보면 onCreate에서 performRestore를 호출하고, onSavedInstanceState에서 performSave를 호출해 줍니다.)
또한 Activity/Fragment는 SavedStateRegistryOwner를 구현합니다. 이때의 구현 함수인 getSavedStateRegistry()를 통해 Activity/Fragment로부터 SavedStateRegistry를 얻어서 사용할 수 있습니다.(나중에 ViewModelFactory쪽에서 사용하게 됩니다.)

ViewModel을 생성하기 위해 SavedStateViewModelFactory를 사용합니다. 아래처럼 사용하는 건 다들 아실겁니다.

private val viewModel by viewModels { SavedStateViewModelFactory(SavedStateRegistryOwner) }

짜잔~ Factory에서 SavedStateRegistryOwner를 파라메터로 받네요. Factory는 SavedStateRegistryOwner로부터 SavedStateRegistry를 얻어오고 이것으로 부터 자신이 저장했던 state를 가져와서 ViewModel에 다시 파라메터로 넘겨줍니다.

간단하게 코드를 살펴보려면 AbstractSavedStateVMFactory의 create 함수를 보면 됩니다.
SavedStateRegistry의 consumeRestoredStateForKey를 호출해서 state를 얻고, 또한 ViewModel에서 저장한 state가 저장될 수 있도록하기 위해 SavedStateRegistry의 registerSavedStateProvider를 호출해서 지금 생성하는 ViewModel의 key와 SavedStateProvider를 등록해 줍니다.

앞에서 state를 저장할 때 performSave를 호출한다고 했습니다. 이때 저장된 모든 (key, provider)에서 provider를 꺼내와서 provider.savedState()를 통해 state를 저장합니다. ViewModel에서 저장하고자 하는 state는 SavedStateHandle에 저장되게 되는데요. SavedStateProvider의 savedSate()를 호출하면 SavedStateHandle에 저장된 state를 가져오게 됩니다.

ViewModel -> SavedStateHandle -> SavedStateProvider

2019년 10월 12일 토요일

libp2p: Transport 를 읽고

https://docs.libp2p.io/concepts/transport/

libp2p는 p2p 관련 프로토콜의 집합입니다.
p2p 기능을 원하는 다른 곳에서 쓰일 수 있도록 라이브러리로 되어 있습니다.

libp2p가 동작하기 위한 기본이 되는 네트워크 연결은 다양한 프로토콜을 통해 이루어 질 수 있게 되어 있습니다.(libp2p에서는 이를 transport라 부릅니다.)
이를 위해 libp2p는 TCP, WebSocket, WebRTC, QUIC등 다양한 프로토콜(참고)을 지원하고 있습니다.

개별 peer간에 연결이 이루어지려면 peer를 구별하는 이름같은 것이 있어야 할 텐데요. 이를 위해 multiaddress(참고)를 사용합니다.
간단하게 예를 들어보면 다음과 같은 형태가 됩니다.

/ip4/1.2.3.4/tcp/4321/p2p/QmcEPrat8ShnCph8WjkREzt5CPXF2RwhYxYBALDcLC1iV6

ip4 주소는 1.2.3.4 이고, tcp 포트는 4321이고, QmcEPrat...는 PeerId를 의미하는 데요. 누구에게 접속하는지를 표시하는 거라고 보면 됩니다.

PeerId(참고)를 좀 더 자세히 볼까요? peer를 유일하게 구별하는 이름인데요.
기술적으로는 public key의 해시 값입니다. 개별 peer들은 동작시 private/public key 쌍을 생성해서 유일한 값을 만들어 내고 이를 자기를 구별하는 값으로 사용합니다.

libp2p를 사용하는 애플리케이션이 하나의 transport만을 사용할 필요는 없습니다. 동시에 여러개의 transport를 지원할 수 있습니다.
이를 switch라고 합니다. switch를 통해 protocol negotiation, stream multiplexing, secure communications, connection upgrading을 할 수 있습니다.

2019년 10월 11일 금요일

ViewBinding

layout으로부터 view를 가져오는 가장 원초적인 방법은 findViewById를 사용하는 겁니다.
그런데 이건 워낙에 코드를 장황하게 만들어서 귀찮죠.

그래서 보통 간단하게 사용할 수 있는 Buffer Knife나 Kotlin Android Extensions를 사용합니다.

저도 이전 앱에서는 Buffer Knife를 사용했었고 현재 앱에서는 Kotlin Android Extensions를 사용하고 있습니다.

하지만 구글에서 ViewBinding을 새로 내놨으니 다음에는 ViewBinding을 써야겠네요.

https://proandroiddev.com/hello-viewbinding-goodbye-findviewbyid-edca92b397c

2019년 1월 23일 수요일

Swift By Sundell - Basics

https://www.swiftbysundell.com/basics

- Unit Testing

Xcode에 포함되어 있는 XCTest를 사용한다.

import XCTestCase

class는 XCTestCase를 상혹하고 함수 이름을 test로 시작하게 짓는다.

함수의 내용은 Given - When - Then 의 형태로 구현한다.
"Given these conditions when these actions are performed, then this is the expected outcome"

Given이 중복되는 경우 setUp() 함수를 사용한다.
setUp: 테스트 실행시 항상 먼저 실행되는 함수

- Grand Central Dispatch

GCD: "a different queue of execution"을 통해 asynchronous하게 code가 실행되도록 한다.
DispatchQueue.main
DispatchQueue.global

qos, attributes 등의 속성을 정해줄 수 있다.

- Layout Anchors

Auto layout을 편하게 지정하게 해준다. iOS9에서 새로 소개되었다.

UIView: "a series of anchors"를 포함하고 있다.

anchor를 지정하고 나면 항상 활성화를 해주어야 한다. 다음의 두가지중 한가지 방법을 사용한다.

1. constraint.isActive를 true로 설정
2. NSLayoutConstraint.activate([constraint])

고려해 봐야 할 점 세가지

1. 기본적으로 모든 view는 initial auto resizing mask를 layout constraints로 바꾼다. 이걸 disable하고 싶으면 setTranslatesAutoresizingMaskIntoConstraints를 false로 설정한다.
2. 여러 view로부터의 anchor를 사용하려면 모두가 같은 view hierarchy에 속해 있어야 한다.
3. constraint can be dynamically enabled and disabled. 이러한 경우 constraint를 add/remove 하기 보다는 isActive를 사용하는 것이 낫다.

- Child View Controllers

1. Parent에 add하기

parent.view.addSubView
parent.addChild
child.didMode

2. Parent에서 remove 하기

child.willMode
child.removeFromParent
child.view.removeFromSuperview

=> 항상 3개의 함수를 호출해 주어야 하므로 extension으로 add/remove 함수를 만들어서 사용하면 편할 것이다.

3. UI를 만들 때 Child view controller로 만들면 유용한 점이 있다.

- viewDidLoad나 viewWillAppear 같은 event를 받아서 처리할 수 있다.
- UI와 UI에 관련된 로직을 한군데에 포함함으로서 하나의 단위로 사용될 수 있다.
- View controller는 child로 추가되면 화면 전체를 차지하게 된다. 따라서, 별도로 full screen UI를 위한 layout code를 만들지 않아도 된다.
- 여러곳에서 사용될 수 있게 된다. Navigation Controller에 push 된다던가, child로 embedd 된다던가.

- Codable

Codable은 단순히 Encodable과 Decodable의 type alias이다.

typealias Codable = Decodable & Encodable

예를 들어 struct User가 Codable를 상속받으면 JSONEncode를 사용해서 쉽게 JSON으로 encoding할 수 있다.

struct User: Codable { ... }

let data = try JSONEncoder().encode(user)
let data2 = try JSONDecoder().decode(User.self, from: data)

그런데 json 포맷이 안맞으면 어떻게 해야 할까?
- 변수 이름을 포맷에 맞출까?

json 포맷에 맞는 struct를 만들어 decoding을 한 후 다시 내가 사용하는 struct로 변환한다.

snake case와 camel case가 안맞는 경우 keyDecodingStrategy를 지정해 주면 된다.

decoder.keyDecodingStrategy = .convertToSnakeCase


Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...