Objective-C Method Parameters in Swift - objective-c

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)

Related

Swift perform selector with inout argument

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

What is the difference between nil in Objective-C and optional in case of swift which by default stores nil

In objective-c for example
NSString *test=nil;
In swift for example
var test: String? = nil
Anything in Swift can be optional, not just object references. You can have an optional bool, with three possible values nil, false, and true. You can have an optional double. That is something that isn't available in Objective-C at all.
In Swift, object references come in two flavours: Optional and non-optional. Optionals can be nil or not nil. Non-optionals cannot be nil ever. In Objective-C, any object reference could be nil.
The Swift type system will force you to deal with the optionality of test. You couldn't use it without unwrapping it first.
You couldn't call test.lowercaseString, because test is a String?, which could be nil. You'd have to unwrap it first:
if let test = test {
print(test.lowercaseString)
}
else {
//test was nil, handle error here
}
In Objective-C, test is just a variable, the type system has no clue about it being nil.
NSLog([test lowercaseString]) //Boom goes the dynamite.
You should read the language guide section on this.

CGContextRef/Objective C to CGContext/Swift confusion [duplicate]

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.

Objc code cannot find Bool variable defined in swift

I have a var defined in one Swift file, but in another Objective-C file, when I try to set this var, the complier complains it cannot find the var. How do I solve this problem? here is the code:
in swift:
var isCreating: Bool!
in objc :
SelectMemberViewController *ctrl = [[SelectMemberViewController alloc]init];
ctrl.isCreating = YES
then the complier is complaining : Property 'isCreating' not found on object of type 'SelectMemberViewController'
The problem is that nothing in Objective-C's world corresponds to a Bool!. Thus, this declaration is not exposed to Objective-C. You need to declare this a plain Bool if you want Objective-C to be able to see it.

Subclassing and Casting in Objective C

I came across a strange problem today. I created a subclass of UIView and added only 1 method to the template code provided by xcode.
#interface FloatView : UIView {
}
- (void)floatTest:(CGFloat)x;
#end
- (void)floatTest:(CGFloat)x {
NSLog(#"float was %f", x);
}
Then in my appDelegate I had code like this:
UIView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Pretty simple, right? What should this print out? I thought it would something like "10.0000", but no, it prints out "0.000000".
I wrestled with this for hours, trying to figure out what I was doing wrong, and then I changed the code in my appDelegate to
FloatView *floatView = [[FloatView alloc] init];
[floatView floatTest:10.0f];
Only then, did it print out the expected "10.0000". Why is this so? I've declared FloatView as a subclass of UIView, shouldn't I be able to assign a FloatView object to a UIView pointer without problems?
Even though floatView was declared a pointer to a UIView, it's really a floatView and it should be able to handle the floatTest message? Am I totally off base here?
Actually, polymorphism is working as expected. If it didn't work, nothing would have been printed (in your example, 0.0000 is being printed). The thing is, while your instance actually responds to testFloat:10.0f message, since the compiler can't statically see the method declaration (as UIView class doesn't declare such a method), it assumes that your method takes ... as argument and returns id.
When CGFloat is passed to a method that expects variable number of arguments (...), it's promoted to double. Thus, the receiving method is passed a double argument and thinks it's a float and it doesn't get printed correctly.
You can verify this behavior by changing NSLog line to:
NSLog(#"%f", *(double*)&x);
When the compiler sends the message to FloatView* rather than a UIView*, it can find the exact signature of the method. It can see it really expects CGFloat and doesn't promote the argument to double. As a result, it works correctly.
Additionally, if UIView* contained the method declaration that took a CGFloat, the compiler would call the method appropriately. To summarize, this is not a polymorphism issue; it's a missing method signature issue.