Skip to content

Instantly share code, notes, and snippets.

@wendyliga
Last active September 25, 2023 04:09
Show Gist options
  • Save wendyliga/792ea9ac5cc887f59de70a9e39cc7343 to your computer and use it in GitHub Desktop.
Save wendyliga/792ea9ac5cc887f59de70a9e39cc7343 to your computer and use it in GitHub Desktop.
swift-composable-architecture readme indonesia translation

The Composable Architecture

Composable Architecture (pendeknya TCA) adalah library untuk membangun aplikasi dengan cara yang konsisten dan mudah dimengerti, dengan komposisi, testing, dan design ergonomis. TCA dapat digunakan di SwiftUI, UIKit, dan lainnya, dan di platform Apple apa pun (iOS, macOS, tvOS, dan watchOS)

Apa yang baru di Composable Architecture?

library ini menyediakan beberapa tool yang dapat digunakan untuk membangun aplikasi dengan berbagai tujuan dan kompleksitas. Terdapat video tutorial(dengan penjelasan) menarik yang dapat Anda ikuti untuk memecahkan banyak masalah yang Anda temui sehari-hari saat membangun aplikasi, seperti:

  • State management
    Cara mengelola state aplikasi Anda menggunakan value type yang sederhana, dan membagikan state aplikasi ke banyak layar sehingga mutasi di satu layar dapat segera diamati di layar lain.

  • Composition
    Cara memecah fitur besar menjadi komponen yang lebih kecil yang dapat diekstraksi ke modul tersendiri yang terisolasi dan dengan mudah direkatkan kembali untuk membentuk fitur.

  • Side effects
    Bagaimana membiarkan bagian-bagian tertentu dari aplikasi berbicara dengan dunia luar dengan cara yang paling dapat diuji dan dimengerti.

  • Testing
    Bagaimana tidak hanya menguji fitur yang dibangun dalam arsitektur, tetapi juga menulis test integrasi untuk fitur yang dibuat dari banyak bagian, dan menulis test end-to-end (menyeluruh) untuk memahami bagaimana Side Effect memengaruhi aplikasi Anda. Ini memungkinkan Anda untuk membuat jaminan yang kuat bahwa logika bisnis Anda berjalan seperti yang Anda harapkan.

  • Ergonomics
    Cara menyelesaikan semua hal di atas dalam API sederhana dengan konsep dan bagian yang bergerak sesedikit mungkin.

Pelajari lebih lanjut

Composable Architecture didesign diatas kursus di banyak episode di Point-Free, vidio series tentang functional programming dan Swift, dibawakan oleh Brandon Williams and Stephen Celis.

Anda dapat menonton semua episode di sini, serta penjelasan latar belakang arsitektur dari awal: bagian 1, bagian 2, bagian 3 and bagian 4.

gambar poster vidio

Contoh

Screen shots of example applications

Repo ini dilengkapi dengan banyak contoh untuk menunjukkan bagaimana memecahkan masalah umum dan kompleks dengan Composable Architecture. Lihat direktori ini untuk melihat semuanya, termasuk:

Mencari sesuatu yang lebih substansial? Lihat kode sumber untuk isowords, game pencarian kata iOS yang dibangun di SwiftUI dan Composable Architecture.

Cara memakai

Untuk membangun fitur menggunakan Composable Architecture, Anda perlu menentukan beberapa type dan value untuk memodelkan domain Anda:

  • State: type yang menjelaskan data yang dibutuhkan fitur Anda untuk menjalankan logikanya dan merender UI-nya.

  • Action: type yang mewakili semua tindakan yang dapat terjadi di fitur Anda, seperti interaksi pengguna, pemberitahuan, event, dan lainnya.

  • Environment: type yang menyimpan dependensi apa pun yang dibutuhkan fitur, seperti client untuk API, klien untuk analitik, dll

  • Reducer: function yang menjelaskan cara memperbaruhi state aplikasi saat ini ke state berikutnya sesuai Action yang diberikan. reducer juga bertanggung jawab untuk mengembalikan semua effect yang harus dijalankan, seperti permintaan API, yang dapat dilakukan dengan mengembalikan Effect.

  • Store: runtime yang benar-benar menjalankan fitur Anda. Anda mengirim semua action pengguna ke store sehingga store dapat menjalankan reducer dan effect, dan Anda dapat mengamati perubahan status di store sehingga Anda dapat memperbarui UI.

Manfaat melakukan ini adalah Anda akan mendapatkan manfaat testability di fitur Anda, dan Anda akan dapat memecah fitur besar dan kompleks menjadi domain yang lebih kecil yang dapat disatukan.

Sebagai contoh sederhana, pertimbangkan UI yang menampilkan angka bersama dengan tombol "+" dan "−" yang menambah dan mengurangi angka. Untuk membuat contoh lebih menarik, misalkan ada juga tombol yang ketika diketuk dapat membuat permintaan API untuk mengambil fakta acak tentang nomor tersebut dan kemudian menampilkan fakta dalam alert.

state fitur ini akan terdiri dari bilangan bulat untuk angka saat ini, serta string opsional yang mewakili judul alert yang ingin kita tampilkan (opsional karena nil menunjukkan tidak menampilkan peringatan):

struct AppState: Equatable {
  var count = 0
  var numberFactAlert: String?
}

Selanjutnya kita memiliki action dalam fitur. Ada action yang jelas, seperti menekan tombol -, tombol +, atau tombol fakta. Tetapi ada juga beberapa yang sedikit tidak jelas, seperti action pengguna mengabaikan alert, dan alert yang terjadi saat menerima respons dari permintaan API fakta:

enum AppAction: Equatable {
  case factAlertDismissed
  case decrementButtonTapped
  case incrementButtonTapped
  case numberFactButtonTapped
  case numberFactResponse(Result<String, ApiError>)
}

struct ApiError: Error, Equatable {}

Selanjutnya kita memodelkan dependency di environment yang dibutuhkan fitur ini untuk melakukan tugasnya. Secara khusus, untuk mengambil fakta angka, kita perlu membuat Effect yang merangkum permintaan ke API. Jadi depedency adalah fungsi dari Int ke Effect<String, ApiError>, di mana String mewakili respons dari permintaan. Selanjutnya, effect akan melakukan tugasnya di latar belakang(background thread) (seperti halnya dengan URLSession), sehingga memerlukan cara untuk menerima effect pada main queue. Kita melakukan ini melalui main queue scheduler, yang merupakan dependency penting untuk dikontrol sehingga kita dapat menulis test. Kita harus menggunakan AnyScheduler sehingga kita dapat menggunakan DispatchQueue langsung dalam production dan test scheduler dalam test.

struct AppEnvironment {
  var mainQueue: AnySchedulerOf<DispatchQueue>
  var numberFact: (Int) -> Effect<String, ApiError>
}

Selanjutnya, kita mengimplementasikan reducer yang mengimplementasikan logika untuk domain ini. Ini menjelaskan bagaimana mengubah state saat ini ke state berikutnya, dan menjelaskan effect apa yang perlu dijalankan. Beberapa action tidak perlu menjalankan effect, dan mereka dapat mengembalikan .none untuk menyatakan bahwa:

let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in
  switch action {
  case .factAlertDismissed:
    state.numberFactAlert = nil
    return .none

  case .decrementButtonTapped:
    state.count -= 1
    return .none

  case .incrementButtonTapped:
    state.count += 1
    return .none

  case .numberFactButtonTapped:
    return environment.numberFact(state.count)
      .receive(on: environment.mainQueue)
      .catchToEffect()
      .map(AppAction.numberFactResponse)

  case let .numberFactResponse(.success(fact)):
    state.numberFactAlert = fact
    return .none

  case .numberFactResponse(.failure):
    state.numberFactAlert = "Could not load a number fact :("
    return .none
  }
}

Dan akhirnya kita mendefinisikan tampilan untuk fitur tersebut. Ini disimpan di Store<AppState, AppAction> sehingga kita dapat mengamati semua perubahan pada status dan merender ulang, dan kita dapat mengirim semua action pengguna ke store sehingga state dapat berubah. Kita juga harus memperkenalkan pembungkus struct di sekitar alert fakta untuk membuatnya Identifiable, yang dibutuhkan oleh pengubah tampilan .alert:

struct AppView: View {
  let store: Store<AppState, AppAction>

  var body: some View {
    WithViewStore(self.store) { viewStore in
      VStack {
        HStack {
          Button("") { viewStore.send(.decrementButtonTapped) }
          Text("\(viewStore.count)")
          Button("+") { viewStore.send(.incrementButtonTapped) }
        }

        Button("Number fact") { viewStore.send(.numberFactButtonTapped) }
      }
      .alert(
        item: viewStore.binding(
          get: { $0.numberFactAlert.map(FactAlert.init(title:)) },
          send: .factAlertDismissed
        ),
        content: { Alert(title: Text($0.title)) }
      )
    }
  }
}

struct FactAlert: Identifiable {
  var title: String
  var id: String { self.title }
}

Penting untuk dicatat bahwa kita dapat menerapkan seluruh fitur ini tanpa memiliki effect langsung seperti pemanggilan ke server. Ini penting karena berarti fitur dapat dibangun secara terpisah tanpa perlu dependency production, yang dapat membantu mengurangi waktu kompilasi.

dan juga mudah untuk memiliki pengontrol UIKit yang dikeluarkan dari store ini. Anda berlangganan store di viewDidLoad untuk memperbarui UI dan menampilkan alert. Kode ini sedikit lebih panjang dari versi SwiftUI, jadi kami telah menciutkannya di sini:

Click to expand!
class AppViewController: UIViewController {
  let viewStore: ViewStore<AppState, AppAction>
  var cancellables: Set<AnyCancellable> = []

  init(store: Store<AppState, AppAction>) {
    self.viewStore = ViewStore(store)
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let countLabel = UILabel()
    let incrementButton = UIButton()
    let decrementButton = UIButton()
    let factButton = UIButton()

    // Omitted: Add subviews and set up constraints...

    self.viewStore.publisher
      .map { "\($0.count)" }
      .assign(to: \.text, on: countLabel)
      .store(in: &self.cancellables)

    self.viewStore.publisher.numberFactAlert
      .sink { [weak self] numberFactAlert in
        let alertController = UIAlertController(
          title: numberFactAlert, message: nil, preferredStyle: .alert
        )
        alertController.addAction(
          UIAlertAction(
            title: "Ok",
            style: .default,
            handler: { _ in self?.viewStore.send(.factAlertDismissed) }
          )
        )
        self?.present(alertController, animated: true, completion: nil)
      }
      .store(in: &self.cancellables)
  }

  @objc private func incrementButtonTapped() {
    self.viewStore.send(.incrementButtonTapped)
  }
  @objc private func decrementButtonTapped() {
    self.viewStore.send(.decrementButtonTapped)
  }
  @objc private func factButtonTapped() {
    self.viewStore.send(.numberFactButtonTapped)
  }
}

Setelah kita siap untuk menampilkan tampilan ini, misalnya dalam scene delegate, kita dapat membangun sebuah store. Ini adalah saat di mana kita perlu menyediakan dependency, dan untuk saat ini kita bisa menggunakan effect yang langsung mengembalikan string tiruan:

let appView = AppView(
  store: Store(
    initialState: AppState(),
    reducer: appReducer,
    environment: AppEnvironment(
      mainQueue: .main,
      numberFact: { number in Effect(value: "\(number) is a good number Brent") }
    )
  )
)

Dan itu cukup untuk memunculkan sesuatu di layar untuk dimainkan. Ini pasti beberapa langkah lebih banyak daripada jika Anda melakukan ini dengan cara vanilla SwiftUI, tetapi ada beberapa manfaatnya. Ini memberi kita cara yang konsisten untuk menerapkan mutasi state, daripada menyebarkan logika di beberapa objek yang dapat diamati dan dalam berbagai action closures komponen UI. Ini juga memberi kita cara singkat untuk mengungkapkan side effect. Dan kita dapat langsung menguji logika ini, termasuk efeknya, tanpa melakukan banyak pekerjaan tambahan.

Testing

Untuk menguji, Anda terlebih dahulu membuat TestStore dengan informasi yang sama dengan yang Anda buat untuk membuat Store biasa, kecuali kali ini kita dapat menyediakan dependency yang cocok untuk pengujian. Kita menggunakan test scheduler daripada langsung menggunakan DispatchQueue.main karena itu memungkinkan kita untuk mengontrol kapan pekerjaan dijalankan, dan kita tidak perlu menunggu antrean untuk menyusul.

let scheduler = DispatchQueue.test

let store = TestStore(
  initialState: AppState(),
  reducer: appReducer,
  environment: AppEnvironment(
    mainQueue: scheduler.eraseToAnyScheduler(),
    numberFact: { number in Effect(value: "\(number) is a good number Brent") }
  )
)

Setelah store pengujian dibuat, kita dapat menggunakannya untuk membuat pernyataan tentang seluruh alur langkah pengguna. Setiap langkah kita perlu membuktikan bahwa keadaan mengubah apa yang kita harapkan. Selanjutnya, jika suatu langkah menyebabkan effect dieksekusi, yang memasukkan data kembali ke penyimpanan, kita harus menegaskan bahwa tindakan tersebut diterima dengan benar.

Pengujian di bawah ini membuat pengguna menambah dan mengurangi jumlah, lalu mereka meminta fakta angka, dan respons dari effect itu memicu alert untuk ditampilkan, dan kemudian mengabaikan alert menyebabkan peringatan itu hilang.

// Test that tapping on the increment/decrement buttons changes the count
store.send(.incrementButtonTapped) {
  $0.count = 1
}
store.send(.decrementButtonTapped) {
  $0.count = 0
}

// Test that tapping the fact button causes us to receive a response from the effect. Note
// that we have to advance the scheduler because we used `.receive(on:)` in the reducer.
store.send(.numberFactButtonTapped)

scheduler.advance()
store.receive(.numberFactResponse(.success("0 is a good number Brent"))) {
  $0.numberFactAlert = "0 is a good number Brent"
}

// And finally dismiss the alert
store.send(.factAlertDismissed) {
  $0.numberFactAlert = nil
}

Itulah dasar-dasar membangun dan menguji fitur dalam Composable Architecture. Ada lebih banyak hal yang harus dieksplorasi, seperti komposisi, modularitas, kemampuan beradaptasi, dan efek kompleks. Direktori Examples memiliki banyak proyek untuk dijelajahi untuk melihat penggunaan lebih lanjut.

Debugging

Composable Architecture dilengkapi dengan sejumlah tool untuk membantu dalam debugging.

  • reducer.debug() menyempurnakan reducer dengan menampilkan debug yang menjelaskan untuk setiap tindakan yang diterima reducer dan setiap mutasi yang dibuatnya.

    received action:
      AppAction.todoCheckboxTapped(
        index: 0
      )
      AppState(
        todos: [
          Todo(
    -       isComplete: false,
    +       isComplete: true,
            description: "Milk",
            id: 5834811A-83B4-4E5E-BCD3-8A38F6BDCA90
          ),
          Todo(
            isComplete: false,
            description: "Eggs",
            id: AB3C7921-8262-4412-AA93-9DC5575C1107
          ),
          Todo(
            isComplete: true,
            description: "Hand Soap",
            id: 06E94D88-D726-42EF-BA8B-7B4478179D19
          ),
        ]
      )
  • reducer.signpost() menggunakan reducer dengan signposts sehingga Anda dapat memperoleh informasi tentang berapa lama tindakan yang diperlukan untuk dieksekusi, dan kapan efek berjalan.

Library Tambahan

Salah satu prinsip terpenting dari Composable Architecture adalah bahwa efek samping tidak pernah dilakukan secara langsung, melainkan dibungkus dalam tipe Effect, dikembalikan dari reduksi, dan kemudian Store melakukan efeknya. Ini sangat penting untuk menyederhanakan bagaimana data mengalir melalui aplikasi, dan untuk mendapatkan testabilitas pada siklus tindakan pengguna end-to-end penuh untuk mempengaruhi eksekusi.

Namun, ini juga berarti bahwa banyak library dan SDK yang berinteraksi dengan Anda setiap hari perlu dipasang kembali agar sedikit lebih ramah dengan gaya Composable Architecture. Itulah mengapa kami ingin meringankan kesulitan menggunakan beberapa framework paling populer Apple dengan menyediakan library pembungkus yang mengekspos fungsionalitasnya dengan cara yang sesuai dengan library kami. Sejauh ini kami mendukung:

  • ComposableCoreLocation: Pembungkus untuk CLLocationManager yang membuatnya mudah digunakan dari reducer, dan mudah menulis tes tentang bagaimana logika Anda berinteraksi dengan fungsionalitas CLLocationManager.
  • ComposableCoreMotion: Pembungkus untuk CMMotionManager yang membuatnya mudah digunakan dari reduver, dan mudah menulis tes tentang bagaimana logika Anda berinteraksi dengan fungsionalitas CMMotionManager.
  • Lebih banyak lagi akan segera hadir.

Jika Anda tertarik untuk berkontribusi dalam library pembungkus untuk framework yang belum kami bahas, jangan ragu untuk membuka issue yang menyatakan minat Anda sehingga kami dapat mendiskusikannya.

FAQ

  • Bagaimana Composable Architecture dibandingkan dengan Elm, Redux, dan lainnya?

    klik untuk melihat jawaban Composable Architecture (TCA) dibangun di atas fondasi ide yang dipopulerkan oleh Elm Architecture (TEA) dan Redux, tetapi dibuat agar terasa seperti di rumah sendiri dalam bahasa Swift dan pada platform Apple.

    Dalam beberapa hal TCA sedikit lebih berpendirian daripada library lain. Misalnya, Redux tidak menentukan cara seseorang mengeksekusi efek samping, tetapi TCA mengharuskan semua efek samping dimodelkan dalam tipe Effect dan dikembalikan dari reducer.

    Dengan cara lain TCA sedikit lebih longgar daripada library lain. Misalnya, Elm mengontrol jenis efek apa yang dapat dibuat melalui jenis Cmd, tetapi TCA memungkinkan untuk efek apa pun karena Effect sesuai dengan protokol Combine Publisher.

    Dan kemudian ada hal-hal tertentu yang sangat diprioritaskan TCA yang bukan merupakan titik fokus untuk Redux, Elm, atau sebagian besar library lainnya. Misalnya, komposisi adalah aspek yang sangat penting dari TCA, yaitu proses memecah fitur besar menjadi unit yang lebih kecil yang dapat direkatkan. Ini dicapai dengan operator pullback dan combine pada reducer, dan ini membantu dalam menangani fitur kompleks serta modularisasi untuk basis kode yang lebih terisolasi dan waktu kompilasi yang lebih baik.

  • Mengapa Store tidak thread-safe?
    Mengapa send tidak diantrekan?
    Mengapa send tidak dijalankan di main thread?

    klik untuk melihat jawaban

    Semua interaksi dengan instance Store (termasuk semua cakupannya dan turunan ViewStores) harus dilakukan pada thread yang sama. Jika store menjalankan tampilan SwiftUI atau UIKit, semua interaksi harus dilakukan pada thread main.

    Saat action dikirim ke Store, reducer dijalankan pada state saat ini, dan proses ini tidak dapat dilakukan dari beberapa thread. Solusi yang mungkin dilakukan adalah menggunakan antrian dalam implementasi send, tetapi ini menimbulkan beberapa komplikasi baru:

    1. Jika dilakukan hanya dengan DispatchQueue.main.async, Anda akan mengalami lompatan thread meskipun Anda sudah berada di thread utama. Ini dapat menyebabkan perilaku tak terduga di UIKit dan SwiftUI, di mana terkadang Anda diharuskan melakukan pekerjaan secara sinkron, seperti di blok animasi.

    2. Dimungkinkan untuk membuat penjadwal yang melakukan pekerjaannya segera saat berada di utas utama dan jika tidak menggunakan DispatchQueue.main.async (e.g. lihat [UIScheduler] ReactiveSwift (https://github.com /ReactiveCocoa/ReactiveSwift/blob/f97db218c0236b0c6ef74d32adb3d578792969c0/Sources/Scheduler.swift)). Ini memperkenalkan lebih banyak kompleksitas, dan mungkin tidak perlu diadopsi tanpa alasan yang sangat bagus.

    Inilah sebabnya mengapa kami mengharuskan semua tindakan dikirim dari thread yang sama. Persyaratan ini memiliki semangat yang sama dengan bagaimana URLSession dan API Apple lainnya dirancang. API tersebut cenderung mengirimkan outputnya pada thread apa pun yang paling nyaman bagi mereka, dan Anda bertanggung jawab untuk mengirim kembali ke antrean utama jika itu yang Anda butuhkan. Composable Architecture membuat Anda bertanggung jawab untuk memastikan mengirim tindakan pada thread utama. Jika Anda menggunakan efek yang dapat mengirimkan outputnya pada thread non-utama, Anda harus secara eksplisit menjalankan .receive(on:) untuk memaksanya kembali ke thread utama.

    Pendekatan ini membuat asumsi paling sedikit tentang bagaimana efek dibuat dan ditransformasikan, dan mencegah lompatan thread yang tidak perlu dan pengiriman ulang. Ini juga memberikan beberapa manfaat pengujian. Jika efek Anda tidak bertanggung jawab atas penjadwalannya sendiri, maka dalam pengujian semua efek akan berjalan secara sinkron dan segera. Anda tidak akan dapat menguji bagaimana beberapa efek dalam penerbangan saling terkait satu sama lain dan memengaruhi state aplikasi Anda. Namun, dengan meninggalkan penjadwalan keluar dari Store, kita dapat menguji aspek-aspek efek ini jika kita menginginkannya, atau kita dapat mengabaikannya jika kita menginginkannya. Kita memiliki fleksibilitas itu.

    Namun, jika Anda masih bukan penggemar pilihan kami, jangan pernah takut! Composable Architecture cukup fleksibel untuk memungkinkan Anda memperkenalkan fungsi ini sendiri jika Anda menginginkannya. Dimungkinkan untuk membuat reducer yang dapat memaksa semua efek untuk mengirimkan outputnya pada utas utama, di mana pun efeknya bekerja:

    extension Reducer {
      func receive<S: Scheduler>(on scheduler: S) -> Self {
        Self { state, action, environment in
          self(&state, action, environment)
            .receive(on: scheduler)
            .eraseToEffect()
        }
      }
    }

    Anda mungkin masih menginginkan sesuatu seperti UIScheduler sehingga Anda tidak perlu melakukan lompatan thread.

Persyaratan

Composable Architectur bergantung pada framework Combine, sehingga memerlukan target penerapan minimum iOS 13, macOS 10.15, Mac Catalyst 13, tvOS 13, dan watchOS 6. Jika aplikasi Anda harus mendukung OS yang lebih lama, ada fork untuk [ReactiveSwift](https ://github.com/trading-point/reactiveswift-composable-architecture) dan RxSwift yang dapat Anda adopsi!

Cara Install

Anda dapat menambahkan ComposableArchitecture ke proyek Xcode dengan menambahkannya sebagai package dependency.

  1. Dari menu File, pilih Swift Packages Add Package Dependency…
  2. Masukkan "https://github.com/pointfreeco/swift-composable-architecture" ke dalam teks URL repositori paket
  3. Bergantung pada bagaimana proyek Anda terstruktur:
    • Jika Anda memiliki satu target aplikasi yang memerlukan akses ke perpustakaan, tambahkan ComposableArchitecture langsung ke aplikasi Anda.
    • Jika Anda ingin menggunakan library ini dari beberapa target Xcode, atau menggabungkan target Xcode dan target SPM, Anda harus membuat shared framework yang bergantung pada ComposableArchitecture dan kemudian bergantung pada framework tersebut di semua target Anda. Sebagai contoh, lihat aplikasi demo Tic-Tac-Toe, yang membagi banyak fitur menjadi modul dan menggunakan static library dengan cara ini menggunakan framework TicTacToeCommon.

Dokumentasi

Dokumentasi terbaru untuk Composable Architecture API tersedia di sini.

Bantuan

Jika Anda ingin mendiskusikan Composable Architecture atau memiliki pertanyaan tentang cara menggunakannya untuk memecahkan masalah tertentu, Anda dapat memulai topik di diskusi dari repo ini, atau tanyakan di forum Swift-nya.

Terjemahan

  • Terjemahan bahasa Korea dari README ini tersedia di sini.
  • Terjemahan bahasa Indonesia dari README ini tersedia di sini.

Jika Anda ingin menyumbangkan terjemahan, silakan buka PR dengan tautan ke Gist!

Credits dan Terima Kasih

Orang-orang berikut memberikan umpan balik tentang library pada tahap awal dan membantu menjadikan library seperti sekarang ini:

Paul Colton, Kaan Dedeoglu, Matt Diephouse, Josef Doležal, Eimantas, Matthew Johnson, George Kaimakas, Nikita Leonov, Christopher Liscio, Jeffrey Macko, Alejandro Martinez, Shai Mishali, Willis Plummer, Simon-Pierre Roy, Justin Price, Sven A. Schmidt , Kyle Sherman, Petr íma, Jasdev Singh, Maxim Smirnov, Ryan Stone, Daniel Hollis Tavares, dan semua pelanggan Point-Free 😁.

Terima kasih khusus kepada Chris Liscio yang membantu kami mengatasi banyak kebiasaan aneh SwiftUI dan membantu menyempurnakan API final.

Dan terima kasih kepada Shai Mishali dan proyek CombineCommunity, yang darinya kami menerapkan Publishers.Create , yang kami gunakan di Effect untuk membantu menjembatani delegasi dan API berbasis panggilan balik, membuatnya lebih mudah untuk berinteraksi dengan kerangka kerja pihak ketiga.

Library Lainnya

Composable Architecture dibangun di atas fondasi ide yang dimulai oleh perpustakaan lain, khususnya Elm dan Redux.

Ada juga banyak perpustakaan arsitektur di komunitas Swift dan iOS. Masing-masing dari ini memiliki serangkaian prioritas dan trade-off yang berbeda dari Composable Architecture.

License

library ini dirilis di bawah lisensi MIT. Lihat LISENSI untuk detailnya.

@andreyyoshua
Copy link

Cool!

@ferico55
Copy link

Awesome Wen!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment