Since recently when I try to pass an array of CLLocation from an Objective-c module to a Swift one I get:
fatal error: NSArray element failed to match the Swift Array Element
type
This is how I call the functions:
routeLine = [PreloadedLine lineWithLoadedPath:preloadedPath
key:lineKey
andNotification:(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)];
and here is the Swift signature for the function:
class func lineWithLoadedPath(path: [CLLocation]?,
key:String?,
andNotification notification:Bool)->Line?
And this is the Objective-c signature for it:
+ (Line * __nullable)lineWithLoadedPath:(NSArray<CLLocation *> * __nullable)path
key:(NSString * __nullable)key
andNotification:(BOOL)notification;
Believe the error message. Something that isn't a CLLocation is getting into the NSArray.
So, on the Objective-C side, you are saying:
[PreloadedLine lineWithLoadedPath:preloadedPath ...
...but preloadedPath is an NSArray with stuff in it that isn't all CLLocations, so on the Swift side, the app blows up.
I also ported the latter class to Swift along with its hierarchy and this problem got away by itself.
Related
I have an objc method I'd like to call from swift, but the method exists in a third party framework that may or may not exist at runtime.
What I'd like to do is call it dynamically, so I'm looking into interacting with it using selectors.
The method signature looks like this in ObjC
- (NSString * _Nullable)buildData:(NSError * _Nullable __autoreleasing * _Nullable)error;
Removing the nullable annotations, it's the bog standard "return a thing, or error" pattern that ObjC has had forever.
- (NSString*)buildData:(NSError*)error;
If swift can load this at compile time, then it quite happily translates into
func buildData() throws -> String
However, I'd like to call it dynamically. I've worked out I can do this:
let _target:NSObject = // obtain a reference to the underlying value
var error: NSError? = nil
_target.perform(NSSelectorFromString("buildData:"), with: &error)
The problem is, I can't pass a reference to the perform selector method. XCode gives me a compile error of
'&' used with non-inout argument of type 'Any?'
So my question is, how can I call this method using selectors?
Try this:
var error: NSError? = nil
let _target: NSObject = // obtain a reference to the underlying value
withUnsafeMutablePointer(to: &error) {
let selector: Selector = NSSelectorFromString("buildData:")
let methodIMP: IMP! = _target.method(for: selector)
unsafeBitCast(methodIMP,to:(#convention(c)(Any?,Selector,OpaquePointer)->Void).self)(_target,selector,OpaquePointer($0))
}
More info on using convention(c) for invoking selectors in Swift in my answer here
Having some issues calling a method declared in Objective-C and bridged into Swift. I created a void method right next to the one in question and am able to call it, so I'm pretty certain it has to do with the way the bridging is handling the method's parameters.
- (void)foo;
- (NSArray *)fetchProductHistoryForProduct:(Product *)product
forCustomer:(Customer *)customer
forField:(Field *)field
forRange:(DatePickerRange *)range
inContext:(CPSPersistenceController *)context;
Then in the Swift file I am calling each like this:
modelUtil.foo()
let result = modelUtil.fetchProductHistoryForProduct(product, forCustomer: nil, forField: nil, forRange: nil, inContext: nil)
Swift complains "Value of type 'ModelUtil' has no member 'fetchProductHistoryforProduct'"
Could you tell me what I'm doing wrong?
You can see the generated interface for the objective c file from the top left corner of the editor window. The generated interface is like a Swift header file for your ObjC file that should tell you the exact name of the method. Also make sure that all the files containing the parameter types are also added to the bridging-header.
You can use Xcode autocompletion to find the calling sequence. In this case, the forProduct: becomes the label for the first parameter:
let result = modelUtil.fetchProductHistory(forProduct: product, forCustomer: nil, forField: nil, forRange: nil, inContext: nil)
In Objective-C, it's expected that you can pass in NULL to any NSError** parameter to ignore the error. However, when I try to pass NULL to a Swift method that throws an error, it generates a runtime error.
// Thrower.swift
class Thrower: NSObject {
static func throwError() throws {
throw NSError(domain: "bla", code: 0, userInfo: nil)
}
}
...
// AppDelegate.m
BOOL success = [Thrower throwErrorAndReturnError:NULL];
This generates an EXC_BAD_INSTRUCTION error, with this stack:
I'm a little surprised at this behavior. I would expect this to either work, or the compiler to generate a warning when you pass NULL to one of these methods.
Here's what the generated header of the Swift method looks like:
+ (BOOL)throwErrorAndReturnError:(NSError * __nullable * __null_unspecified)error;
If this was not supposed to work, why wouldn't they generate NSError * __nullable * __nonnull, so that a compiler warning is generated when you try to pass in a nullable NSError*?
Is there something I'm missing here, or is this just expected behavior?
On WWDC'15 Session 401 (Swift and Objective-C Interoperability) Doug Gregor said:
This means, 'I thought about it, I couldn't come to an answer.' The best thing to do is keep it implicitly unwrapped optional in Swift, keep it null-unspecified here.
So, basically, your null is mapped to ImplicitlyUnwrappedOptional<ErrorType>.None, that pretty much explains the crash.
On the session above they mention NSError ** is assumed to be nullable on both pointers. Apparently they've changed their mind or the mapping is not symmetrical, anyway it looks wrong to me.
Considering that behavior and not being NSError * __nullable * __nonnull I'd say it's a bug, I'd open a radar. If you do please let us know so we can dupe it.
I'm using an Objective-C class in my Swift project via a bridging header. The method signature looks something like this:
- (CFArrayRef)someMethod:(someType)someParameter;
I started by getting an instance of the class, calling the method, and storing the value:
var myInstance = MyClassWithThatMethod();
var cfArr = myInstance.someMethod(someValue);
Then try to get a value in the array:
var valueInArrayThatIWant = CFArrayGetValueAtIndex(cfArr, 0);
However I get the error Unmanaged<CFArray>' is not identical to 'CFArray'. What does Unmanaged<CFArray> even mean?
I looked through How to convert CFArray to Swift Array? but I don't need to convert the array to a swift array (however that would be nice). I just need to be able to get values from the array.
I have also tried the method of passing the CFArray into a function outlined in this answer:
func doSomeStuffOnArray(myArray: NSArray) {
}
However I get a similar error when using it:
doSomeStuffOnArray(cfArr); // Unmanaged<CFArray>' is not identical to 'NSArray'
I am using CFArray because I need to store an array of CGPathRef, which cannot be stored in NSArray.
So how am I supposed to use CFArray in Swift?
As explained in
Working with Core Foundation Types, there are two possible solutions when
you return a Core Foundation object from your own function that is imported in Swift:
Annotate the function with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED.
In your case:
- (CFArrayRef)someMethod:(someType)someParameter CF_RETURNS_NOT_RETAINED;
Or convert the unmanaged object to a memory managed object with takeUnretainedValue() or takeRetainedValue() in Swift. In your case:
var cfArr = myInstance.someMethod(someValue).takeUnretainedValue()
An Unmanaged is a wrapper for an actual CF value. (Sort of like an optional.) It's there because ARC can't tell from looking at the declaration of someMethod: whether that method retains the value it returns.
You unwrap an Unmanaged by telling ARC what memory management policy to use for the value inside. If someMethod calls CFRetain on its return value:
let cfArr = myInstance.someMethod(someValue).takeRetainedValue()
If it doesn't:
let cfArr = myInstance.someMethod(someValue).takeUnretainedValue()
After you do that, cfArr is a CFArray, so you can use the bridging tricks from the other questions you linked to for accessing it like a Swift array.
If you own the code for someMethod you can change it a bit to not need this. There's a couple of options for that:
Annotate with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED to tell the compiler what memory behavior is needed
Since it's an ObjC method, bridge to NSArray and return that--it'll automatically become an [AnyObject] array in Swift.
I am refactoring to use ARC in my project and can not figure out this problem for the life of me!
I don't know where I got the code from.
screenshot of error http://img341.imageshack.us/img341/972/xcode.png"screenshot of error"
http://img341.imageshack.us/img341/972/xcode.png
The problem is that you are not using bridged casting. You have to use bridging to cast between C types and Objective-C types:
[UIView beginAnimations:#"earthquake" context:(__bridge void *)itemView];
When casting from a C pointer type to an Objective-C type:
UIView * item = (__bridge UIView *)context;