Using TCA on Windows

The Browser Company just released the Windows 11 version of their Arc browser for macOS. @compnerd from TBC shared that they use TCA for decoupling the business logic parts from the UI.

The majority of the business logic is shared code. We are using TCA, and that provides for a natural split between the business logic and UI logic. Article about Swift on Windows - #8 by compnerd

Not knowing TCA I quickly reviewed the product on GitHub and some online videos on Point-Free https://www.pointfree.co and I have the following questions:

  • does TCA replace the Observable for state management or is it a complement to Observable?

  • does TCA depend on Combine?

  • Observable and Combine being only available on Apple platforms (macOS, iOS, etc.), what about TCA on Windows?

  • Are there any examples of TCA demo code under Windows?

Thanks

1 Like

Hi @Datagram, here are some answers to your questions:

It is a replacement, but it uses Swift's observation tools under the hood.

It does currently, but ideally we would disentangle it from Combine in the near future.

@Observable is available on all platforms, not just iOS/macOS/etc. But it is true that Combine is an Apple-only framework, and so TCA proper cannot support until it fully disentangles from Combine, among other things. Browser currently maintains a fork do some of that work on their own timeline.

Not sure, you may want to ask someone at Browser about that.

4 Likes

TCA available for Windows is Forked by BrowserCompany. This is an example of it actually working well, just a little older, based on 1.4.2.

If you want to develop using ObservableState, please use the TCA fork I am creating, 1.4.2 -> 1.8.2 update from the BrowserCompany fork.

If you want to use a real Windows demo of TCA in action, take a look at the sample code I created, and there is also a video on Twitter showing this in action.

5 Likes

Building SimpleCounter fail with the following error in XCTFail.swift:

  2 |

  3 | public struct XCTFailContext: Sendable {

  4 |   @TaskLocal public static var current: Self?

    |                                `- error: external macro implementation type 'SwiftMacros.TaskLocalMacro' could not be found for macro 'TaskLocal()'; plugin for module 'SwiftMacros' not found
  5 |

  6 |   public var file: StaticString


_Concurrency.TaskLocal:2:72: note: 'TaskLocal()' declared here
1 | @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
2 | @attached(accessor) @attached(peer, names: prefixed(`$`)) public macro TaskLocal() = #externalMacro(module: "SwiftMacros", type: "TaskLocalMacro")
  |                                                                        `- note: 'TaskLocal()' declared here
[5/62] Compiling XCTestDynamicOverlay XCTFail.swift

swift --version
compnerd.org Swift version 6.0-dev (LLVM 3417addab6ff197, Swift d677b7c23fd6355)
Target: x86_64-unknown-windows-msvc

Any idea ?

Hmm, I can't think of a solution right away. Please give me some time to debug.

Hi @arasan01

How to make CSQLite available to GRDB?
Is there any additional step besides running the SPM?

I'm writing the necessary procedures for this document. SPM is not easy to use on Windows at the moment, so a few additional steps are required. If you are using vcpkg, you need to provide a path that can link to sqlite3.dll or place it in the same directory as app.exe.

Or you can set up csqlite to build directly with SPM. ( This needs to be forked and remodeled by yourself.)

Thanks @arasan01

calling swift build --pkg-config-path=C:\vcpkg\installed\x64-windows\lib\pkgconfig worked.

setting PKG_CONFIG_PATH didn't worked.

1 Like

@arasan01

I removed the need for the var observables: [ObservationToken] = [].

The new observation framework works quite well with Windows.

I just needed to do

  func updateList(_ listView : WinUI.ListView) {
      withObservationTracking{ [weak self] in
        guard let self else { return }
        listView.items.clear()
        store.players.forEach { listView.items.append("\($0.name): \($0.score)") }
      } onChange: {
            guard let dispatcherQueue = WinAppSDK.DispatcherQueue.getForCurrentThread() else { return }
            let _ = try! dispatcherQueue.tryEnqueue {
                self.updateList(listView)
            }
      }
  }

  func updateOrdering(_ changeOrderingButton: Button) {
      withObservationTracking{ [weak self] in
        guard let self else { return }
        switch store.ordering {
        case .byName:
          changeOrderingButton.content = "Order by name"
        case .byScore:
          changeOrderingButton.content = "Order by score"
        }

      } onChange: {
            guard let dispatcherQueue = WinAppSDK.DispatcherQueue.getForCurrentThread() else { return }
            let _ = try! dispatcherQueue.tryEnqueue {
                self.updateOrdering(changeOrderingButton)
            }
      }
  }

and use

    self.loaded.addHandler { _, _ in
      self.send(.viewAppeared)

      self.updateList(listView)
      self.updateOrdering(changeOrderingButton)
    }

I guess that the Windows code can look quite nice with a couple of helpers and extensions!

1 Like

Hi @arasan01

I'm getting a lot of ComposableArchitectureMacros.exe getting stuck and removal denied during the build process.
Have you faced the same issue?

Windows may refuse to remove XXX.exe if someone else is using it. In this case, we assume that we are developing using VSCode.exe, which calls sourcekit-lsp.exe. The sourcekit-lsp.exe calls ComposableArchitectureMacros.exe for code completion on VSCode. In this case, the rebuild fails because sourcekit-lsp.exe references xxxMacros.exe. I don't think there is a workaround for this in SPM at this time: either restart sourcekit-lsp from VSCode to dereference it, or explicitly drop sourcekit-lsp.exe once.

But is strange that only ComposableArchitectureMacros.exe gets stuck by the sourcekit.