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

Building asynchronous views in SwiftUI 정리

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