Git Product home page Git Product logo

Comments (4)

shoumikhin avatar shoumikhin commented on September 1, 2024 4

Thank you for reporting that @ingun37 !

The root of the issue goes deeply in Swfit-ObjC interoperability, which has special handling for hashable containers (Set and Dictionary). Feel free to play with the attached example: SwiftObjCCasting.zip

Briefly, if we have the following code in ObjC:

@implementation ObjC
- (nullable id)cast:(nullable id)object {
  return object;
}
@end

When we pass a Swift object in such a method and try to restore the type for the returned value, the Swift dynamic cast will crash in case the object is a hashable container with object of custom Hashable (non-builtin and non-NSObject) types in it:

class TestClass: Hashable {
  init(_ string: String) {
    self.string = string
  }
  var hashValue: Int { return string.hashValue }
  static func == (lhs: TestClass, rhs: TestClass) -> Bool {
    return lhs.string == rhs.string
  }
  private let string: String
}

let input = Set([TestClass("a")])
let output = ObjC().cast(input)
let value = output as? Set<TestClass>  // Run-time crash!

Or the same in case of a Dictionary:

let input = [TestClass("a"): "a"]
let output = ObjC().cast(input)
let value = output as? Dictionary<TestClass, String>  // Run-time crash!

Since Promises for Swift rely on ObjC implementation for compatibility reasons, any value a promise gets resolved with goes through conversion to nullable id and then back to its original Swift type. That's what you see in your original crash stack trace: some promise got resolved with Swift value of hashable container type and tries to notify subscribers about that. The control leaves ObjC core logic and enters Swift wrapper, where the original Swift value is initially passed as Any?, which we try to cast back to the original type in asValue helper func.

Generally, the issue may be perceived as a flaw in Swift-ObjC interoperability and unfortunately, we don't currently have a proper fix or a good workaround for it, other than not using Promises with hashable containers holding custom types (any NSObjects are fine, though), which is obviously less than ideal.

A direction to explore would be something like:

let output = ObjC().cast(input) as AnyObject
if let keys = output.allKeys as? [AnyHashable], let values = output.allObjects {
  let value =  Dictionary(uniqueKeysWithValues: zip(keys, values)) as? Type
} else if let values = output.allObjects as? [AnyHashable] {
  let value = Set(values) as? Type
} else {
  let value = output as? Type
}

Where Type is the original type of input. But that approach is quite suboptimal due to the copy overhead.

Anyhow, I hope the above sheds some light on the root of the problem. We're open for any suggestions and continue searching for a real fix w/o modifying the compiler, of course.

from promises.

ingun37 avatar ingun37 commented on September 1, 2024

I tested inheriting NSObject and it worked fine. I hope theres other solution because I wouldn't like the idea of inheriting unnecessary class.
Thank you

from promises.

shoumikhin avatar shoumikhin commented on September 1, 2024

Hi @ingun37, could you provide more context, please?
A small code snippet which reproduces the issue for you would be much appreciated!
Also, Xcode and Swift version may be useful.
Thanks!

from promises.

ingun37 avatar ingun37 commented on September 1, 2024

I found a solution
I changed Promise<Set<MyClass>> to Promise<[MyClass]> and it works fine now
Im satisfied with the solution but I hv made this reproducing unit test code anyway because I would like to contribute as much as I love this library.

Xcode version: 9.4.1
Swift version: 4.1

class MyClass: Hashable, Decodable {
    let s:String
    init(_ s:String) {
        self.s = s
    }
    var hashValue: Int {return s.hashValue}
    static func == (lhs: apiTests.MyClass, rhs: apiTests.MyClass) -> Bool {
        return lhs.s == rhs.s
    }
}
    
func testFail() {
    let ex = XCTestExpectation(description: "test")
    Promise { Set([MyClass("a"), MyClass("b")]) }.then { (a)-> Promise<Set<MyClass>> in
        Promise {a}
    }.then {
        print($0)
        ex.fulfill()
    }
    wait(for: [ex], timeout: 10.0)
}
func testSuccess() {
    let ex = XCTestExpectation(description: "test")
    Promise { Set([MyClass("a"), MyClass("b")]) }.then {
        print($0)
        ex.fulfill()
    }
    wait(for: [ex], timeout: 10.0)
}

It doesnt break at the exactly same point but I think its the same bug
Thank you

from promises.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.