Closure with typed throws stored as a SwiftUI View property crashes on iOS 17

Not sure if this is a SwiftUI bug or a Swift bug so I'm posting this also into the Swift forum.

I've encountered an issue where storing a throws(PermissionError) closure as a property inside a SwiftUI View causes a runtime crash with EXC_BAD_ACCESS on iOS 17, while it works correctly on iOS 18.

Here’s an example of the affected code:

enum PermissionError: Error {
  case denied
}

struct PermissionCheckedView<AllowedContent: View, DeniedContent: View>: View {
  var protectedView: () throws(PermissionError) -> AllowedContent
  var deniedView: (PermissionError) -> DeniedContent

  init(
    @ViewBuilder protectedView: @escaping () throws(PermissionError) -> AllowedContent,
    @ViewBuilder deniedView: @escaping (PermissionError) -> DeniedContent
  ) {
    self.protectedView = protectedView
    self.deniedView = deniedView
  }

  public var body: some View {
    switch Result(catching: protectedView) {
    case .success(let content): content
    case .failure(let error): deniedView(error)
    }
  }
}

@main
struct TestApp: App {
  var body: some Scene {
    WindowGroup {
      PermissionCheckedView {
        
      } deniedView: { _ in
        
      }
    }
  }
}

Here's the stack trace (not sure how to get the txt version so posting image).
Since I noticed some func like swift_getTypeByMangledNameInContextImpl in the stack trace I thought that this could be a Swift bug

If I use var protectedView: () throws -> AllowedContent without typed throws it works.

1 Like

Typed throws is new in the Swift 6 compiler and iOS 18, so I'm guessing the demangling used by SwiftUI on iOS 17 doesn't know how to handle it. I think typed throws is back deployable, but only if you don't rely on implementation details like SwiftUI. I'm not sure if the language can fix this, but hopefully there's something Apple can do.

In general I can use typed throws on iOS 17 as long as I don't store closures in views. I suppose there's nothing we can do if Apple doesn't tackle this

1 Like

@Danny I can actually change my code to something like this and perform the do catch in the init to avoid storing it as a closure:

struct PermissionCheckedView<AllowedContent: View, DeniedContent: View>: View {
  private enum Content {
    case allowed(AllowedContent)
    case denied(DeniedContent)
  }

  private var content: Content

  init(
    @ViewBuilder protectedView: () throws(PermissionError) -> AllowedContent,
    @ViewBuilder deniedView: (PermissionError) -> DeniedContent
  ) {
    do throws(PermissionError) {
      content = try .allowed(protectedView())
    } catch {
      content = .denied(deniedView(error))
    }
  }

  public var body: some View {
    switch content {
    case .allowed(let content): content
    case .denied(let content): content
    }
  }
}

But my question was more generic as I couldn't understand the reason of the crash

1 Like

SwiftUI (am grouping SwiftUI, SwiftUICore and AttributeGraph together here) relies heavily on runtime reflection internally.

My guess is that SwiftUI's internal runtime reflection code was written relative to the Swift runtime ABI interface before typed throws was implemented. Typed throws the language feature backdeploys, yes, and implementation is (I could be wrong) purely additive from what I understand.

That being said, if you write code that relies on an exhaustive understanding of all possible runtime structures (I imagine SwiftUI's internals do this), you're bound to either make assumptions or implement code-paths that exit-early with respect to new, 'foreign' metadata.

I'm guessing that somewhere in the implementation there are areas that trip up on the runtime changes (even if they're purely additive in the technical sense) introduced with the implementation of typed throws as a new language feature.

2 Likes