Failed to demangle witness error when using Realm in a custom Publisher

This question actually crosses over between the RW Combine and Realm books but, as they share some authors I’m hoping this won’t cause a problem.

I’m attempting to test a custom Publisher that emits Realm object arrays associated with a given query. The code works as expected in a playground and inside the app. The problem arises when I attempt to be good and write a test (as described in the Combine book)… the test case crashes with an error I’ve never seen before relating to mangled witness table names. This is far further into the weeds than I’ve gone before and I’m uncertain if it’s a bug in my code, a Realm/Combine bug or something to do with threading. Any thoughts or guidance on how to resolve this would be very much appreciated as I’ve no idea what to do next!

Code base as follows (the Patient Object is a very simple class but I can provide the code if needed):

Custom publisher

// MARK: Custom publisher - produces a stream of Object arrays in response to change notifcations on a given Realm collection
extension Publishers {
    struct Realm<Collection: RealmCollection>: Publisher {
        typealias Output = Array<Collection.Element>
        typealias Failure = Never // TODO: Not true but deal with this later

        let collection: Collection

        init(collection: Collection) {
            self.collection = collection
        }

        func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            let subscription = RealmSubscription(subscriber: subscriber, collection: collection)
            subscriber.receive(subscription: subscription)
        }
    }
}

// MARK: Convenience accessor function to the custom publisher
extension Publishers {
    static func realm<Collection: RealmCollection>(collection: Collection) -> Publishers.Realm<Collection> {
        return Publishers.Realm(collection: collection)
    }
}

// MARK: Custom subscription
private final class RealmSubscription<S: Subscriber, Collection: RealmCollection>: Subscription where S.Input == Array<Collection.Element> {
    private var subscriber: S?
    private let collection: Collection
    private var notificationToken: NotificationToken?

    init(subscriber: S, collection: Collection) {
        self.subscriber = subscriber
        self.collection = collection

        self.notificationToken = collection.observe { (changes: RealmCollectionChange) in
            switch changes {
            case .initial:
                // Results are now populated and can be accessed without blocking the UI
                print("Initial")
                let _ = subscriber.receive(Array(collection.elements)) // ERROR THROWN HERE 
            //            case .update(_, let deletions, let insertions, let modifications):
            case .update(_, _, _, _):
                print("Updated")
                let _ = subscriber.receive(Array(collection.elements))
            case .error(let error):
                fatalError("\(error)")
                #warning("Impl error handling - do we want to fail or log and recover?")
            }
        }
    }

    func request(_ demand: Subscribers.Demand) {
        // no impl as RealmSubscriber is effectively just a sink
    }

    func cancel() {
        print("Cancel called on RealnSubscription")
        subscriber = nil
        notificationToken = nil
    }
}

Service class

protocol RealmServiceType {
    func all<Element>(_ type: Element.Type, within realm: Realm) -> AnyPublisher<Array<Element>, Never> where Element: Object

    @discardableResult
    func addPatient(_ name: String, to realm: Realm) throws -> AnyPublisher<Patient, Never>

    func deletePatient(_ patient: Patient, from realm: Realm)
}

extension RealmServiceType {
    func all<Element>(_ type: Element.Type) -> AnyPublisher<Array<Element>, Never> where Element: Object {
        print("Called \(#function)")
        return all(type, within: try! Realm())
    }
}

final class TestRealmService: RealmServiceType {
    private let patients = [
        Patient(name: "Tiddles"), Patient(name: "Fang"), Patient(name: "Phoebe"), Patient(name: "Snowy")
    ]

    init() {
        let realm = try! Realm()
        guard realm.isEmpty else { return }
        try! realm.write {
            for p in patients {
                realm.add(p)
            }
        }
    }

    func all<Element>(_ type: Element.Type, within realm: Realm) -> AnyPublisher<Array<Element>, Never> where Element: Object {
        return Publishers.realm(collection: realm.objects(type).sorted(byKeyPath: "name")).eraseToAnyPublisher()
    }


    func addPatient(_ name: String, to realm: Realm) throws -> AnyPublisher<Patient, Never> {
        let patient = Patient(name: name)
        try! realm.write {
            realm.add(patient)
        }
        return Just(patient).eraseToAnyPublisher()
    }

    func deletePatient(_ patient: Patient, from realm: Realm) {
        try! realm.write {
            realm.delete(patient)
        }
    }

}

Test case

class AthenaVSTests: XCTestCase {
    private var cancellables = Set<AnyCancellable>()
    private var service: RealmServiceType?

    override func setUp() {
        service = TestRealmService()
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        service = nil
        cancellables.removeAll()
    }

    func testRealmPublisher() {
        var outcome = [""]
        let expectation = self.expectation(description: #function)
        let expected = ["Tiddles", "Fang", "Phoebe", "Snowy"]

        let _ = service?.all(Patient.self)
            .sink(receiveCompletion: { _ in
                expectation.fulfill() },
                  receiveValue: { value in
                    outcome += value.map { $0.name }
            })
            .store(in: &cancellables)

        waitForExpectations(timeout: 2, handler: nil)

        XCTAssert(outcome == expected, "Expected \(expected) Objects but got \(outcome)")
    }
}

Error message

failed to demangle witness for associated type ‘Iterator’ in conformance ‘RealmSwift.Results: Sequence’ from mangled name ‘10RealmSwift11RLMIteratorVyxG’ 2020-01-13 22:46:07.159964+0000 AthenaVS[3423:171342] failed to demangle witness for associated type ‘Iterator’ in conformance ‘RealmSwift.Results: Sequence’ from mangled name ‘10RealmSwift11RLMIteratorVyxG’

The error is thrown when attempting to execute code in the Realm notification observer within RealmSubscription (I’ve flagged it in the code above), specifically:

let _ = subscriber.receive(Array(collection.elements))

Ideas?

Update on this one…

I was using SwiftPM to handle dependencies (the only one in this case was Realm) and the issue has been isolated to this. I’ve dropped back to using Carthage and the runtime error has disappeared. I’m not sure if this is a SwiftPM bug or down to Realm’s implementation. For now however it’s working OK :slight_smile:

@rustprooffish Glad you fixed this! Cheers! :]

Hey, just wanted to leave a note for future readers. I was facing this warning as well:

warning: the Swift runtime was unable to demangle the type of field 'attachedNotes'. the mangled type name is '        \333'. this field will show up as an empty tuple in Mirrors
2020-12-22 15:57:44.197081-0300 xctest[92143:5416854] warning: the Swift runtime was unable to demangle the type of field 'attachedNotes'. the mangled type name is '        \^B\^]\M-[\^G'. this field will show up as an empty tuple in Mirrors

Followed by a crash within Realm, with a nasty stacktrace.

I was using SPM. Switching to a full cocoapods implementation solved the issue :cry:

Here’s more info about my environment:

  • Swift Package X with RealmSwift as dependency (obv via SPM), contained models A, B, C…
  • X had unit tests to test the models, and everything worked fine.
  • I plugged in X into a framework project (regular xcode proj, not Swift Package), and now when executing tests in this framework, those warnings were thrown, followed by a crash.
  • The properties that were causing that warning all that a type such as List<SomeRealmObject>. Commenting them out also fixed the warning and the crash, but I needed them in place.

Hope this really helps someone out there. In the future I will try to convert this Framework to Swift Package, and add RealmSwift as SPM dependency again to see if the bug remains. Don’t expect me to come back here to report my results though, because if I don’t face issues I probably (and hopefully) will never come back to this page again that I found because of this issue :innocent: