2025년 7월 12일 토요일

Generic interfaces 요점

 https://go.dev/blog/generic-interfaces

 Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다.
pointer receiver를 사용해야 하는 복잡도가 증가하는 상황일 때 한번 더 생각해보자. 반드시 필요한 작업인지.

Do not over-engineer things.

 

2025년 7월 4일 금요일

Building Replication-Safe LSM Trees in Postgres 요점

 https://www.paradedb.com/blog/lsm_trees_in_postgres

 

 pg_search에 LSM 트리를 사용. 문제는 postgres의 replication 사용시 LSM 트리가 안전하지 않음
primary에서 VACUUM 동작시 에서 사용중인 튜플도 삭제할 가능성이 있음.
standby에서 주기적으로 사용중인 최저 트랜잭션 ID를 primary에게 알려주는 hot_standby_feedback을 사용하여 문제 해결

 

Using Rust async for Query Execution and Cancelling Long-Running Queries 요점

 https://datafusion.apache.org/blog/2025/06/30/cancellation/

 

tokio의 특성(Cooperative)상 poll이 제어권을 넘겨야 런타임이 다음 과정을 진행할 수 있다.
따라서 poll 안에서 pending이 될 때까지 무한 루프를 도는 경우에 문제가 발생할 수 있다.
이에 대한 해결을 위해 tokio 전체에 대해 budget을 두고 이 값이 0이 되면 무조건 pending을 발생시키는 기능이 tokio에 들어가 있다.

  

2022년 2월 9일 수요일

Building asynchronous views in SwiftUI 정리

self loading views
View model 사용하기
Combine을 사용한 AnyPublisher
refreshable with error : async/await
publisher에 의한 data를 감지하고 싶을 때 onReceive를 사용할 수 있다.

2022년 2월 4일 금요일

State management in SwiftUI 정리

State는 View의 내부 정보를 저장하고 싶을 때, State의 값이 변경되었을 때 View를 즉시 업데이트 하고 싶을 때 사용한다.
Binding은 State로부터 값을 받고, 이 값을 바꾸었을 때 기존 State도 같이 바뀌도록 하고 싶을 때 사용한다.

위 State와 Binding은 View 내에서 사용한다. 외부에서 같은 동작이 일어나도록 하고 싶으면 ObservableObject와 ObservedObject를 사용한다.

ObservableObject내에 notification을 받고 싶은 property를 Published로 선언한다. 또한, 주의해야 할점은 ObservedObject의 초기화가 View의 initializer에서 일어나야 한다는 것이다. 그렇게 하지 않으면 rerendering이 일어날 때마다 매번 새로 초기화될 수 있다. 이에 대한 해결로 iOS14에서 StateObject가 새로 나왔다. StateObject는 rerendering이 일어날 때 새로 초기화되지 않는다.

Parent와 바로 아래 children의 관계라면  Binding을 사용하는 것이 보통이다. 그러나, 만약 한창 아래의 view 계층에 있는 view에 environment를 전달하고 싶으면 EnvironmentObject를 사용한다. 상위의 view에서 .environmentObject(value)를 호출해주면 그 view 이하의 view에서 EnvironmentObject의 선언을 통해 값을 사용할 수 있다.
또는, EnvironmentKey를 사용한다. custom EnvironmentKey를 선언하고 EnvironmentValues에 관련 값의 get/set을 구현한다. 이 값은 아래와 같은 식으로 사용한다.

// EnvironmentValue에 선언된 값이 theme인 경우
@Environment(\.theme)

EnvironmentObject는 상위 view에서 값을 지정해주지 않으면 런타임에 크래시가 일어나고 EnvironmentKey는 컴파일 타임에 초기값을 지정해 주어야 하는 차이가 있다.

List에 Binding 사용하기
TabView와 NavigationView 에서 EnvironmetObject등 사용하기
View가 observe 하는 방식
Model - View 간의 연결에 Model을 변환해야 하는 경우
: View는 정렬이 된 item list가 필요하다.

1. item 리스트가 필요할 때마다 원 모델을 정렬한다.
 - 값이 필요할 때마다 정렬을 하게 된다.(불필요한 작업이 될 수 있다.)
2. View 생성시 정렬된 item 리스트를 가지고 있는다.
 - rerendering 마다 정렬이 새로 이루어진다.
3. ViewModel 안에 정렬된 item 리스트를 넣어둔다. 원 모델 변경시 마다 정렬된 item 리스트를 전달할 수 있다.
View 를 dismiss 하는 방법

1. @State와 @Binding 의 연결을 통한 isPresented 값 변경
2. @Environment(\.presentationMode) var presentationMode 의 사용
3. @Environment(\.dismiss) private var dismiss 의 사용
  - dismiss는 callAsFunction 이다.

2022년 1월 21일 금요일

[요약] Android Touch System — Part 1: Touch Functions and the View Hierarchy

https://proandroiddev.com/android-touch-system-part-1-touch-functions-and-the-view-hierarchy-1f6526e55d78

Android Touch System — Part 1: Touch Functions and the View Hierarchy


터치 스크린상에서의 모든 움직임은 MotionEvent로 알려진다.

MotionEvent는 다음의 값들을 가진다.

action : 수행한 action의 타입
x : 터치한 x좌표, view에 상대적인 위치
y : 터치한 y좌표,  view에 상대적인 위치
rawX : 터치한 절대 x좌표, 디바이스 화면에 상대적인 위치
rawY : 터치한 절대 y좌표, 디바이스 화면에 상대적인 위치
eventTime : 이벤트가 발생한 시간, SystemClock.uptimeMilllis()

안드로이드 화면은 좌상단이 (0, 0)이고 우하단이 (maxX, maxY)이다.

action은 다음의 값을 가진다.


ACTION_DOWN : 터치가 처음으로 일어났을 때
ACTION_UP : 터치를 화면에서 떼었을 때
ACTION_MOVE : ACTION_DOWN과 ACTION_UP 사이에 터치가 이동할 때
ACTION_CANCEL : 현재 터치가 취소될 때. parent view가 child view의 이벤트를 인터셉트할 때 발생한다.

이벤트의 흐름


motion event가 발생하면 root view(예: Activity)로부터 중간에 인터셉트당하지 않으면 가장 하위의 view까지 내려간다. 내려갈 때, view의 dispatchTouchEvent()가 호출된다. 여기 안에서 onInterceptTouchEvent()가 호출되는데 false를 리턴하면 아래로 계속 내려가지만 true를 리턴하면 더이상 아래로 내려가지 않는다.(이벤트는 여기서 소비된다.) 이벤트가 leaf view까지 내려가거나 onInterceptTouchEvent()가 true를 리턴해서 더이상 아래로 내려가지 않으면 다시 위로 올라가면서 onTouchEvent()가 호출된다. onTouchEvent()가 true를 리턴하면 이벤트는 여기서 멈춘다.

dispatchTouchEvent()

View.dispatchTouchEvent : view는 children을 갖지 않는다. 따라서, 구현은 간단하다. onTouchEvent()를 호출하고, touch listener를 view에 설정한다. 이 리스너중 하나라도 true를 리턴하면 true를 리턴한다.
custom view는 dispatchTouchEvent 보다는 onTouchEvent를 override 하는 것이 좋다.

ViewGroup.dispatchTouchEvent : onInterceptTouchEvent()를 호출한다. 여기서 false를 리턴하면 추가된 반대의 순서로 child view들을 순회한다. 이중 터치가 child view의 안에서 발생한 것이면 child.dispatchTouchEvent()를 호출한다. 여기서 false를 리턴하면 다음 child에서 child.dispatchTouchEvent()를 호출한다.
ViewGroup은 dispatchTouchEvent 보다는 onInterceptTouchEvent를 override 하는 것이 좋다.

ScrollView.dispatchTouchEvent : ViewGroup과 같다.

Activity.dispatchTouchEvent : 자식들의 dispatchTouchEvent()를 호출한다. 

onInterceptTouchEvent()


View.onInterceptTouchEvent : 없다.

ViewGroup.onInterceptTouchEvent : 기본 구현은 false를 리턴한다. 이 함수를 override 하는 주 목적은 특정한 타입의 터치 이벤트만 다루고 나머지는 자식들이 다루도록 하기 위함이다. 예를 들어 ScrollView는 스크롤은 직접 다루고, 클릭 같은 것은 자식이 다루도록 한다.

ScrollView.onIntreceptTouchEvent : 이벤트가 ACTION_MOVE이고, velocity가 충분하면 true를 리ㅣ턴한다. 그리고 자식들은 ACTION_CANCELLEDD를 받게 된다. 또한 requestDisallowInterceptTouchEvent를 호출한다.

Activity.onInterceptTouchEvent : 없다.

onTouchEvent()


View.onTouchEvent : view가 clickable 하면 기본은 true를 리턴한다. overriding할거면 super.onTouchEvent()를 호출해주는 것이 좋다. 또는 click gesture만 다룰거라면 performClick()를 override하는 것이 좋다.

ViewGroup.onTouchEvent : View의 onTouchEvent와 같다.

ScrollView.onTouchEvent : event의 정보를 통해 얼마나 스크롤했는지를 알아내서 스크롤을 수행한다. 스크롤과 관련된 애니메이션도 다룬다. 또한 requestDisallowInterceptTouchEvent()를 호출한다.

Activity.onTouchEventv : 기본 구현은 항상 false를 리턴한다.

requestDisallowInterceptTouchEvent()

ViewParent 인터페이스의 함수. parent나 ancestor view가 터치 이벤트를 인터셉트하지 못하도록 할 때 사용한다.

2020년 4월 11일 토요일

Cancellation and Exceptions in Coroutines 요약

1. Coroutines: First things first

- CoroutineScope :  launch나 async로 실행행된 코루틴을 참조. Job이 있는 경우 scope.cancel()을 통해 코루틴을 취소할 수 있다. 이때 관련된 모든 코루틴들(children)이 취소된다.

- Job : 코루틴에의 핸들. 코루틴을 생성하면 리턴되는 값. 코루틴의 lifecycle을 관리한다. 특정 코루틴만 취소하고 싶은 경우에는 코루틴이 리턴하는 Job의 cancel()을 통해 그 코루틴만 취소할 수 있다.

- CoroutineContext : Job, CoroutineDispatcher, CoroutineName, CoroutineExceptionHandler를 통해 코루틴의 행동을 정의하는 context로서 Job을 통해 코루틴의 lifecycle을 관리하고 CoroutineDispatcher를 통해 어떤 쓰레드에서 작업을 실행할지 지정하고 CoroutineName은 코루틴의 이름을 의미하고 CoroutineExceptionHandler를 통해 uncaught exception을 처리한다.

- Job lifecycle : 정상적인 경우 다음의 상태를 가진다. New -> Active -> Completing -> Completed. Active나 Completing에서 cancel이나 fail이 발생하면 Cancelling 상태로 변경되고, 이 Job과 관련된 모든 코루틴들의 작업이 끝나면 Cancelled로 변경된다.

- Parent context = Defaults + inherited CoutineContext + arguments
(Defaults : CoroutineDispatcher=Dispatchers.Default, CoroutineName="coroutine")

- New coroutine context = parent CoroutineContext + Job()

2. Cancellation in coroutines

- Job을 취소할 때 사용하는 함수인 cancel()에는 parameter로 CancellationException을 받을 수 있다. 취소에 대한 좀 더 자세한 정보를 알리고 싶을 때 사용한다.
Job이 취소되면  job은 자신의 parent에게 exception을 알린다. parent는 이 exception이 CancellationException이면 무시한다.

cancel() 함수를 호출한다고 해서 코루틴이 하던 작업이 바로 취소되는 것은 아니다.(cooperative) 따라서, 작업이 끝날때까지 무한 루프를 도는 코루틴이라면 중간에 취소 여부를 확인해주지 않으면 작업이 끝날때까지 동작하게 된다.

- 중간에 취소여부를 확인하기위해 다음의 4가지 방법을 사용할 수 있다. suspend function, job.isActive, ensureActive(), yield()
모든 suspend 함수는 cancellable하기 때문에 이 지점에서 취소 여부에 따라 중단될 수 있다.
if 문에서 isActive를 통해 상태를 확인할 수 있다. 이를 편하게 해주는 ensureActive() 함수를 제공하고 있다. yield는 코루틴의 상태가 cancelled나 completed이면 CancellationException을 throow한다.

- 코루틴으로부터 결과를 기다리는 두가지 방법 : Job.join(), Deferred.await()
Job.join() : 코루틴의 작업이 끝날때까지 기다린다.
Deferred.await() : async { ... }의 리턴에 대해 await를 호출하면 async의 작업이 끝날때까지 기다린다. cancell된 deferred에 대해 await를 호출하면 JobCancellatingException을 throw한다.

- 코루틴이 취소되었을 때 특정한 action을 실행하는 방법
- isActive를 통해 작업을 끝내고 clean up을 진행한다.
- 작업이 취소되면 CancellationException이 호출되므로 try catch finally를 사용한다.
=> clean up 할 때 suspend 함수를 사용하면 안된다.(suspend 함수는 cancelled 상태에서는 실행되지 않는다. 굳이 하고 싶으면 withContext(NonCancellable)을 사용한다.)

3. Exceptions in Coroutines

- Exception 전달 경로
child coroutine에서 exception이 발생하면 이 정보가 parent에게 전달된다. -> parent는 모든 children coroutine을 취소한다. -> exception가 다시 상위(parent의 parent)로 전달된다.

- 모든 children이 취소되지 않고 나 자신만 취소되게 하려면 SupervisorJob을 사용한다. 이 경우 취소된 child coroutine만 취소되고 다른 child들은 취소되지 않는다.

- Exception 처리하기

-- launch : exception이 발생하면 바로 throw된다. 따라서, 코드에서 try catch를 사용한다.
-- async

--- root coroutine일 경우에는 exception이 바로 throw 되지 않고 나중에 await를 실행할 때 throw 된다. 따라서, await() 주위에서 try catch를 사용한다. 또한 이때 SupervisorJob을 사용행야 한다. Job을 사용하면 exception이 parent로 전달되어 catch가 동작하지 않게 된다.
--- 다른 코루틴 안에서 async가 실행될 경우에는 exception이 바로 throw 된다.

- CoroutineExceptionHandler의 실행 조건(둘 다 성립해야 한다.)
-- exception을 throw하는 코루틴에서 실행된다.(work with launch, not async)
-- root coroutine이거나 CoroutineScope의 CoroutineContext안에 있는 경우

4. Coroutines & Patterns for work that should't be cancelled

- Coroutine vs WorkManager
process가 있는 동안만 동작해야 하는 경우에는 Coroutine을 사용하고 process와 상관없이 background에서 동작해야 하는 경우에는 WorkManager를 사용한다.

- 취소되지 않아야 하는 작업의 경우를 위해 Application 레벨에서의 CoroutineScope을 만들어서 사용한다.
- GlobalScope, ProcessLifecycleOwner, NonCancellable은 사용하지 않는다.

Generic interfaces 요점

 https://go.dev/blog/generic-interfaces  Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다. pointer receiver를...