Forum Xamarin.iOS

Objective sharpie generation from dynamic swift library (.framework) causes NSUnknownKeyException

fjaeger08fjaeger08 USMember ✭✭
edited March 20 in Xamarin.iOS

Hi, I'm trying to generate a xamarin.iOS Binding Library which exposes a dynamic swift library (.framework).
I did follow the tutorial using _objective sharpie tool _and the c# wrapper is well generated.
The SDK is working fine, but I have some _callbacks _in my application which are well called, but some of them are passing me references **to complex objects who inherit from NSObject.
I know the objects composition (it's a swift structure) so I should be able to access my objects properties using > obj.ValueForKey("propertyName"), but every time this raises a **NSUnknownKeyException
: this class is not key value coding-compliant for the key propertyName.

For exemple here i'm interested into TripPoint object, this is the native library definition :

@class TripPoint;
@class CLLocation;

SWIFT_PROTOCOL("_TtP20DriveKitTripAnalysis12TripListener_")
@protocol TripListener
- (void)tripStartedWithStartMode:(enum StartMode)startMode;
- (void)tripPointWithTripPoint:(TripPoint * _Nonnull)tripPoint;
- (void)tripFinishedWithPost:(PostGeneric * _Nonnull)post response:(PostGenericResponse * _Nonnull)response;
- (void)tripCancelledWithCancelTrip:(enum CancelTrip)cancelTrip;
- (void)tripSavedForRepost;
- (void)beaconDetected;
- (void)significantLocationChangeDetectedWithLocation:(CLLocation * _Nonnull)location;
- (void)sdkStateChangedWithState:(enum State)state;
@end


SWIFT_CLASS("_TtC20DriveKitTripAnalysis9TripPoint")
@interface TripPoint : NSObject
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
@end

The website documentation references this object as :

public struct TripPoint {
    let latitude: Double
    let longitude: Double
    let speed: Double
    let accuracy: Double
    let elevation: Double
    let distance: Double
    let heading: Double
    let duration: Double
}

So sharpie is generating the entity in ApiDefinitions.cs as :
// @interface TripPoint : NSObject
[BaseType (typeof(NSObject), Name = "_TtC20DriveKitTripAnalysis9TripPoint")]
[DisableDefaultCtor]
interface TripPoint
{
}

and the output object looks like :

public unsafe partial class TripPoint : NSObject {

        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        static readonly IntPtr class_ptr = Class.GetHandle ("_TtC20DriveKitTripAnalysis9TripPoint");

        public override IntPtr ClassHandle { get { return class_ptr; } }

        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected TripPoint (NSObjectFlag t) : base (t)
        {
            IsDirectBinding = GetType ().Assembly == global::ApiDefinitions.Messaging.this_assembly;
        }

        [BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
        [EditorBrowsable (EditorBrowsableState.Advanced)]
        protected internal TripPoint (IntPtr handle) : base (handle)
        {
            IsDirectBinding = GetType ().Assembly == global::ApiDefinitions.Messaging.this_assembly;
        }

    } /* class TripPoint */

Everything looks fine to me and my guess is that I should be accessing TripPoint properties like this : > tripPoint.ValueForKey((NSString)"speed"), but I have NSUnknownKeyExceptionevery time. So I must be missing something.

Maybe you would have a guess ?

Thanks

Posts

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    I know the objects composition (it's a swift structure) so I should be able to access my objects properties using > obj.ValueForKey("propertyName"), but every time this raises a **NSUnknownKeyException: this class is not key value coding-compliant for the key propertyName.

    I don't believe this is a safe assumption. The definition for TripPoint:

    SWIFT_CLASS("_TtC20DriveKitTripAnalysis9TripPoint")
    @interface TripPoint : NSObject
    - (nonnull instancetype)init SWIFT_UNAVAILABLE;
    + (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
    @end
    

    Does not list any selector for them, and I don't believe arbitrary swift struct properties are available via ValueForKey.

    Unless you have an objective-c sample showing that this works, I don't think this works that way. The NSUnknownKeyException you see is not coming from us but the native object, so I believe you would get the same behavior from an objective-c app.

  • fjaeger08fjaeger08 USMember ✭✭

    Hi Chris, thank you for your answer.
    As you suggested, I implemented the native iOS SDK into a native Swift application using Pod dependency tool.

    I jumped to the definition of this specific class to get this :

    `@objc public class TripPoint : NSObject {

    public let latitude: Double
    
    public let longitude: Double
    
    public let speed: Double
    
    public let accuracy: Double
    
    public let elevation: Double
    
    public let distance: Double
    
    public let heading: Double
    
    public let duration: Double
    
    public init(latitude: Double, longitude: Double, speed: Double, accuracy: Double, elevation: Double, distance: Double, heading: Double, duration: Double)
    

    }`

    And with that I can access my TripPoint properties into my AppDelegate callback :
    func tripPoint(tripPoint: TripPoint) { let speed = tripPoint.speed }

    It feels like I have a shortened class definition into my .framework header (the one that is used by objective sharpie to generate the C# wrapper), but the whole definition is located into the compiled swift library files located into .swiftmodule folder, and it can be understood/ compiled from a native application.
    Beside, the SDK is working well, all the methods are called without any error, I just miss this final piece.

    Thanks

  • fjaeger08fjaeger08 USMember ✭✭
    edited March 26

    I tried to update the ApiDefinitions.cs like this :

    [BaseType(typeof(NSObject), Name = "_TtC20DriveKitTripAnalysis9TripPoint")]
    [DisableDefaultCtor]
    interface TripPoint
    {
    // @property (readonly) double speed;
    //[BindAs(typeof(NSNumber))]
    [Export("speed")]
    NSNumber Speed { [Bind("speed")]get; }
    }

    But I still got

    Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInvalidArgumentException Reason: -[DriveKitTripAnalysis.TripPoint speed]: unrecognized selector sent to instance

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    I believe you may need to use the @objc attribute to expose those properties to the objective c runtime.

  • fjaeger08fjaeger08 USMember ✭✭

    Hi Chris,
    just to let you know, I resolved my issue by creating my own native iOS swift framework, which is serving as a proxy to expose everything I need from the original framework (the one I don't access the source code).
    Just as you stated, @objc annotation is used to expose methods/properties/classes to the objectiveC runtime, and it indicates to objective sharpie to generate the corresponding c# instructions.

    Thank you for your assistance.

Sign In or Register to comment.