2017년 12월 12일 화요일

Swift 스터디 - Associated Types Versus Generics

https://doc.rust-lang.org/book/second-edition/ch19-03-advanced-traits.html

https://www.youtube.com/watch?v=XWoNjiSPqI8

http://www.russbishop.net/swift-associated-types

https://www.bignerdranch.com/blog/why-associated-type-requirements-become-generic-constraints/

https://pdfs.semanticscholar.org/30c6/1a232dbf78a1720b06946eab0c9cb0a645b2.pdf

type parameter가 늘어날수록 사용이 불편해진다.
Generic은 public interface에 포함된다. 따라서, type이  open된다.
type parameter가 여러개일 경우 이중 하나에만 특정지을 수 없다.(나머지도 항상 표시가 필요하다.)
새로운 type parameter를 추가하면 이전 코드가 깨진다.
type parameter로 표현하는 경우 SomeProtocol<String>과 SomeProtocol<UITableViewCell>처럼 여러 타입에 대한 구현을 가질 수 있다. 따라서, 프로토콜에서 관련 함수를 호출하려면 타입 캐스팅등이 필요하게 된다.

associatedtype을 사용하는 protocol은 generic constraint와 함께 사용된다. 그렇지 않을 경우 아래와 같은 에러 메시지를 보게 된다.
  "Protocol `SomeProtocol` can only be used as a generic constraint because it has Self or associated type requirements"

Covariant Parameter Types

function return values can changed to subtypes, moving down the hierarchy, whereas function parameters can be changed to supertypes, moving up the hierarchy

https://www.java-tips.org/java-se-tips-100019/24-java-lang/482-covariant-parameter-types.html

https://softwareengineering.stackexchange.com/questions/267310/overriding-methods-by-passing-as-argument-the-subclass-object-where-the-supertyp

https://mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html

Type Erasure

https://krakendev.io/blog/generic-protocols-and-their-shortcomings

https://www.bignerdranch.com/blog/breaking-down-type-erasure-in-swift/

https://www.slacktime.org/type-erasure/

https://academy.realm.io/posts/tryswift-gwendolyn-weston-type-erasure/

2017년 10월 23일 월요일

Conductor 소스 분석


Conductor
  • MainActivity: 최상단 activity
    • Conductor.attachRouter를 통해 Activity와 Activity 안의 container(FrameLayout으로 만든다.)와 savedInstanceState를 연결하는 Router를 만든다.
    • attachRouter: LifecycleHandler라는 Fragment를 만들어서 Activity에 연결한다. 실제 Router는 LifecycleHandler에서 만든다.
  • Router: stack을 통해 Controller의 go/back을 관리한다.
    • Router의 setRoot를 통해 최상단에 적용할 Controller를 연결한다. Controller는 RouterTransaction을 통해 Router에 연결된다.
  • Controller: inflateView를 통해 View를 연결한다.
  • ControllerChangeHandler: View의 변환시 Animation이나 Transition을 한다.

LifecycleHandler

  • Fragment를 상속한다.
  • setRetainInstance(true)를 통해 activity가 recreate될 때 fragment도 같이 recreate되지 않도록 한다.
  • activeLifecycleHandlers를 통해 activity마다 하나의 LifecycleHandler를 연결한다.
  • application에 자신을 ActivityLifecycleCallbacks으로 등록한다.
  • pendingPermissionRequests: 퍼미션 요청하는 것에 대한 관리
  • routerMap을 통해 container(ViewGroup)마다 하나의 ActivityHostedRouter를 연결한다.

Router

  • ActivityHostedRouter and ControllerHostedRouter
  • ActivityHostedRouter는 LifecycleHandler와 연결하고, ControllerHostedRouter는 Controller와 연결한다.
  • InstanceState를 써서 data를 유지한다: KEY_BACKSTACK, KEY_POPS_LAST_VIEW
  • setRoot를 통해 최상단 RouterTransaction을 연결한다.
    • BackStack에 RouterTransaction을 넣는다.
    • ControllerChangeHandler.executeChange를 통해 RouterTransaction의 Controller에 연결되어 있는 View를 addView한다. 이전것은 removeView한다.
    • add와 remove를 위해 ControllerChangeHandler를 사용한다. RouterTransaction에 연결된 ControllerChangeHandler가 없으면 애니메이션없는 SimpleSwapChangeHandler를 사용한다.
  • Backstack
    • ArrayDeque을 통해 백스택 구현
    • Iterator<RouterTransaction> 및 reverseIterator 제공
  • Backstack에 controller가 들어가면 아래의 라이프 관련 함수가 호출된다.
    • onContextAvailable
    • inflate
  • Backstack에서 controller가 빠지면 아래의 라이프 관련 함수가 호출된다.
    • detach
    • destroy

ControllerChangeHandler

  • Controller로부터 View를 얻어온다: inflate
  • performChange를 통해 View의 push/pop을 실행한다.
  • AnimatorChangeHandler
    • performChange 함수에서 addView를 하고 animation을 시작한다.
    • animation은 subclass의 getAnimator를 통해 얻어온다.
    • FadeChangeHandler: AnimatorChangeHandler를 상속하여 getAnimator와 resetFromView를 구현
      • getAnimator: AnimatorSet를 사용하여 기존의 뷰는 알파를 0으로, 새로운 뷰는 알파를 0에서 1로 변경한다.
      • resetFromView: 애니메이션이 끝나면 호출되는 함수. 기존의 뷰의 알파를 1로 바꾼다.

Controller

  • instanceId: UUID.randomUUID().toString()
  • 화면의 구성을 위해 inflate가 호출된다.
    • onCreateView를 호출하여 View를 생성한다.
    • subclass에서 inflateView와 onViewBound를 구현한다.
  • detach시 onSaveViewState를, inflate시 onRestoreViewState를 호출해준다.

RouterTransaction

  • Controller와 (pushChangeHandler, popChangeHandler)의 연결고리를 Router에 제공한다.

Lifecycle 관리

  • LifecycleHandler는 ActivityLifecycleCallbacks를 구현한다.
    • ActivityLifecycleCallbacks는 Application에 선언되어 있는 인터페이스이다.
    • 콜백이 불리면 Router의 Lifecycle 관련 함수를 호출한다.
  • Router는 Lifecycle 관련 함수를 정의하고 있다.
    • 이 함수에서는 Controller의 Lifecycle 관련 함수를 호출한다.
    • child router가 있으면 이의 Lifecycle 관련 함수를 호출한다.
  • LifecycleListener
    • Controller에 등록해서 Lifecycle 이벤트를 받는다.

AutoDispose

  • ControllerScopeProvider
    • Controller에서 라이프 사이클이 바뀌면 BehaviorSubject를 통해 이벤트가 발생하도록 한다.
    • lifecycle에서 아이덴티티를 숨긴 lifecycleSubject.hide()를 리턴한다.
    • correspondingEvents에서 dispose를 하기 위해 대응되는 매핑(CORRESPONDING_EVENTS)을 리턴한다.
    • peekLifecycle에서 BehaviorSubject의 현재 값을 리턴한다.

2017년 2월 16일 목요일

Android architecture를 통한 MVP, MVVM의 이해

android-architecture 를 통한 MVP, MVVM의 이해

MVP

  • TasksActivity
    • TasksFragment와 TasksPresenter를 생성하고 연결하는 controller의 역할을 한다.
  • TasksFragment와 TasksPresenter간의 연결은 TasksContract의 View와 Presenter에 의해 이루어진다.
  • TasksFragment
    • newInstance 함수를 통해 생성함수를 제공한다.
    • setPresenter를 통해 presenter를 입력받는다.
    • presenter의 start함수를 호출한다.
  • TasksPresenter
    • 초기화시 view의 setPresenter를 호출하여 자기자신을 View에 등록한다.

MVP with databinding

  • TasksActivity
    • TasksFragment와 TasksPresenter와 TasksViewModel을 생성한다.
    • TasksPresenter 생성시 TasksFragment를 파라메타로 넣어준다: new TasksPresenter(.., tasksFragment);
    • TasksViewModel 생성시 TasksPresenter를 파라케타로 넣어준다: new TasksViewModel(getApplicationContext(), mTasksPresenter);
    • TasksFragment에 TasksViewModel을 설정한다: tasksFragment.setViewModel(tasksViewModel);
  • TasksPresenter
    • TasksFragment가 바라보는 TasksContract.Presenter를 구현한다.
    • 생성자에서 TasksContract.View를 받는다: 여기서는 TasksFragment이다.
    • 생성자에서 받은 View에 presenter를 설정해준다: mTasksView.setPresenter(this);
    • start 함수를 구현한다.
    • xml 로부터의 이벤트를 받는다: addNewTask()
  • TasksFragment
    • TasksPresenter가 바라보는 TasksContract.View를 구현한다.
    • setPresenter를 구현하여 TasksContract.Presenter를 받는다: 여기서는 TasksPresenter이다.
    • 시작시 presenter의 start 함수를 호출한다: mPresenter.start();
    • layout file의 이름이 tasks_frag.xml이라면 TasksFragBinding으로 databinding을 한다: TasksFragBinding.inflate(inflater, container, false);
    • xml에 지정한 data의 값을 설정해준다: tasksFragBinding.setTasks(mTasksViewModel); and tasksFragBinding.setActionHandler(mPresenter);
    • TasksFragBinding에서 생성된 View는 getRoot 함수를 통해 얻어온다: tasksFragBinding.getRoot();
    • ListView를 위한 adapter에 presenter를 넘겨준다: ListView에서 처리하고자 하는 이벤트 발생시 presenter의 관련 함수를 호출해준다.
  • TasksViewModel
    • BaseObservable을 상속한다.
    • 생성자에서 TasksContract.Presenter를 받는다: 여기서는 TasksPresenter이다.
    • @Bindable을 통해 xml 파일과 연결한다.
    • 가져올 필요가 있는 값을 presenter를 통해 얻어온다.

MVVM

TasksActivity를 만든다. TasksActivity는 TasksFragment와 TasksViewModel을 생성한다. TasksFragment와 TasksViewModel는 서로 상호 참조를 해야 한다. 상호참조를 위해 TasksFragment는 setPresenter 함수를 통해 TasksViewModel를 입력받고, TasksViewModel은 생성시 TasksFragment를 입력받는다. 이때 서로를 직접적으로 입력받는 것이 아닌 인터페이스를 입력받는다.
TasksFragment의 기본 구성은 아래와 같다.
public class TasksFragment extends Fragment implements TasksNavigator {
  private TasksViewModel tasksViewModel;

  // 생성 함수
  public static TasksFragment newInstance() {
    return new TasksFragment();
  }

  // view model을 받는다.
  public void setViewModel(TasksViewModel viewModel) {
    tasksViewModel = viewModel;
  }
}
그리고 시작시 ViewModel의 start 함수를 호출하여 데이타 로딩같은 초기 작업이 이루어지도록 한다.
// 여기서는 onResume에서 start를 호출하지만 필요에 따라 onCreate에서 호출할 수도 있을 것이다.
@Override
public void onResume() {
  super.onResume();
  tasksViewModel.start();
}
databinding을 통해 View와 ViewModel을 연결한다.
// xml 파일의 이름이 tasks_frag.xml이므로 TasksFragBinding으로 연결이 된다.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
  tasksFragBinding = TasksFragBinding.inflate(inflater, container, false);
  tasksFragBinding.setViewModel(tasksViewModel);
  View root = tasksFragBinding.getRoot();
  return root;
}
TasksViewModel의 기본 구성은 다음과 같다.
public class TasksViewModel extends BaseObservable {
  private final TasksNavigator navigator;

  public TasksViewModel(TasksNavigator navigator) {
    this.navigator = navigator;
  }

  public void start() {
    // 데이타를 로드한다.
    loadTasks(false);
  }
}
View와의 databinding을 위한 변수들을 설정한다. ObservableList, ObservableBoolean, ObservableField등의 다양한 databinding타입을 사용한다.(이 타입들의 변수들에 대한 설정은 get/set함수를 통해 이루어진다.) 또는 @Bindable을 사용해서 연결할 수도 있다.
// list view에서 보여지는 데이타들을 가지고 있는 변수
public final ObservableList<Task> items = new ObservableArrayList<>();
// 현재의 필터링 레벨을 보여주는 변수
public final ObservableField<String> currentFilteringLabel = new ObservableField<>();

// Bindable을 사용하는 경우에는 필요한 곳에서 아래의 형태로 호출을 해주어야 한다.
// notifyPropertyChanged(BR.empty); // It's a @Bindable so update manually
@Bindable
public boolean isEmpty() {
  return items.isEmpty();
}
여기까지가 기본적인 TasksFragment와 TasksViewModel의 구현이다. 이를 기반으로 실제 비지니스 로직이 들어가는 함수들을 구현하면 된다. 예를 들면, TasksNavigator에 필요한 인터페이스를 정의하여 TasksFragment가 이를 구현하게 하고 TasksViewModel은 View에 변경이 가해지거나(버튼 클릭등) Model이 바뀌는 경우 TasksNavigator의 필요한 함수를 호출하게 한다.
TasksFragment는 ListView를 포함하고 있다. 이 경우 ListView용의 ViewModel을 따로 만들어준다. 이의 구현을 살펴보자. ListView의 Adapter에 별도의 MVVM이 있다고 보면 될듯하다.
public static class TasksAdapter extends BaseAdapter {
  // Fragment의 TaskNavigator와 같은 역할을 하는 것
  private final TaskItemNavigator taskItemNavigator;
  // ListView에서의 처리에 TasksViewModel를 사용할 필요가 있는 경우도 있으므로 필요하면 이처럼 내부에 가지고 있는다.
  private final TasksViewModel tasksViewModel;

  public TasksAdapter(List<Task> tasks, TaskItemNavigator taskItemNavigator, TasksViewModel tasksViewModel) {
    this.taskItemNavigator = taskItemNavigator;
    this.tasksViewModel = tasksViewModel;
    // 데이타 로딩
    setList(tasks);
  }

  // TasksViewModel의 items에 변경이 일어나면 호출되기 위한 함수
  public void replaceData(List<Task> tasks) {
    setList(tasks);
  }

  @Override
  public View getView(int i, View view, ViewGroup viewGroup) {
    Task task = getItem(i);
    // xml 파일의 이름이 task_item.xml이다.
    TaskItemBinding binding;
    if (view == null) {
      // Inflate
      LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
      // Create the binding
      binding = TaskItemBinding.inflate(inflater, viewGroup, false);
    } else {
      // Recycling view
      binding = DataBindingUtil.getBinding(view);
    }

    // ViewModel을 생성한다.
    final TaskItemViewModel viewModel = new TaskItemViewModel(taskItemNavigator);
    // View와 ViewModel을 연결한다.
    binding.setViewModel(viewmodel);
    // To save on PropertyChangedCallbacks, wire the item's snackbar text observable to the
    // fragment's.
    viewModel.snackbarText.addOnPropertyChangedCallback(
        new Observable.OnPropertyChangedCallback() {
      @Override
      public void onPropertyChanged(Observable observable, int i) {
        tasksViewModel.snackbarText.set(viewmodel.getSnackbarText());
      }
    });
    viewModel.setTask(task);
    return binding.getRoot();
  }

  private void setList(List<Task> tasks) {
      this.tasks = tasks;
      notifyDataSetChanged();
  }
}
TasksViewModel이 task item의 리스트를 가지고 있다. 이의 변경이 일어나면 어떻게 ListView에 적용이 되는 걸까? tasks_frag.xml의 ListView를 보면 app:items="@{viewmodel.items}" property가 적용되어 있다. 여기에 추가하여 아래의 사용자 지정 바인딩을 사용한다.
public class TasksListBindings {

  // app:items의 값, 즉 TasksViewModel의 items에 변경이 일어나면 이 함수가 호출된다.
  // adapter의 내용을 items로 변경해준다.
  @SuppressWarnings("unchecked")
  @BindingAdapter("app:items")
  public static void setItems(ListView listView, List<Task> items) {
    TasksFragment.TasksAdapter adapter = (TasksFragment.TasksAdapter) listView.getAdapter();
    if (adapter != null) {
      adapter.replaceData(items);
    }
  }
}

Dagger2

  • Component와 Module을 정의한다.
  • Component
    • TasksRepositoryComponent에 dependency를 가지며 TasksPresenterModule을 사용하는 Component
@FragmentScoped
@Component(dependencies = TasksRepositoryComponent.class, modules = TasksPresenterModule.class)
public interface TasksComponent {
  void inject(TasksActivity activity);
}
  • Module
@Module
public class TasksPresenterModule {
  private final TasksContract.View mView;

  public TasksPresenterModule(TasksContract.View view) {
    mView = view;
  }

  @Provides
  TasksContract.View provideTasksContractView() {
    return mView;
  }
}
  • Component와 Module의 정의 후 아래와 같이 사용한다.
public class TasksActivity extends AppCompatActivity {
  @Inject TasksPresenter mTasksPresenter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    ...
    DaggerTasksComponent.builder()
        .tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
        .tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
        .inject(this);
  }
}

2017년 1월 13일 금요일

Swift Study - Boyer-Moore String Search

swift-algorithm-club를 통한 swift 공부

Boyer-Moore String Search : String 사용방법을 알 수 있다.
  • String 에 extension으로 index 함수를 구현한다.
  • func index(of pattern: String) -> Index? { ... }
  • String의 length는 String.characters.count로 구할 수 있다.
  • let patternLength = pattern.characters.count
  • skip table은 dictionary type으로 저장하고 있는다.
  • var skipTable = [Character: Int]()
  • String으로부터 index와 Character를 뽑아낼 수 있다.
  • for (i, c) in pattern.characters.enumerated() { ... }
  • String에는 다음과 같은 property가 있다: startIndex, endIndex
  • String에는 index를 리턴하는 함수들이 있다: index(before: String.Index)
  • index(before: String.Index)
  • index(after: String.Index)
  • index(i: String.Index, offsetBy: String.IndexDistance)
  • index(i: String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)
  • index 값을 아는 경우 함수 안에서 self로 String의 character에 접근할 수 있다.(여기서 i는 Int값이 아니라 String.Index라는데 주의)
  • let c = self[i]

2017년 1월 11일 수요일

Swift Study - Binary Search Tree

swift-algorithm-club를 통한 swift 공부

Binary Search Tree


  • convenience initializer를 통해 binary search tree의 초기화가 쉽게 이루어질 수 있도록 한다.(convenience initializer는 반드시 designated initializer를 호출해야 한다.)
  • BinarySearchTree가 CustomStringConvertible을 상속하여 debugging output을 제공한다.

extension BinarySearchTree: CustomStringConvertible {
  public var description: String {
    ...
  }
}
  • while case let을 통해 pattern matching이 되는 동안 while문을 실행할 수 있다.
  • enum 선언시 recursive enum을 위해 indirect를 사용한다.

2017년 1월 10일 화요일

Swift Study - Insertion Sort

swift-algorithm-club를 통한 swift 공부

Insertion Sort

  • for-in loop의 사용
  • 예를 들어 1에서 5까지 loop를 돌고 싶으면 아래와 같이 쓴다.
for x in 1...5 {
    ...
}
  • 5를 포함하지 않는 경우는 아래와 같이 쓴다.
for x in 1..<5 {
    ...
}

Swift Study - Stack

swift-algorithm-club를 통한 swift 공부

Stack

  • fileprivate : Stack이 정의된 소스 파일에서는 access가 가능하도록 한다. 소스 파일 외부에서는 private이라고 보면 된다.
  • array의 타입은 Array<T>이다. 이의 초기화는 간단하게  [T]()로 할 수 있다.
  • struct는 value type이기 때문에 기본적으로 method에서 property를 수정할 수 없다. 하지만, 수정하고 싶으면 mutating 키워드를 통해서 할 수 있다. push와 pop은 array를 수정하므로 mutating이 필요하다.
  • isEmpty와 count와 top은 간단하므로 read-only computed property로 만든다.
  • pop()과 top의 경우 element를 리턴하므로 nil 체크를 위해 T?를 사용한다.




2017년 1월 6일 금요일

RxSwift 소스 분석 - Subject에 대한 이해

RxSwift 소스 분석해보기

1. 기본구조이해
2. 기본구조이해 - 2
3. Scheduler에 대한 이해
4. Subject에 대한 이해

https://github.com/ReactiveX/RxSwift


PublishSubject

PublishSubject는 Observable, SubjectType, Cancelable, ObserverType, SynchronizedUnsubscribeType을 상속한다. 후아, 동작이 복잡하구나. Observable을 통해 observer들을 등록해 주는 기능이 들어가고, ObserverType을 통해 노티를 날려줄 수 있게 되고, Cancelable을 통해 등록된 모든 observer들을 취소할 수 있게 되고, SynchronizedUnsubscribeType을 통해 특정 observer만 취소할 수 있게 된다. PublishSubject가 더이상 동작하지 않게 하는 방법은 completed 또는 error 이벤트를 받거나 dispose에 의해 명시적으로 취소하거나 이다.
내부에서 사용하는 변수들은 다음과 같다.
  • _observers: Bag<AnyObserver<Element>> -> 등록된 observer들을 가지고 있는다. Bag은 간단하게 설명하면 내부에 dictionary를 통해 element들을 가지고 있는다.
  • _stoppedEvent: completed 또는 error가 불리면 이 이벤트를 저장하고 있는다.
  • _isDisposed: 더이상 observer에게 노티를 보내지 않는 경우 true로 설정한다.
  • _stopped: completed 또는 error시 true로 설정하여 이후 next가 발생해도 observer들에게 노티가 가지 않도록 한다. 앞에서 말한 PublishSubject가 더이상 동작하지 않게 하는 두가지 방법에 대한 상태를 위해 _isDisposed와 _stopped가 사용된다.
  • _lock: NSRecursiveLock. 내부 변수를 건드릴 때 사용한다.
observer 등록은 subscribe를 통해 다음과 같이 한다.
public override func subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == Element {
  _lock.lock(); defer { _lock.unlock() }
  return _synchronized_subscribe(observer)
}

func _synchronized_subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
    // 이미 completed나 error가 불렸는지 확인한다.
    if let stoppedEvent = _stoppedEvent {
      observer.on(stoppedEvent) // 이 observer에게 끝난 subject임을 알린다.
      return Disposables.create()
    }

    // dispose에 의해 취소된 PublishSubject인지 확인한다.
    if _isDisposed {
      observer.on(.error(RxError.disposed(object: self))) // 이 observer에게 끝난 subject임을 알린다.
      return Disposables.create()
    }

    // 노티를 받기위한 observer 리스트에 추가한다.
    let key = _observers.insert(observer.asObserver())
    return SubscriptionDisposable(owner: self, key: key)
}
subscribe는 _synchronized_subscribe에 대해 lock으로 보호하여 호출한다. _synchronized_subscribe는 이미 completed나 error가 불렸으면 이 이전의 이벤트를(_stoppedEvent에 저장되어 있다.) observer에 노티한다. PublishObject가 이미 이전에 dispose에 의해 취소되었으면 error 이벤트를 observer에 노티한다. 이외의 경우라면 observer가 노티를 받을 수 있도록 _observers에 추가한다. 이와 같은 정상적인 경우라면 나중에 자기 자신을 취소할 수 있게 해주는(_observers에서 제거) SubscriptionDisposable을 리턴하고 다른 경우라면 취소 함수(dispose)를 호출해도 아무런 동작이 일어나지 않도록 Disposables.create를 리턴해준다.
모든 observer가 노티를 받는 것의 취소는 dispose를 쓴다.(개별 observer의 취소는 subscribe의 리턴 값에 dispose를 한다.)
public func dispose() {
  _lock.lock(); defer { _lock.unlock() }
  _synchronized_dispose()
}

final func _synchronized_dispose() {
  _isDisposed = true
  _observers.removeAll()
  _stoppedEvent = nil
}
dispose는 _synchronized_dispose에 대해 lock으로 보호하여 호출한다. _synchronized_dispose는 observer에 대한 모든 노티가 취소되었음을 설정한다.
개별 observer의 취소를 위해서는 subscribe에서 리턴된 값에 dispose를 사용한다고 앞에서 말했다. SubscriptionDisposable을 살펴보자.
struct SubscriptionDisposable<T: SynchronizedUnsubscribeType> : Disposable {
  private let _key: T.DisposeKey
  private weak var _owner: T?

  init(owner: T, key: T.DisposeKey) {
    _owner = owner
    _key = key
  }

  func dispose() {
    _owner?.synchronizedUnsubscribe(_key)
  }
}
owner로 PublishSubject를 받고 key로 Bag의 KeyType(_observers에 저장된 observer에 대한 unique identifier이다.)을 받는다. dispose를 호출하면 PublishSubject의 synchronizedUnsubscribe를 호출해준다. PublishSubject의 synchronizedUnsubscribe 구현을 보자.
func synchronizedUnsubscribe(_ disposeKey: DisposeKey) {
  _lock.lock(); defer { _lock.unlock() }
  _synchronized_unsubscribe(disposeKey)
}

func _synchronized_unsubscribe(_ disposeKey: DisposeKey) {
  _ = _observers.removeKey(disposeKey)
}
synchronizedUnsubscribe는 _synchronized_unsubscribe를 lock으로 보호한다. _synchronized_unsubscribe는 _observers에서 obsesver를 빼준다.
이제 이벤트를 발생시켜 보자. 이것은 ObserverType을 상속함으로서 기능(onNext, onCompleted, onError)이 들어가게 되고 내부적으로 on이 호출되게 된다.
public func on(_ event: Event<Element>) {
  _synchronized_on(event).on(event)
}

func _synchronized_on(_ event: Event<E>) -> Bag<AnyObserver<Element>> {
  _lock.lock(); defer { _lock.unlock() }

  switch event {
  case .next(_):
    // PublishSubject가 종료되었는지를 확인한다.
    if _isDisposed || _stopped {
      return Bag()
    }

    return _observers
  case .completed, .error:
    if _stoppedEvent == nil {
      _stoppedEvent = event
      _stopped = true
      let observers = _observers // Bag이 struct이므로 카피가 일어난다.
      _observers.removeAll()
      return observers
    }

    return Bag()
  }
}
_synchronized_on을 lock으로 보호한 후 이벤트를 처리한다.(근데 왜 이전의 코드와 lock의 사용법이 다를까? 리턴값에 바로 on을 적용하기 위해서이다.) 그런데, 리턴값의 타입이 Bag인데 어떻게 on을 호출할 수 있는 것일까? Bag+Rx.swift를 보면 Bag의 element의 타입이 ObserverType인 경우에 대해 extension으로 내부의 element들(observer)에 on을 호출해주는 on이 정의되어 있다.

BehaviorSubject

PublishSubject와 거의 유사하기 때문에 다른 점 기준으로 보자. BehaviorSubject는 마지막으로 발생한 이벤트를 새로 subscribe하는 observer에 전달해준다. 따라서 마지막 이벤트를 _value에 가지고 있는다. 생성하고 바로 subscribe하는 경우에도 이벤트가 발생하도록 하기 위해 init에 초기 값을 설정해준다.
public init(value: Element) {
  _value = value
}
그리고 value의 현재 값을 제공하는 get 함수를 제공한다. _value에 대한 접근을 lock으로 보호하고 에러의 경우 throw를 던진다.
public func value() throws -> Element {
  _lock.lock(); defer { _lock.unlock() } // {
    // subject가 취소되었는지 확인한다.
    if _isDisposed {
      throw RxError.disposed(object: self)
    }

    // .error 이벤트가 발생했는지 확인한다.
    if let error = _stoppedEvent?.error {
      // intentionally throw exception
      throw error
    }
    else {
      return _value
    }
  //}
}
_value의 업데이트는 on(.next) 이벤트 발생시 해주고 subscribe시 _value를 새로 등록되는 observer에게 발생시킨다.
func _synchronized_on(_ event: Event<E>) -> Bag<AnyObserver<Element>> {
  _lock.lock(); defer { _lock.unlock() }
  if _stoppedEvent != nil || _isDisposed {
    return Bag()
  }

  switch event {
  case .next(let value):
    _value = value // _value를 최신 이벤트로 유지한다.
  case .error, .completed:
    _stoppedEvent = event
  }

  return _observers
}

func _synchronized_subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
  if _isDisposed {
    observer.on(.error(RxError.disposed(object: self)))
    return Disposables.create()
  }

  if let stoppedEvent = _stoppedEvent {
    observer.on(stoppedEvent)
    return Disposables.create()
  }

  let key = _observers.insert(observer.asObserver())
  observer.on(.next(_value)) // 마지막에 발생했던 이벤트를 이 observer에 발생시킨다.

  return SubscriptionDisposable(owner: self, key: key)
}

ReplaySubject

ReplaySubject에 Replay를 생성하기 위한 create/createUnbounded 함수가 있다. bufferSize에 따라 ReplayOne 또는 ReplayMany를 생성하거나 bound없이 생성하려면 ReplayAll을 생성한다.
public static func create(bufferSize: Int) -> ReplaySubject<Element> {
  if bufferSize == 1 {
    return ReplayOne()
  }
  else {
    return ReplayMany(bufferSize: bufferSize)
  }
}

public static func createUnbounded() -> ReplaySubject<Element> {
  return ReplayAll()
}
Replay는 세 종류가 있는데 이들의 상속관계는 다음과 같다.
  • ReplayOne -> ReplayBufferBase -> ReplaySubject
  • ReplayMany -> ReplayManyBase -> ReplayBufferBase -> ReplaySubject
  • ReplayAll -> ReplayManyBase -> ReplayBufferBase -> ReplaySubject
ReplayBufferBase에서 Replay의 동작을 위한 trim, addValueToBuffer, replayBuffer라는 세 인터페이스를 정의한다. ReplayBufferBase를 상속하는 클래스는 이 세 함수를 구현해야 한다. 그리고 subscribe시 replayBuffer를 호출해주고, 이벤트 발생시는 on(.next)시는 addValueToBuffer와 trim을 on(.error)나 on(.completed)에는 trim만들 호출해준다.
func _synchronized_subscribe<O : ObserverType>(_ observer: O) -> Disposable where O.E == E {
  if _isDisposed {
    observer.on(.error(RxError.disposed(object: self)))
    return Disposables.create()
  }

  let AnyObserver = observer.asObserver()

  replayBuffer(AnyObserver)
  if let stoppedEvent = _stoppedEvent {
    observer.on(stoppedEvent)
    return Disposables.create()
  }
  else {
    let key = _observers.insert(AnyObserver)
    return SubscriptionDisposable(owner: self, key: key)
  }
}

func _synchronized_on(_ event: Event<E>) -> Bag<AnyObserver<Element>> {
  _lock.lock(); defer { _lock.unlock() }
  if _isDisposed {
    return Bag()
  }

  if _stoppedEvent != nil {
    return Bag()
  }

  switch event {
  case .next(let value):
    addValueToBuffer(value)
    trim()
    return _observers
  case .error, .completed:
    _stoppedEvent = event
    trim()
    let observers = _observers
    _observers.removeAll()
    return observers
  }
}
ReplayOne은 addValueToBuffer가 호출되면 이때의 값을 _value에 저장해 놓는다. 그리고, 나중에 subscribe에 의해 replayBuffer가 호출되면 이때의 observer에 저장해둔 _value를 보낸다. trim에서는 아무짓도 하지 않는다.
override func trim() {

}

override func addValueToBuffer(_ value: Element) {
  _value = value
}

override func replayBuffer(_ observer: AnyObserver<Element>) {
  if let value = _value {
    observer.on(.next(value))
  }
}
ReplayManyBase는 queue를 가지고 addValueToBuffer의 경우 value를 queue에 넣고 replayBuffer의 경우 queue에 있는 값들을 observer에게 보낸다. 그리고 queue를 초기화 할 때 queue가 지정한 크기보다 1개 더 클 수 있으므로 사용자가 지정한 크기에서 (+1)을 하여 초기 queue의 크기를 정한다.(이벤트 발생시 addValueToBuffer후 trim을 하기 때문)
init(queueSize: Int) {
  _queue = Queue(capacity: queueSize + 1)
}

override func addValueToBuffer(_ value: Element) {
  _queue.enqueue(value)
}

override func replayBuffer(_ observer: AnyObserver<E>) {
  for item in _queue {
    observer.on(.next(item))
  }
}
ReplayMany는 ReplayManyBase를 상속하므로 trim만을 구현한다. queue의 크기가 지정한 크기보다 클경우 오래된 값을 삭제한다.
override func trim() {
  while _queue.count > _bufferSize {
    _ = _queue.dequeue()
  }
}
ReplayAll도 ReplayManyBase를 상속하므로 trim만을 구현한다. trim에서 아무짓도 하지 않음으로서 queue가 무제한 늘어날 수 있도록 한다.
override func trim() {

}

Variable

초기화시 BehaviorSubject를 가지고 _value의 값을 변경할 때 BehaviorSubject를 통해 이벤트를 발생시킨다.
public init(_ value: Element) {
  _value = value
  _subject = BehaviorSubject(value: value)
}

public var value: E {
 get {
    _lock.lock(); defer { _lock.unlock() }
    return _value
  }
  set(newValue) {
    _lock.lock()
    _value = newValue
    _lock.unlock()

    _subject.on(.next(newValue)) // 이벤트를 발생시킨다.
  }
}
Variable에 observer를 등록할 수 있도록 하기 위해 실제로는 BehaviorSubject를 리턴하는 asObservable 함수를 제공한다.
public func asObservable() -> Observable<E> {
  return _subject
}

Building asynchronous views in SwiftUI 정리

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