Skip to content

TestFlight builds can crash unless Swift Language Version is set to Swift 6 #3604

@AndrewBardallis

Description

@AndrewBardallis

Description

We have a very large, modularized project that has been using more and more TCA over the years. For context, a cursory search returns about 97 different modules that list TCA as a dependency. Recently, we attempted to update from TCA 1.12.1 to TCA 1.17.1 but needed to roll back the update when we saw crashes spike in our Beta Testing Group for just two of our many TCA features.

In both cases, the trace will have only two lines on the thread that crashes:

EXC_BAD_ACCESS   Attempted to deference garbage pointer 0x3e4234f7.

Thread 15
0    libswiftCore.dylib        _swift_getObjectType
1    App                       closure #1 in FeatureClient.init(service:)

We have also noticed the following peculiar characteristics of this issue:

  • The crash ONLY occurs on TestFlight builds (signed with a Distribution Cert) installed on physical devices.
  • It does NOT occur on Release builds signed with a Distribution Cert on simulators.
  • It does NOT occur on Release builds signed with a Development Cert on physical devices or simulators.
  • It does NOT occur on Debug builds signed with a Development Cert on physical devices or simulators.

As strange as it is, multiple developers have double checked that list with 100% reproducibility. This appears to be only an issue in builds created for TestFlight/App Store when ran on physical devices.

Dependency management

We manage our dependencies using uber/needle and this has worked well for our previous architecture as well as TCA. We use a Builder that has access to our feature's dependencies from Needle that's responsible for initializing the SwiftUI/UIKit+TCA stack.

For instance, let's assume our FeatureNeedleComponent is set up with some service dependency to make requests to the backend:

class FeatureBuilder {
    private let component: FeatureNeedleComponent
    
    init(component: FeatureNeedleComponent) {
        self.component = component
    }

    func build() -> FeatureView {
        FeatureView(store: Store(initialState: .init()) {
            FeatureReducer(
                client: FeatureClient(service: component.service)
            )
        }
    }
}


@Reducer
struct FeatureReducer {
    // State
    // Action
    
    let client: FeatureClient
    
    var body: some ReducerOf<Self> {
        Reduce { state, action in 
            switch action {
            case .onAppear:
                return .run { send in 
                    let response = try await client.getWidgets()
                    await send(.getWidgetsResult(.success(response)))
                } catch: {...}
            ...
            }
        }
    }
}

struct FeatureClient {
    let getWidgets: () async throws -> [Widget]
}

extension FeatureClient {
    init(service: FeatureService) {
        self.getWidgets = {
            try await service.getWidgets()
        }
    }
}

In this pseudo code setup, we would see the crash as soon as try await service.getWidgets() is called.

The client itself can be called (i.e. if we were to replace the closure with just self.getWidgets = {[]}, but if the service that's passed in is accessed, it will crash.

What fixes this?

We have thus far found 2 ways to fix this (and I use "fix" loosely).

  1. Undo the change that introduced this issue in swift-composable-architecture.
    We did a manual bisect and isolated the issue to commit cad094a. The commit prior to this one does not exhibit the crash. I then set up our project with this commit of swift-composable-architecture as a local dependency and began reverting changes. I reverted the change in Effect.swift as well as the change in the .map {} of each of file in Reducer/Reducers (ForEachReducer.swift, IfCaseLetReducer.swift, IfLetReducer.swift, PresentationReducer.swift, Scope.swift, StackReducer.swift). I did not change anything in Internal/, TestStore.swift or ViewStore.swift. After pushing to a Draft PR and getting a build through CI that was signed for Distribution, we found the crash was no longer occurring. We all collectively shrugged and wondered if the fact that self was no longer being captured by .map {} had anything to do with it.

  2. Set the module to use "SWIFT_VERSION": "6.0".
    With the swift-composable-architecture dependency set back to a remote version that exhibits the issue, we updated the language version of one of our two problematic modules to Swift 6.0. We made the necessary changes for that to compile and retested. We no longer saw a crash in that feature and continued to see a crash in the feature that remained on Swift 5.0.

Now what?

We're a little stuck. We want to update to the latest version of TCA to take advantage of all the great features it has to offer. While it's very encouraging that we can update the language version to Swift 6.0 and see the issue resolved, this isn't a fully viable solution in the near term for a couple reasons:

  • We are still completely in the dark on why these 2 out of 97 modules exhibited the crash and the others did not. Even if we update these 2 to use Swift 6.0, how will we release beyond Beta with a high level of confidence that this issue will not show up somewhere else that we just haven't discovered yet? Beta is great, but it's not a catch all. The not-so-easy answer is...
  • Update all our modules to Swift 6.0. While this is certainly something we're working on, this is a massive undertaking at our scale, especially considering that we also need to balance this with demands coming from the business.

Checklist

This issue hasn't been addressed in an existing GitHub issue or discussion.

It kinda smells like this discussion, but we're not using @Dependencies, the stack trace is different enough, and that discussion lacked all the details needed to determine if this was likely to be the same issue. Tried to do our diligence here, but this felt like it warranted a new post.

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

No response

Actual behavior

No response

Reproducing project

No response

The Composable Architecture version information

cad094a

Destination operating system

iOS 16.0+ (most testing done on 18.2)

Xcode version information

16.2 (16C5032a)

Swift Compiler version information

swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
Target: arm64-apple-macosx15.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    apple bugSomething isn't working due to a bug on Apple's platforms.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions