braze-inc / braze-swift-sdk

Braze SDK for the Apple ecosystem, including: iOS, macOS, iPadOS, visionOS, tvOS
https://www.braze.com
Other
50 stars 19 forks source link

[Bug]: Content card - ContentCard.Data.extras.getter #58

Closed Oaseru17 closed 1 year ago

Oaseru17 commented 1 year ago

Platform

iOS

Platform Version

iOS 16

Braze SDK Version

5.11.1

Xcode Version

14.1

Computer Processor

Apple (M1)

Repro Rate

50%

Steps To Reproduce

Example:

  1. Implement a protocol on Braze ContentCard.
    
    public protocol ContentCard {
    var extras: [AnyHashable: Any]? { get }
    }

2. Get extra in an Interval block. 

### Expected Behavior

Extra is always available

### Actual Incorrect Behavior

Crash occurs

### Verbose Logs

```shell
Crashed: com.apple.root.user-initiated-qos.cooperative
0  libswiftCore.dylib             0xad274 static _DictionaryStorage.copy(original:) + 32
1  App                            0x151df10 specialized _NativeDictionary.mapValues<A>(_:) + 4320108304
2  App                            0x151ad28 Braze.ContentCard.Data.extras.getter + 4320095528
3  App                            0xe2ef0c Braze.ContentCard... + 4312837900
4  App                            0xe328c4 ContentCardsService...(_:) + 4312852676
5  App                            0xe31078 ContentCardsService...+ 4312846456
6  libswift_Concurrency.dylib     0x2e700 swift::runJobInEstablishedExecutorContext(swift::Job*) + 336
7  libswift_Concurrency.dylib     0x2f408 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 68
8  libdispatch.dylib              0x48d48 _dispatch_root_queue_drain + 328
9  libdispatch.dylib              0x49514 _dispatch_worker_thread2 + 160
10 libsystem_pthread.dylib        0x1b14 _pthread_wqthread + 224
11 libsystem_pthread.dylib        0x167c start_wqthread + 8

Additional Information

No response

lowip commented 1 year ago

Hi @Oaseru17,

This is an interesting bug that seems specific to your Xcode version. In Xcode 14.3 the compiler actually reports a compile time error:

public protocol ContentCard {
  var extras: [AnyHashable: Any]? { get }
}

extension Braze.ContentCard: ContentCard {}

ERROR: Type 'Braze.ContentCard' does not conform to protocol 'ContentCard'

Explanation

So foremost, Braze.ContentCard extras property is a non-optional [String: Any]. The type mismatch may prevent the compiler to recognize the Braze.ContentCard.extras property.

Unfortunately, changing the protocol to adopt a matching type is not enough and produces the same error:

public protocol ContentCard {
  var extras: [String: Any] { get }
}

extension Braze.ContentCard: ContentCard {}

ERROR: Type 'Braze.ContentCard' does not conform to protocol 'ContentCard'

This doesn't work because extras and other content cards properties are actually implemented in a Braze.ContentCard.Data struct and not on the Braze.ContentCard itself.

The canonical way of accessing the extras property on a content card is:

card.data.extras

The syntax card.extras is provided as a shortcut via the @dynamicMemberLookup Swift attribute (see BrazeContentCardDataLookup)

Workarounds

Thankfully, it's fairly easy to work around that behavior and implement the ContentCard protocol. The solution works via implementing a default implementation of the ContentCard protocol restricted to the Braze.ContentCard type.

Type [String: Any]

public protocol ContentCard {
  var extras: [String: Any] { get }
}

// `ContentCard` default implementation restricted to the `Braze.ContentCard` type
extension ContentCard where Self == Braze.ContentCard {

  // We use the canonical syntax to access the `extras`
  public var extras: [String: Any] { self.data.extras }

}

extension Braze.ContentCard: ContentCard {}

Type [AnyHashable: Any]?

As [String: Any] is directly convertible to [AnyHashable: Any]?, updating the types is enough to get in compile order:

public protocol ContentCard {
  var extras: [AnyHashable: Any]? { get }
}

// `ContentCard` default implementation restricted to the `Braze.ContentCard` type
extension ContentCard where Self == Braze.ContentCard {

  // We use the canonical syntax to access the `extras`
  public var extras: [AnyHashable: Any]? { self.data.extras }

}

extension Braze.ContentCard: ContentCard {}

Please let me know if that resolves your issue.

Oaseru17 commented 1 year ago

Hi @lowip I must apologise, as I gave snippets that resulted in this confusion

extension Braze.ContentCard: ContentCard {
  public var extras: [AnyHashable: Any]? {
        data.extras
    }
}

This is was the implementation looks like on my end, the above solves the compile time error. And the bug I highlighted above is experienced

Oaseru17 commented 1 year ago

Hello @lowip

Is this bug fixed now? As I explained in my last comment, The snippet I gave when submitting this report didn't include my implementation for the extension. I went on to give the implementation I used. And highlighted that the crash above is still being reported.

lowip commented 1 year ago

Hi @Oaseru17,

Sorry I misinterpreted your previous answer. I'm reopening the issue.

Could you provide more information about Get extra in an Interval block? Any sample code would help. Also, do you have stack traces for other threads when the crash occurs?

Oaseru17 commented 1 year ago

crash_stacktrace.txt

Here is the stack trace as provide by Crashlytics.

lowip commented 1 year ago

Hi @Oaseru17,

We've been trying to reproduce this crash, but are currently unsuccessful.

Could you provide more information about Get extra in an Interval block?

^

Are there any extra information you could share about your setup? I assume you should be able to reproduce it fairly easily with a 50% repro rate.

hokstuff commented 1 year ago

Hi @Oaseru17,

Can you contact support@braze.com with more information and link this Github thread for us to investigate this issue, including:

Thanks!