Skip to content

Instantly share code, notes, and snippets.

@zeero
Last active December 21, 2023 08:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zeero/32095f805b89825b32591b195c4bf7e9 to your computer and use it in GitHub Desktop.
Save zeero/32095f805b89825b32591b195c4bf7e9 to your computer and use it in GitHub Desktop.
Auto-generate mermaid class diagram powered by Sourcery
class_diagram.md
sources:
- FIXME_path_to_sources
templates:
- mermaid_class_diagram.stencil
output:
class_diagram.md

Usage

  1. Install Sourcery
  2. Download source code from Gist
  3. Modify .sourcery.yml to specify the source code for analyzing (Sourcery can specify the analysis target by directory or target multiple directories)
  4. Execute sourcery command in the downloaded directory
  5. Display the class_diagram.md file in the same directory with GitHub Issue or other mermaid-compatible tools

Specifications

  • Type
    • Types are only classes and protocols
      • struct, enum, and typealias are out of scope
        • If it comes up in a relationship, it will be listed, and the disadvantage of noise seems to be greater
    • Nested generics are not supported by mermaid
      • I want to omit it, but it seems hard to write conditions in various places, so I decided to leave it as it is.
    • InnerType didn't seem to be supported by mermaid, so I replace from . to __.
  • Variables
    • Variables of closure type are omitted because the variable names do not display correctly and Mermaid does not seem to support them.
      • I would like to know if there is a specification that can be written 🙏
  • Methods
    • Method names are in selector format
  • Relationships
    • Implement the following
      • Inheritance
      • Realization
        • If there is an inheritor of the protocol, implementer also will be related to inheritor.
          • Because of Sourcery specifications... It may be difficult to handle with Stencil only...
      • Composition
      • Aggregation
        • Sourcery doesn't handle collection types well, and it parses them on a character basis, so it may be a bit noisy and buggy.
    • Association is out of scope
      • I was able to draw a association relationship by method argument, but the number of lines became too many, making it difficult to understand other relationships, so I scoped it out (commented out).
    • Dependency may be able to draw a relationship if it is an argument type such as Initializer, but it is not supported because there seem to be too many false positives.

使い方

  1. Sourceryをインストール
    • プロジェクトで使ってるとかがないなら、brewで入れちゃうのが楽です
  2. Gistからコード一式をダウンロード
  3. .sourcery.yml を修正して、解析対象のソースコードを指定
    • Sourceryなので、解析対象をディレクトリ単位で指定したり、複数を対象にしたりできます
  4. ダウンロードしたディレクトリで sourcery コマンド実行
  5. 同ディレクトリにできた class_diagram.md を、GitHub Issueなどmermaid対応してるツールで表示する

仕様

  • 型(クラスとプロトコル)
    • 型はクラスとプロトコルを対象にする
      • struct、enum、typealiasはスコープアウト
        • リレーションシップで出てきたら記載されるし、ノイズになるデメリットの方が大きそう
    • ネストしたジェネリクスはMermaidが対応していない
      • オミットしてしまおうかとも思ったけど判定を色んなところに書くのが辛そうなのでいったんそのまま
    • InnerTypeはMermaidが対応してなさそうだったので、 . -> __ に変換した
  • 変数
    • closure型の変数は変数名が正しく表示できず、Mermaidが対応してなさそうなのでオミット
      • 記述できる仕様があれば知りたい🙏
  • メソッド
    • メソッド名はセレクタ形式
  • リレーションシップ
    • 以下を実装
      • Inheritance
      • Realization
        • 実装してるプロトコルの継承元がある場合、そちらにもリレーション張ってしまう
          • Sourceryの仕様...Stencilだけだとハンドリングが難しそう...
      • Composition
      • Aggregation
        • Sourceryがコレクション型のハンドリングがうまくできてなく、文字ベースで解析してるのでSwift標準ライブラリの型なども対象に入ってきて少しノイジーかもしれない
    • Associationは、メソッドの引数の型で関係引けたけど、線が多くなり過ぎて他のリレーションがわかりにくくなったのでスコープアウト(コメントアウト)
    • Dependencyは、Initializerとかの引数の型なら関係引けるかもしれないけど、誤陽性多そうなので未対応
classDiagram

  class PKHUDAnimating {
    <<interface>>
    +startAnimation() 
    +stopAnimation() 
  }


  class ContainerView {
    -Bool keyboardIsVisible
    -CGFloat keyboardHeight
    +FrameView frameView
    -Bool willHide
    -UIView backgroundView
    -commonInit() 
    +layoutSubviews() 
    +showFrameView() 
    +hideFrameView(animated:completion:) 
    +showBackground(animated:) 
    +hideBackground(animated:) 
    +registerForKeyboardNotifications() 
    +deregisterFromKeyboardNotifications() 
    -keyboardWillShow(notification:) 
    -keyboardWillBeHidden(notification:) 
    -animateHUDWith(duration:curve:toLocation:) 
    -calculateHudCenter() 
  }
  ContainerView *-- FrameView : composition


  class FrameView {
    -UIView _content
    +UIView content
    -commonInit() 
  }


  class HUD {
  }


  class PKHUD {
    +UIView? viewToPresentOn
    -ContainerView container
    -Timer? hideTimer
    -[String: TimerAction] timerActions
    +TimeInterval graceTime
    +TimeInterval gracePeriod
    -Timer? graceTimer
    +Bool dimsBackground
    +Bool userInteractionOnUnderlyingViewsEnabled
    +Bool isVisible
    +UIView contentView
    +UIVisualEffect? effect
    +CGFloat leadingMargin
    +CGFloat trailingMargin
    +deinit() 
    +show(onView:) 
    +showContent() 
    +hide(animated:completion:) 
    +hide(_:completion:) 
    +hide(afterDelay:completion:) 
    +willEnterForeground(_:) 
    +startAnimatingContentView() 
    +stopAnimatingContentView() 
    +registerForKeyboardNotifications() 
    +deregisterFromKeyboardNotifications() 
    +performDelayedHide(_:) 
    +handleGraceTimer(_:) 
  }
  PKHUD *-- ContainerView : composition
  PKHUD *-- PKHUD : composition
  PKHUD o--  TimerAction : aggregation


  class PKHUDAnimation {
  }


  class PKHUDAssets {
  }


  class PKHUDErrorView {
    +PKHUDErrorView dashOneLayer
    +PKHUDErrorView dashTwoLayer
    +rotationAnimation(_:) 
    +startAnimation() 
    +stopAnimation() 
  }
  PKHUDAnimating <|.. PKHUDErrorView : realization
  PKHUDSquareBaseView <|-- PKHUDErrorView : inheritance
  PKHUDErrorView *-- PKHUDErrorView : composition
  PKHUDErrorView *-- PKHUDErrorView : composition


  class PKHUDProgressView {
    +startAnimation() 
    +stopAnimation() 
  }
  PKHUDAnimating <|.. PKHUDProgressView : realization
  PKHUDSquareBaseView <|-- PKHUDProgressView : inheritance


  class PKHUDRotatingImageView {
    +startAnimation() 
    +stopAnimation() 
  }
  PKHUDAnimating <|.. PKHUDRotatingImageView : realization
  PKHUDSquareBaseView <|-- PKHUDRotatingImageView : inheritance


  class PKHUDSquareBaseView {
    +UIImageView imageView
    +UILabel titleLabel
    +UILabel subtitleLabel
    +layoutSubviews() 
  }


  class PKHUDSuccessView {
    +CAShapeLayer checkmarkShapeLayer
    +startAnimation() 
    +stopAnimation() 
  }
  PKHUDAnimating <|.. PKHUDSuccessView : realization
  PKHUDSquareBaseView <|-- PKHUDSuccessView : inheritance


  class PKHUDSystemActivityIndicatorView {
    +UIActivityIndicatorView activityIndicatorView
    +commonInit() 
    +layoutSubviews() 
    +startAnimation() 
  }
  PKHUDAnimating <|.. PKHUDSystemActivityIndicatorView : realization
  PKHUDSquareBaseView <|-- PKHUDSystemActivityIndicatorView : inheritance


  class PKHUDTextView {
    +UILabel titleLabel
    +commonInit(_:) 
    +layoutSubviews() 
  }
  PKHUDWideBaseView <|-- PKHUDTextView : inheritance


  class PKHUDWideBaseView {
  }


  class WindowRootViewController {
    +UIInterfaceOrientationMask supportedInterfaceOrientations
    +UIStatusBarStyle preferredStatusBarStyle
    +Bool prefersStatusBarHidden
    +UIStatusBarAnimation preferredStatusBarUpdateAnimation
    +Bool shouldAutorotate
  }

classDiagram

  class Action {
    <<interface>>
  }


  class AnyStoreSubscriber {
    <<interface>>
    +_newState(state:) 
  }


  class Coding {
    <<interface>>
    +[String: AnyObject] dictionaryRepresentation
  }
  Coding o--  AnyObject : aggregation


  class DispatchingStoreType {
    <<interface>>
    +dispatch(_:) 
  }


  class StateType {
    <<interface>>
  }


  class StoreSubscriber {
    <<interface>>
    +newState(state:) 
    +_newState(state:) 
  }
  AnyStoreSubscriber <|-- StoreSubscriber : inheritance


  class StoreType {
    <<interface>>
    +State state
    +DispatchFunction dispatchFunction
    +subscribe(_:) 
    +subscribe(_:transform:) 
    +subscribe(_:transform:) 
    +unsubscribe(_:) 
    +dispatch(_:) 
    +dispatch(_:) 
    +dispatch(_:callback:) 
  }
  DispatchingStoreType <|-- StoreType : inheritance


  class Assertions {
  }


  class Store {
    +State state
    +DispatchFunction dispatchFunction
    -Reducer<State> reducer
    +Set<SubscriptionType> subscriptions
    -Synchronized<Bool> isDispatching
    -Bool subscriptionsAutomaticallySkipRepeats
    +[Middleware<State>] middleware
    -createDispatchFunction() 
    -_subscribe(_:originalSubscription:transformedSubscription:) 
    +subscribe(_:) 
    +subscribe(_:transform:) 
    +subscriptionBox(originalSubscription:transformedSubscription:subscriber:) SubscriptionBox
    +unsubscribe(_:) 
    +_defaultDispatch(action:) 
    +dispatch(_:) 
    +dispatch(_:) 
    +dispatch(_:) 
    +dispatch(_:callback:) 
    +subscribe(_:transform:) 
  }
  DispatchingStoreType <|.. Store : realization
  StoreType <|.. Store : realization
  Store *-- Synchronized : composition
  Store o-- SubscriptionType : aggregation
  Store o-- Middleware~State~ : aggregation


  class Subscription {
    -_select(_:) Subscription
    +select(_:) Subscription
    +select(_:) Subscription
    +skipRepeats(_:) Subscription
    +newValues(oldState:newState:) 
    +skipRepeats() Subscription
    +skip(when:) Subscription
    +only(when:) Subscription
  }


  class SubscriptionBox {
    -Subscription<State> originalSubscription
    +AnyStoreSubscriber? subscriber
    -ObjectIdentifier objectIdentifier
    +Int hashValue
    +hash(into:) 
    +newValues(oldState:newState:) 
  }
  SubscriptionBox *-- Subscription : composition
  SubscriptionBox *-- AnyStoreSubscriber : composition

classDiagram

  class ControlEventType {
    <<interface>>
    +asControlEvent() ControlEvent
  }
  ObservableType <|-- ControlEventType : inheritance


  class ControlPropertyType {
    <<interface>>
    +ControlProperty<String> orEmpty
    +asControlProperty() ControlProperty
  }
  ObservableType <|-- ControlPropertyType : inheritance
  ControlPropertyType *-- ControlProperty : composition


  class DelegateProxyType {
    <<interface>>
    +forwardToDelegate() 
    +setForwardToDelegate(_:retainDelegate:) 
    +_forwardToDelegate() 
    +_setForwardToDelegate(_:retainDelegate:) 
  }
  DelegateProxyType *-- DelegateProxyFactory : composition


  class HasDataSource {
    <<interface>>
    +DataSource? dataSource
  }


  class HasDelegate {
    <<interface>>
    +Delegate? delegate
  }


  class HasPrefetchDataSource {
    <<interface>>
    +PrefetchDataSource? prefetchDataSource
  }


  class KVOObservableProtocol {
    <<interface>>
    -AnyObject target
    -String keyPath
    -Bool retainTarget
    -KeyValueObservingOptions options
  }
  KVOObservableProtocol *-- Bool : composition
  KVOObservableProtocol *-- KeyValueObservingOptions : composition


  class KVORepresentable {
    <<interface>>
  }


  class MessageInterceptorSubject {
    <<interface>>
    -Bool isActive
    -IMP targetImplementation
  }
  MessageInterceptorSubject *-- Bool : composition


  class RxCollectionViewDataSourceType {
    <<interface>>
    +collectionView(_:observedEvent:) 
  }


  class RxPickerViewDataSourceType {
    <<interface>>
    +pickerView(_:observedEvent:) 
  }


  class RxTableViewDataSourceType {
    <<interface>>
    +tableView(_:observedEvent:) 
  }


  class SectionedViewDataSourceType {
    <<interface>>
    +model(at:) 
  }


  class SharedSequenceConvertibleType {
    <<interface>>
    +asSharedSequence() SharedSequence
    +drive(_:) 
    +drive(_:) 
    +drive(_:) 
    +drive(_:) 
    +drive(_:) 
    +drive(_:) 
    +drive(_:) 
    +drive(_:curriedArgument:) 
    +drive(with:onNext:onCompleted:onDisposed:) 
    +drive(onNext:onCompleted:onDisposed:) 
    +drive() 
    +asDriver() SharedSequence
    +asObservable() 
    +map(_:) SharedSequence
    +compactMap(_:) SharedSequence
    +filter(_:) SharedSequence
    +switchLatest() SharedSequence
    +flatMapLatest(_:) SharedSequence
    +flatMapFirst(_:) SharedSequence
    +`do`(onNext:afterNext:onCompleted:afterCompleted:onSubscribe:onSubscribed:onDispose:) SharedSequence
    +debug(_:trimOutput:file:line:function:) SharedSequence
    +distinctUntilChanged() SharedSequence
    +distinctUntilChanged(_:) SharedSequence
    +distinctUntilChanged(_:) SharedSequence
    +distinctUntilChanged(_:comparer:) SharedSequence
    +flatMap(_:) SharedSequence
    +merge() SharedSequence
    +merge(maxConcurrent:) SharedSequence
    +throttle(_:latest:) SharedSequence
    +debounce(_:) SharedSequence
    +scan(_:accumulator:) SharedSequence
    +withUnretained(_:resultSelector:) SharedSequence
    +withUnretained(_:) SharedSequence
    +withLatestFrom(_:resultSelector:) SharedSequence
    +withLatestFrom(_:) SharedSequence
    +skip(_:) SharedSequence
    +startWith(_:) SharedSequence
    +delay(_:) SharedSequence
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(to:) 
    +emit(with:onNext:onCompleted:onDisposed:) 
    +emit(onNext:onCompleted:onDisposed:) 
    +emit() 
    +asSignal() SharedSequence
  }
  ObservableConvertibleType <|-- SharedSequenceConvertibleType : inheritance


  class SharingStrategyProtocol {
    <<interface>>
  }


  class BarButtonItemTarget {
    +UIBarButtonItem? barButtonItem
    +Callback callback
    +dispose() 
    +action(_:) 
  }
  RxTarget <|-- BarButtonItemTarget : inheritance


  class CollectionViewDataSourceNotSet {
    +collectionView(_:numberOfItemsInSection:) Int
    +collectionView(_:cellForItemAt:) 
  }


  class CollectionViewPrefetchDataSourceNotSet {
    +collectionView(_:prefetchItemsAt:) 
  }


  class ControlTarget {
    +Selector selector
    +Control? control
    +UIControl.Event controlEvents
    +Callback? callback
    +eventHandler(_:) 
    +dispose() 
  }
  RxTarget <|-- ControlTarget : inheritance


  class DeallocObservable {
    +ReplaySubject<Void> subject
    +deinit() 
  }


  class DeallocatingProxy {
    +IMP targetImplementation
    +Bool isActive
    +deallocating() 
    +deinit() 
  }
  MessageInterceptorSubject <|.. DeallocatingProxy : realization
  DeallocatingProxy *-- Bool : composition


  class DelegateProxy {
    -[Selector: MessageDispatcher] _sentMessageForSelector
    -[Selector: MessageDispatcher] _methodInvokedForSelector
    -ParentObject? _parentObject
    +sentMessage(_:) 
    +methodInvoked(_:) 
    -checkSelectorIsObservable(_:) 
    +_sentMessage(_:withArguments:) 
    +_methodInvoked(_:withArguments:) 
    +forwardToDelegate() 
    +setForwardToDelegate(_:retainDelegate:) 
    -hasObservers(selector:) Bool
    +responds(to:) Bool
    -reset() 
    +deinit() 
  }
  DelegateProxy o--  MessageDispatcher : aggregation
  DelegateProxy o--  MessageDispatcher : aggregation


  class DelegateProxyFactory {
    -Any.Type _delegateProxyType
    -UnsafeRawPointer _identifier
    -extend(make:) 
    -createProxy(for:) 
  }
  DelegateProxyFactory o--  DelegateProxyFactory : aggregation


  class GestureTarget {
    +#selector selector
    +Recognizer? gestureRecognizer
    +Callback? callback
    +eventHandler(_:) 
    +dispose() 
  }
  RxTarget <|-- GestureTarget : inheritance


  class KVOObservable {
    +AnyObject target
    +AnyObject? strongTarget
    +String keyPath
    +KeyValueObservingOptions options
    +Bool retainTarget
    +subscribe(_:) 
  }
  KVOObservableProtocol <|.. KVOObservable : realization
  ObservableType <|-- KVOObservable : inheritance
  KVOObservable *-- KeyValueObservingOptions : composition
  KVOObservable *-- Bool : composition


  class KVOObserver {
    +KVOObserver? retainSelf
    +dispose() 
    +deinit() 
  }
  KVOObserver *-- KVOObserver : composition


  class MessageDispatcher {
    -PublishSubject<[Any]> dispatcher
    -Observable<[Any]> result
    -Selector selector
    +Bool hasObservers
    +asObservable() 
  }
  MessageDispatcher *-- Bool : composition
  MessageDispatcher o-- PublishSubject~Any~ : aggregation
  MessageDispatcher o-- Observable~Any~ : aggregation


  class MessageSentProxy {
    +PublishSubject<[Any]> messageSent
    +PublishSubject<[Any]> methodInvoked
    +IMP targetImplementation
    +Bool isActive
    +messageSent(withArguments:) 
    +methodInvoked(withArguments:) 
    +deinit() 
  }
  MessageInterceptorSubject <|.. MessageSentProxy : realization
  MessageSentProxy *-- Bool : composition
  MessageSentProxy o-- PublishSubject~Any~ : aggregation
  MessageSentProxy o-- PublishSubject~Any~ : aggregation


  class PickerViewDataSourceNotSet {
    +numberOfComponents(in:) Int
    +pickerView(_:numberOfRowsInComponent:) Int
  }


  class RxAttributedStringPickerViewAdapter {
    -AttributedTitleForRow attributedTitleForRow
    +pickerView(_:attributedTitleForRow:forComponent:) 
  }


  class RxCollectionViewDataSourcePrefetchingProxy {
    +UICollectionView? collectionView
    -PublishSubject<[IndexPath]>? _prefetchItemsPublishSubject
    +PublishSubject<[IndexPath]> prefetchItemsPublishSubject
    -UICollectionViewDataSourcePrefetching? _requiredMethodsPrefetchDataSource
    +setForwardToDelegate(_:retainDelegate:) 
    +deinit() 
    +collectionView(_:prefetchItemsAt:) 
  }
  DelegateProxyType <|.. RxCollectionViewDataSourcePrefetchingProxy : realization
  RxCollectionViewDataSourcePrefetchingProxy *-- UICollectionView : composition
  RxCollectionViewDataSourcePrefetchingProxy o-- PublishSubject~IndexPath~ : aggregation
  RxCollectionViewDataSourcePrefetchingProxy o-- PublishSubject~IndexPath~ : aggregation


  class RxCollectionViewDataSourceProxy {
    +UICollectionView? collectionView
    -UICollectionViewDataSource? _requiredMethodsDataSource
    +setForwardToDelegate(_:retainDelegate:) 
    +collectionView(_:numberOfItemsInSection:) Int
    +collectionView(_:cellForItemAt:) 
  }
  DelegateProxyType <|.. RxCollectionViewDataSourceProxy : realization
  RxCollectionViewDataSourceProxy *-- UICollectionView : composition


  class RxCollectionViewDelegateProxy {
    +UICollectionView? collectionView
  }
  DelegateProxyType <|.. RxCollectionViewDelegateProxy : realization
  RxScrollViewDelegateProxy <|-- RxCollectionViewDelegateProxy : inheritance
  RxCollectionViewDelegateProxy *-- UICollectionView : composition


  class RxCollectionViewReactiveArrayDataSource {
    +[Element]? itemModels
    +CellFactory cellFactory
    +modelAtIndex(_:) 
    +model(at:) 
    +_collectionView(_:numberOfItemsInSection:) Int
    +_collectionView(_:cellForItemAt:) 
    +collectionView(_:observedElements:) 
  }
  SectionedViewDataSourceType <|.. RxCollectionViewReactiveArrayDataSource : realization
  _RxCollectionViewReactiveArrayDataSource <|-- RxCollectionViewReactiveArrayDataSource : inheritance
  RxCollectionViewReactiveArrayDataSource o-- Element : aggregation


  class RxCollectionViewReactiveArrayDataSourceSequenceWrapper {
    +collectionView(_:observedEvent:) 
  }
  RxCollectionViewDataSourceType <|.. RxCollectionViewReactiveArrayDataSourceSequenceWrapper : realization


  class RxNavigationControllerDelegateProxy {
    +UINavigationController? navigationController
  }
  DelegateProxyType <|.. RxNavigationControllerDelegateProxy : realization
  RxNavigationControllerDelegateProxy *-- UINavigationController : composition


  class RxPickerViewAdapter {
    -ViewForRow viewForRow
    +pickerView(_:viewForRow:forComponent:reusing:) 
  }


  class RxPickerViewArrayDataSource {
    -[T] items
    +model(at:) 
    +numberOfComponents(in:) Int
    +pickerView(_:numberOfRowsInComponent:) Int
  }
  SectionedViewDataSourceType <|.. RxPickerViewArrayDataSource : realization
  RxPickerViewArrayDataSource o-- T : aggregation


  class RxPickerViewDataSourceProxy {
    +UIPickerView? pickerView
    -UIPickerViewDataSource? _requiredMethodsDataSource
    +setForwardToDelegate(_:retainDelegate:) 
    +numberOfComponents(in:) Int
    +pickerView(_:numberOfRowsInComponent:) Int
  }
  DelegateProxyType <|.. RxPickerViewDataSourceProxy : realization
  RxPickerViewDataSourceProxy *-- UIPickerView : composition


  class RxPickerViewDelegateProxy {
    +UIPickerView? pickerView
  }
  DelegateProxyType <|.. RxPickerViewDelegateProxy : realization
  RxPickerViewDelegateProxy *-- UIPickerView : composition


  class RxPickerViewSequenceDataSource {
    +pickerView(_:observedEvent:) 
  }
  RxPickerViewDataSourceType <|.. RxPickerViewSequenceDataSource : realization


  class RxScrollViewDelegateProxy {
    +UIScrollView? scrollView
    -BehaviorSubject<CGPoint>? _contentOffsetBehaviorSubject
    +BehaviorSubject<CGPoint> contentOffsetBehaviorSubject
    +deinit() 
    +scrollViewDidScroll(_:) 
  }
  DelegateProxyType <|.. RxScrollViewDelegateProxy : realization
  RxScrollViewDelegateProxy *-- UIScrollView : composition


  class RxSearchBarDelegateProxy {
    +UISearchBar? searchBar
  }
  DelegateProxyType <|.. RxSearchBarDelegateProxy : realization
  RxSearchBarDelegateProxy *-- UISearchBar : composition


  class RxSearchControllerDelegateProxy {
    +UISearchController? searchController
  }
  DelegateProxyType <|.. RxSearchControllerDelegateProxy : realization
  RxSearchControllerDelegateProxy *-- UISearchController : composition


  class RxStringPickerViewAdapter {
    -TitleForRow titleForRow
    +pickerView(_:titleForRow:forComponent:) 
  }


  class RxTabBarControllerDelegateProxy {
    +UITabBarController? tabBar
  }
  DelegateProxyType <|.. RxTabBarControllerDelegateProxy : realization
  RxTabBarControllerDelegateProxy *-- UITabBarController : composition


  class RxTabBarDelegateProxy {
    +UITabBar? tabBar
  }
  DelegateProxyType <|.. RxTabBarDelegateProxy : realization
  RxTabBarDelegateProxy *-- UITabBar : composition


  class RxTableViewDataSourcePrefetchingProxy {
    +UITableView? tableView
    -PublishSubject<[IndexPath]>? _prefetchRowsPublishSubject
    +PublishSubject<[IndexPath]> prefetchRowsPublishSubject
    -UITableViewDataSourcePrefetching? _requiredMethodsPrefetchDataSource
    +setForwardToDelegate(_:retainDelegate:) 
    +deinit() 
    +tableView(_:prefetchRowsAt:) 
  }
  DelegateProxyType <|.. RxTableViewDataSourcePrefetchingProxy : realization
  RxTableViewDataSourcePrefetchingProxy *-- UITableView : composition
  RxTableViewDataSourcePrefetchingProxy o-- PublishSubject~IndexPath~ : aggregation
  RxTableViewDataSourcePrefetchingProxy o-- PublishSubject~IndexPath~ : aggregation


  class RxTableViewDataSourceProxy {
    +UITableView? tableView
    -UITableViewDataSource? _requiredMethodsDataSource
    +setForwardToDelegate(_:retainDelegate:) 
    +tableView(_:numberOfRowsInSection:) Int
    +tableView(_:cellForRowAt:) 
  }
  DelegateProxyType <|.. RxTableViewDataSourceProxy : realization
  RxTableViewDataSourceProxy *-- UITableView : composition


  class RxTableViewDelegateProxy {
    +UITableView? tableView
  }
  DelegateProxyType <|.. RxTableViewDelegateProxy : realization
  RxScrollViewDelegateProxy <|-- RxTableViewDelegateProxy : inheritance
  RxTableViewDelegateProxy *-- UITableView : composition


  class RxTableViewReactiveArrayDataSource {
    +[Element]? itemModels
    +CellFactory cellFactory
    +modelAtIndex(_:) 
    +model(at:) 
    +_tableView(_:numberOfRowsInSection:) Int
    +_tableView(_:cellForRowAt:) 
    +tableView(_:observedElements:) 
  }
  SectionedViewDataSourceType <|.. RxTableViewReactiveArrayDataSource : realization
  _RxTableViewReactiveArrayDataSource <|-- RxTableViewReactiveArrayDataSource : inheritance
  RxTableViewReactiveArrayDataSource o-- Element : aggregation


  class RxTableViewReactiveArrayDataSourceSequenceWrapper {
    +tableView(_:observedEvent:) 
  }
  RxTableViewDataSourceType <|.. RxTableViewReactiveArrayDataSourceSequenceWrapper : realization


  class RxTarget {
    -RxTarget? retainSelf
    +dispose() 
    +deinit() 
  }
  RxTarget *-- RxTarget : composition


  class RxTextFieldDelegateProxy {
    +NSTextField? textField
    -PublishSubject<String?> textSubject
    +controlTextDidChange(_:) 
  }
  DelegateProxyType <|.. RxTextFieldDelegateProxy : realization


  class RxTextStorageDelegateProxy {
    +NSTextStorage? textStorage
  }
  DelegateProxyType <|.. RxTextStorageDelegateProxy : realization
  RxTextStorageDelegateProxy *-- NSTextStorage : composition


  class RxTextViewDelegateProxy {
    +UITextView? textView
    +NSTextView? textView
    -PublishSubject<String> textSubject
    +textDidChange(_:) 
    +textView(_:shouldChangeTextIn:replacementText:) Bool
  }
  DelegateProxyType <|.. RxTextViewDelegateProxy : realization
  RxScrollViewDelegateProxy <|-- RxTextViewDelegateProxy : inheritance


  class RxWKNavigationDelegateProxy {
    +WKWebView? webView
  }
  DelegateProxyType <|.. RxWKNavigationDelegateProxy : realization


  class TableViewDataSourceNotSet {
    +tableView(_:numberOfRowsInSection:) Int
    +tableView(_:cellForRowAt:) 
  }


  class TableViewPrefetchDataSourceNotSet {
    +tableView(_:prefetchRowsAt:) 
  }


  class _RxCollectionViewReactiveArrayDataSource {
    +numberOfSections(in:) Int
    +_collectionView(_:numberOfItemsInSection:) Int
    +collectionView(_:numberOfItemsInSection:) Int
    -_collectionView(_:cellForItemAt:) 
    +collectionView(_:cellForItemAt:) 
  }


  class _RxTableViewReactiveArrayDataSource {
    +numberOfSections(in:) Int
    +_tableView(_:numberOfRowsInSection:) Int
    +tableView(_:numberOfRowsInSection:) Int
    -_tableView(_:cellForRowAt:) 
    +tableView(_:cellForRowAt:) 
  }

classDiagram

  class CollectionSectionProtocol {
    <<interface>>
    +layoutSection() 
    +didSelectItem(at:) 
  }


  class LoggerProtocol {
    <<interface>>
    +debug(_:file:function:line:column:) 
    +info(_:file:function:line:column:) 
    +notice(_:file:function:line:column:) 
    +error(_:file:function:line:column:) 
    +fault(_:file:function:line:column:) 
    +exception(_:file:function:line:column:) 
  }


  class MaterialView {
    <<interface>>
    +elevate(elevation:) 
  }


  class MessagingBridgeDelegate {
    <<interface>>
    +didReceiveRegistrationToken(_:) 
  }


  class MonsterDetailEventHandler {
    <<interface>>
    +viewDidLoad() 
    +didTapDancingImageView(dancingImage:) 
    +didTapShareButton(_:name:description:icon:) 
  }


  class MonsterDetailInteractorInput {
    <<interface>>
  }


  class MonsterDetailInteractorOutput {
    <<interface>>
  }


  class MonsterDetailRouterInput {
    <<interface>>
    +popupDancingImage(_:) 
    +showActivity(_:text:icon:) 
  }


  class MonsterDetailUserInterface {
    <<interface>>
  }


  class MonsterListEventHandler {
    <<interface>>
    +viewDidLoad() 
    +didTapContactUs() 
    +didTapPrivacyPolicy() 
    +didTapLicenses() 
    +didTapAboutThisApp() 
  }


  class MonsterListInteractorInput {
    <<interface>>
    +monsters() 
    +saveMonsterInSpotlight(_:) 
  }


  class MonsterListInteractorOutput {
    <<interface>>
  }


  class MonsterListRouterInput {
    <<interface>>
    +showMonsterDetail(monster:) 
    +showContactUs() 
    +showPrivacyPolicy() 
    +showSettings() 
    +showAboutThisApp() 
  }


  class MonsterListUserInterface {
    <<interface>>
    +showMonsters(_:) 
    +startIndicator() 
    +stopIndicator() 
  }


  class MonsterSectionEventHandler {
    <<interface>>
    +didSelectMonster(at:) 
  }


  class MonstersRepository {
    <<interface>>
    +monsters() 
  }


  class MonstersTempRepository {
    <<interface>>
    +monster(key:) MonsterEntity
    +saveMonster(_:forKey:) 
  }


  class SpotlightRepository {
    <<interface>>
    +saveMonster(_:forKey:) 
  }


  class BaseView {
    -CGFloat cornerRadius
  }
  MaterialView <|.. BaseView : realization
  UIView <|-- BaseView : inheritance


  class FirebaseMessagingBridge {
    +MessagingBridgeDelegate? delegate
    +messaging(_:didReceiveRegistrationToken:) 
  }
  FirebaseMessagingBridge *-- MessagingBridgeDelegate : composition


  class ImageCacheManager {
    +cacheImage(with:) UIImage
    +cacheGIFImage(with:) UIImage
  }


  class ImagePopupViewController {
    +UIImage image
    -UIImageView imageView
    +viewDidLoad() 
    -didTapCloseButton(_:) 
    -configureView() 
  }
  UIViewController <|-- ImagePopupViewController : inheritance
  ImagePopupViewController *-- UIImage : composition
  ImagePopupViewController *-- UIImageView : composition


  class InAppWebBrowserViewController {
    +URL url
    -UIProgressView progressView
    -NSKeyValueObservation? estimatedProgressObservation
    -WKWebView webView
    +viewDidLoad() 
    -configureView() 
    -configureProgressView() 
    -configureWebView() 
    -observeWebView() 
    -loadWebView() 
  }
  UIViewController <|-- InAppWebBrowserViewController : inheritance
  InAppWebBrowserViewController *-- URL : composition


  class Logger {
    -os.Logger logger
    +debug(_:file:function:line:column:) 
    +info(_:file:function:line:column:) 
    +notice(_:file:function:line:column:) 
    +error(_:file:function:line:column:) 
    +fault(_:file:function:line:column:) 
    +exception(_:file:function:line:column:) 
    -logRow(_:file:function:line:column:) 
  }
  LoggerProtocol <|.. Logger : realization
  Logger *-- Logger : composition


  class MonsterCollectionSection {
    -MonsterSectionEventHandler presenter
    +layoutSection() 
    +didSelectItem(at:) 
  }
  CollectionSectionProtocol <|.. MonsterCollectionSection : realization
  MonsterCollectionSection *-- MonsterSectionEventHandler : composition


  class MonsterCollectionViewCell {
    -Bool masksToBounds
    -BaseView baseView
    -UIImageView iconImageView
    -UILabel nameLabel
    +prepareForReuse() 
    +setupWith(name:iconURL:elevation:) 
  }
  MonsterCollectionViewCell *-- BaseView : composition
  MonsterCollectionViewCell *-- UIImageView : composition


  class MonsterDetailInteractor {
    -MonsterDetailInteractorOutput presenter
    +inject(presenter:) 
  }
  MonsterDetailInteractorInput <|.. MonsterDetailInteractor : realization
  MonsterDetailInteractor *-- MonsterDetailInteractorOutput : composition


  class MonsterDetailPresenter {
    -MonsterDetailUserInterface view
    -MonsterDetailInteractorInput interactor
    -MonsterDetailRouterInput router
    +viewDidLoad() 
    +didTapDancingImageView(dancingImage:) 
    +didTapShareButton(_:name:description:icon:) 
  }
  MonsterDetailEventHandler <|.. MonsterDetailPresenter : realization
  MonsterDetailInteractorOutput <|.. MonsterDetailPresenter : realization
  MonsterDetailPresenter *-- MonsterDetailUserInterface : composition
  MonsterDetailPresenter *-- MonsterDetailInteractorInput : composition
  MonsterDetailPresenter *-- MonsterDetailRouterInput : composition


  class MonsterDetailRouter {
    -MonsterDetailViewController viewController
    +popupDancingImage(_:) 
    +showActivity(_:text:icon:) 
  }
  MonsterDetailRouterInput <|.. MonsterDetailRouter : realization
  MonsterDetailRouter *-- MonsterDetailViewController : composition


  class MonsterDetailViewController {
    -MonsterDetailEventHandler presenter
    -MonsterItem monster
    -UIImageView iconImageView
    -UIImageView dancingImageView
    -UILabel nameLabel
    -UILabel descriptionLabel
    +viewDidLoad() 
    -didTapShareButton(_:) 
    +inject(presenter:monster:) 
    -didTapDancingImageView(_:) 
    -configureView() 
  }
  MonsterDetailUserInterface <|.. MonsterDetailViewController : realization
  UIViewController <|-- MonsterDetailViewController : inheritance
  MonsterDetailViewController *-- MonsterDetailEventHandler : composition
  MonsterDetailViewController *-- MonsterItem : composition
  MonsterDetailViewController *-- UIImageView : composition
  MonsterDetailViewController *-- UIImageView : composition


  class MonsterListInteractor {
    -MonsterListInteractorOutput presenter
    -MonstersRepository monstersRepository
    -MonstersTempRepository monstersTempRepository
    -SpotlightRepository spotlightRepository
    +inject(presenter:) 
    +monsters() 
    +saveMonsterInSpotlight(_:) 
  }
  MonsterListInteractorInput <|.. MonsterListInteractor : realization
  MonsterListInteractor *-- MonsterListInteractorOutput : composition
  MonsterListInteractor *-- MonstersRepository : composition
  MonsterListInteractor *-- MonstersTempRepository : composition
  MonsterListInteractor *-- SpotlightRepository : composition


  class MonsterListPresenter {
    -MonsterListUserInterface view
    -MonsterListInteractorInput interactor
    -MonsterListRouterInput router
    -[MonsterEntity] monsters
    +viewDidLoad() 
    +didTapContactUs() 
    +didTapPrivacyPolicy() 
    +didTapLicenses() 
    +didTapAboutThisApp() 
    +didSelectMonster(at:) 
  }
  MonsterListEventHandler <|.. MonsterListPresenter : realization
  MonsterListInteractorOutput <|.. MonsterListPresenter : realization
  MonsterSectionEventHandler <|.. MonsterListPresenter : realization
  MonsterListPresenter *-- MonsterListUserInterface : composition
  MonsterListPresenter *-- MonsterListInteractorInput : composition
  MonsterListPresenter *-- MonsterListRouterInput : composition
  MonsterListPresenter o-- MonsterEntity : aggregation


  class MonsterListRouter {
    -MonsterListViewController viewController
    +showMonsterDetail(monster:) 
    +showContactUs() 
    +showPrivacyPolicy() 
    +showSettings() 
    +showAboutThisApp() 
  }
  MonsterListRouterInput <|.. MonsterListRouter : realization
  MonsterListRouter *-- MonsterListViewController : composition


  class MonsterListViewController {
    -MonsterListEventHandler presenter
    -[CollectionSectionProtocol] sections
    -UICollectionView monstersCollectionView
    -UICollectionViewDiffableDataSource<Section, Item> dataSource
    -UIBarButtonItem menuButton
    -UIActivityIndicatorView activityIndicatorView
    +viewDidLoad() 
    +viewWillAppear(_:) 
    +inject(sections:presenter:) 
    -configureMonstersCollectionView() 
    -applyToDataSource(monsters:) 
    +collectionView(_:didSelectItemAt:) 
    +showMonsters(_:) 
    +startIndicator() 
    +stopIndicator() 
  }
  MonsterListUserInterface <|.. MonsterListViewController : realization
  UIViewController <|-- MonsterListViewController : inheritance
  MonsterListViewController *-- MonsterListEventHandler : composition
  MonsterListViewController *-- UICollectionView : composition
  MonsterListViewController o-- CollectionSectionProtocol : aggregation


  class MonstersFirestoreClient {
    -Firestore firestore
    +monsters() 
  }
  MonstersRepository <|.. MonstersFirestoreClient : realization
  MonstersFirestoreClient *-- MonstersFirestoreClient : composition


  class SpotlightClient {
    -CSSearchableIndex searchableIndex
    -LoggerProtocol logger
    +saveMonster(_:forKey:) 
    -attributeSet(title:contentDescription:thumbnailData:) 
  }
  SpotlightRepository <|.. SpotlightClient : realization
  SpotlightClient *-- LoggerProtocol : composition


  class UserDefaultsClient {
    -UserDefaults userDefaults
    +removeAll() 
    +monster(key:) MonsterEntity
    +saveMonster(_:forKey:) 
  }
  MonstersTempRepository <|.. UserDefaultsClient : realization
  UserDefaultsClient *-- UserDefaultsClient : composition

{# --- macro definitions --- #}
{# --- Variables --- #}
{% macro Variables type %}
{% for variable in type.variables where not variable.isStatic and not variable.typeName|contains:"(" %}
{% call Variable variable %}
{% endfor %}
{% endmacro %}
{# --- outputs --- #}
{% macro Variable variable %}
{% call VariableVisibility variable %}{% call VariableTypeName variable.typeName.name %} {{ variable.name }}
{% endmacro %}
{% macro VariableVisibility target %}{% if target.readAccess == "private" %}-{% elif target.readAccess == "fileprivate" %}-{% else %}+{% endif %}{% endmacro %}
{% macro VariableTypeName typeName %}{% set tmp1 %}{% call PrefixFirst typeName "{" %}{% endset %}{{ tmp1|replace:"!","" }}{% endmacro %} {# work-around for variables with initializer in oneline #}
{# --- Methods --- #}
{% macro Methods type %}
{% for method in type.methods where not method.isInitializer and not method.isStatic and not method.isClass %}
{% call Method method %}
{% endfor %}
{% endmacro %}
{# --- outputs --- #}
{% macro Method method %}
{% call MethodVisibility method %}{% call MethodName method %} {{ method.returnType.name }}
{% endmacro %}
{% macro MethodVisibility target %}{% if target.accessLevel == "private" %}-{% elif target.accessLevel == "fileprivate" %}-{% else %}+{% endif %}{% endmacro %}
{% macro MethodName method %}{{ method.selectorName }}{% if method.parameters.count == 0 %}(){% endif %}{% endmacro %}
{# --- Relationships --- #}
{% macro ClassRelationships type %}
{% for basedTypeName, basedType in type.basedTypes %}
{% if basedType.kind == 'protocol' %}
{% call Realization basedType.name type.name %}
{% else %}
{% call Inheritance basedType.name type.name %}
{% endif %}
{% endfor %}
{% call CompositionRelationships type %}
{% call AggregationRelationships type %}
{# {% call AssociationRelationships type %} #}
{% endmacro %}
{% macro ProtocolRelationships type %}
{% for basedTypeName, basedType in type.basedTypes %}
{% call Inheritance basedType.name type.name %}
{% endfor %}
{% call CompositionRelationships type %}
{% call AggregationRelationships type %}
{# {% call AssociationRelationships type %} #}
{% endmacro %}
{% macro CompositionRelationships type %}
{% for variable in type.variables %}
{% if variable.type.name %}
{% call Composition type.name variable.type.name %}
{% endif %}
{% endfor %}
{% endmacro %}
{% macro AggregationRelationships type %}
{% for variable in type.variables %}
{# --- Closure --- #}
{% if variable.typeName|contains:"(" %}
{# nop #}
{# --- Dictionary --- #}
{% elif variable.typeName|contains:":" %}
{% set element %}{% call SugarDictionaryValueElement variable.typeName %}{% endset %}
{% set elementFormatted %}{% call FormatClassName element %}{% endset %}
{% call Aggregation type.name elementFormatted %}
{% elif variable.typeName|contains:"Dictionary<" %}
{% set element %}{% call DictionaryValueElement variable.typeName %}{% endset %}
{% set elementFormatted %}{% call FormatClassName element %}{% endset %}
{% call Aggregation type.name elementFormatted %}
{# --- Array --- #}
{% elif variable.typeName|contains:"[" %}
{% set element %}{% call SugarArrayElement variable.typeName %}{% endset %}
{% set elementFormatted %}{% call FormatClassName element %}{% endset %}
{% call Aggregation type.name elementFormatted %}
{% elif variable.typeName|contains:"Array<" %}
{% set element %}{% call ArrayElement variable.typeName %}{% endset %}
{% set elementFormatted %}{% call FormatClassName element %}{% endset %}
{% call Aggregation type.name elementFormatted %}
{# --- Set --- #}
{% elif variable.typeName|contains:"Set<" %}
{% set element %}{% call SetElement variable.typeName %}{% endset %}
{% set elementFormatted %}{% call FormatClassName element %}{% endset %}
{% call Aggregation type.name elementFormatted %}
{% endif %}
{% endfor %}
{% endmacro %}
{% macro AssociationRelationships type %}
{% for method in type.methods %}
{% for parameter in method.parameters %}
{% if parameter.type.name %}
{% call Association type.name parameter.type.name %}
{% endif %}
{% endfor %}
{% endfor %}
{% endmacro %}
{# --- outputs --- #}
{% macro Inheritance basedType typeName %}
{% call FormatClassName basedType %} <|-- {% call FormatClassName typeName %} : inheritance
{% endmacro %}
{% macro Realization basedType typeName %}
{% call FormatClassName basedType %} <|.. {% call FormatClassName typeName %} : realization
{% endmacro %}
{% macro Composition subject target %}
{% call FormatClassName subject %} *-- {% call FormatClassName target %} : composition
{% endmacro %}
{% macro Aggregation subject target %}
{% call FormatClassName subject %} o-- {% call FormatClassName target %} : aggregation
{% endmacro %}
{% macro Association subject target %}
{% call FormatClassName subject %} --> {% call FormatClassName target %} : association
{% endmacro %}
{# --- Helpers --- #}
{% macro SugarDictionaryValueElement target %}{% for element in target|replace:"",""|split:":" %}{{ element|replace:"[",""|replace:"]",""|replace:"<","~"|replace:">","~" if forloop.last }}{% endfor %}{% endmacro %}
{% macro DictionaryValueElement target %}{% for element in target|replace:"Dictionary<",""|replace:">",""|split:"," %}{{ element if forloop.last }}{% endfor %}{% endmacro %}
{% macro SugarArrayElement target %}{{ target|replace:"[",""|replace:"]","" }}{% endmacro %}
{% macro ArrayElement target %}{{ target|replace:"Array",""|replace:"<",""|replace:">","" }}{% endmacro %}
{% macro SetElement target %}{{ target|replace:"Set<",""|replace:">","" }}{% endmacro %}
{% macro FormatClassName target %}{% set tmp1 %}{% call FormatOptional target %}{% endset %}{% set tmp2 %}{% call FormatGenerics tmp1 %}{% endset %}{% call FormatInnerType tmp2 %}{% endmacro %}
{% macro FormatOptional target %}{{ target|replace:"?",""|replace:"!","" }}{% endmacro %}
{% macro FormatGenerics target %}{{ target|replace:"<","~"|replace:">","~" }}{% endmacro %}
{% macro FormatInnerType target %}{{ target|replace:".","__" }}{% endmacro %}
{% macro PrefixFirst target separator %}{% for element in target|split:separator %}{{ element if forloop.first }}{% endfor %}{% endmacro %}
{# --- macro definitions end --- #}
```mermaid
classDiagram
{% for type in types.protocols %}
class {% call FormatClassName type.name %} {
<<interface>>
{% call Variables type %}
{% call Methods type %}
}
{% call ProtocolRelationships type %}
{% endfor %}
{% for type in types.classes %}
class {% call FormatClassName type.name %} {
{% call Variables type %}
{% call Methods type %}
}
{% call ClassRelationships type %}
{% endfor %}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment