This question has been inspired by this one: Swift closure in array becomes nil in Objective-c, which I answered with a workaround using a wrapper class, but I'm still curious as to why one can't call, in Objective-C, a Swift closure passed via id. A simple example follows.
Objective-C code:
// In a header
typedef void (^EmptyBlock)();
#interface MyClassOC : NSObject
-(void)invoke:(id)blk;
#end
// In an implementation (.m) file
#implementation MyClassOC
-(void)invoke:(id)blk {
EmptyBlock emptyBlock = blk;
emptyBlock();
}
#end
No problem providing an Objective-C block:
EmptyBlock block = ^{ puts("In OC empty block..."); };
MyClassOC * myClassOC = [[MyClassOC alloc] init];
[myClassOC invoke:block];
However, the Objective-C code in invoke... can't call a Swift closure passed in via id:
let myClassOC = MyClassOC()
let myBlock : EmptyBlock = {
print("In Swift EmptyBlock...")
}
myClassOC.invoke(myBlock)
I end up with EXC_BAD_ACCESS on this line:
EmptyBlock emptyBlock = blk;
Any idea what's going on here?
The reason is probably the support introduced in Swift 3 for any Swift type to be represented as an opaque object of id type in Objective-C. Read Swift Value Types in Objective-C in this Apple blog post for details. In that you will see that Swift types are passed as _SwiftValue * when the parameter type is id, this is an opaque type.
The argued benefit of this approach is that Swift value types can be stored in Objective-C collections; however the disadvantage is that you cannot convert to an Objective-C compatible value on the Objective-C side. Debug your code and you'll see the block is being passed as a _SwiftValue * and not an Objective-C block type.
Declare the bak parameter to have EmptyBlock type and your code works.
HTH
Because Swift closures and Objective-C blocks are not the same things. Swift automatically converts a closure to an Objective-C block if they see that you are calling a method with an Objective-C block type, but it doesn't do that otherwise. And as CRD's answer mentions, in Swift 3 any Swift value can be represented as an Objective-C object, so even though it sees that it expects an Objective-C object, it still doesn't know you want an Objective-C block because a Swift closure can still be bridged to an opaque object.
The only way I figured out to pass it from Swift and work is something like:
myClassOC.invoke(myBlock as #convention(block) () -> Void)
Related
I want to call this Objective-C function from Swift code.
The accepted answer on this question shows how to use an Objective-C class in Swift, not a function. Secondly, the Objective-C implementation in it is contained in a .m file, while the one I have linked above is a .h file.
What is the easiest way I can call an Objective-C function defined in a .h file from Swift?
As mentioned in the comments first you'll need to do all the usual steps for using Objective-C classes from Swift (bridging header, etc.) Then you'll already be able to call your function and you'd only need a bit of tweaking to make it more usable in Swift.
func CopyLaunchedApplicationsInFrontToBackOrder_Swift() -> [[String: AnyObject]]? {
guard let cfarray = CopyLaunchedApplicationsInFrontToBackOrder() else {
return nil
}
let array = unsafeBitCast(cfarray, to: NSArray.self) as [AnyObject]
guard let result = array as? [[String: AnyObject]] else {
return nil
}
return result
}
If your function is implemented in .h file it would be better to move implementation into a corresponding .m file leaving in .h only it's declaration:
#import Foundation;
CFArrayRef CopyLaunchedApplicationsInFrontToBackOrder(void);
It's also possible to put this declaration into your bridging header and leave implementation in the .h file, but that would be unusual making your code more error prone and harder to maintain.
the documentation says:
Global constants defined in C and Objective-C source files are automatically imported by the Swift compiler as Swift global constants.
But it doesn't say anything about the other way around. I need to define a global swift constant and be able to see it one the objective c side like a global c constant. Like on the swift side define:
public let CARDS = ["card1", "card2"]
and see use it on the objective c side like
NSLog(#"Cards count: %d", [CARDS count])
What should I do? I've already imported the swift automatically generated header like:
#import "MyProject-Swift.h"
and in Xcode if I command-click on it, it takes me to the correct place in the swift code, but at compile time I get:
'User of undeclared Identifier CARDS'
on my objective c side.
Here is the document about it
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Global variables (including constants) are unaccessible from Objective-C.
Instead, you have to declare a class which has accessors for the global constants.
// Swift
public let CARDS = ["card1", "card2"]
#objc class AppConstant {
private init() {}
class func cards() -> [String] { return CARDS }
}
// Objective-C
NSArray *cards = [AppConstant cards];
Nice answer by #rintaro, but another alternative simple Swift answer for constants that can be used in both Swift and Objective-C:
#objcMembers
class MyConstants: NSObject {
static let kMyConstant1 = "ConstantValue1";
static let kMyConstant2 = "ConstantValue2";
static let CARDS = ["card1", "card2"]
}
You can access this on both Swift and Objective-C by:
MyConstants.kMyConstant1 // this will return "ConstantValue1"
MyConstants.CARDS // this will return array ["card1", "card2"]
Swift global functions (ie. swift top-level functions) cannot be accessed by objC. Period. End of story.
See above answer from rintaro, to wit...
"This excludes Swift-only features such as those listed here:
. . .
Top-level functions defined in Swift"
boom
I am trying to use a method that was defined as Objective C method, from a swift file
the method in objective c is defined as
-(instancetype) init:(NSString*)string data:(id)data;
the method name cannot be refactored.
I am trying to invoke it as follows
let myObject = MyObject("MyString",data:["1","2","3"])
in return i am getting the compile error : "String! does not conform to protocol 'ExpressibleByStringLiteral'"
How can I fix it?
EDIT:
MyObject is defined as follows
`#interface MyObject<__covariant Type> : NSObject #end
#implementation
#end`
In Swift 3, ObjC generics are imported as Swift generics. And you always need to specify the type parameter in Swift generics. (And Swift 3 is not yet so refined as to generate appropriate diagnostics...)
let myObject = MyObject<NSString>("MyString",data:["1","2","3"])
worked.
Is your data actually data, or a [String]?
the documentation says:
Global constants defined in C and Objective-C source files are automatically imported by the Swift compiler as Swift global constants.
But it doesn't say anything about the other way around. I need to define a global swift constant and be able to see it one the objective c side like a global c constant. Like on the swift side define:
public let CARDS = ["card1", "card2"]
and see use it on the objective c side like
NSLog(#"Cards count: %d", [CARDS count])
What should I do? I've already imported the swift automatically generated header like:
#import "MyProject-Swift.h"
and in Xcode if I command-click on it, it takes me to the correct place in the swift code, but at compile time I get:
'User of undeclared Identifier CARDS'
on my objective c side.
Here is the document about it
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
Tuples
Enumerations defined in Swift
Structures defined in Swift
Top-level functions defined in Swift
Global variables defined in Swift
Typealiases defined in Swift
Swift-style variadics
Nested types
Curried functions
Global variables (including constants) are unaccessible from Objective-C.
Instead, you have to declare a class which has accessors for the global constants.
// Swift
public let CARDS = ["card1", "card2"]
#objc class AppConstant {
private init() {}
class func cards() -> [String] { return CARDS }
}
// Objective-C
NSArray *cards = [AppConstant cards];
Nice answer by #rintaro, but another alternative simple Swift answer for constants that can be used in both Swift and Objective-C:
#objcMembers
class MyConstants: NSObject {
static let kMyConstant1 = "ConstantValue1";
static let kMyConstant2 = "ConstantValue2";
static let CARDS = ["card1", "card2"]
}
You can access this on both Swift and Objective-C by:
MyConstants.kMyConstant1 // this will return "ConstantValue1"
MyConstants.CARDS // this will return array ["card1", "card2"]
Swift global functions (ie. swift top-level functions) cannot be accessed by objC. Period. End of story.
See above answer from rintaro, to wit...
"This excludes Swift-only features such as those listed here:
. . .
Top-level functions defined in Swift"
boom
I'd like to override a method in an Objective C class that I don't have the source to.
I've looked into it, and it appears that Categories should allow me to do this, but I'd like to use the result of the old method in my new method, using super to get the old methods result.
Whenever I try this though, my method gets called, but "super" is nil... Any idea why? I'm doing iPhone development with the XCode 2.2 SDK. I'm definitely working with an instance of a class, and the method of the class is an instance method.
#implementation SampleClass (filePathResolver)
-(NSString*) fullPathFromRelativePath:(NSString*) relPath
{
NSString *result = [super fullPathFromRelativePath: relPath];
... do some stuff with the old result
return result;
}
Note and clarification: From what I can see in the Apple Docs, it appears to me that this should be allowed?
Categories docs at developer.apple.com:
When a category overrides an inherited method, the method in the
category can, as usual, invoke the
inherited implementation via a message
to super. However, if a category
overrides a method that already
existed in the category's class, there
is no way to invoke the original
implementation.
Categories extend the original class, but they don't subclass it, therefore a call to super doesn't find the method.
What you want is called Method Swizzling. But be aware that your code could break something. There's an article on Theocacao written by Scot Stevenson about Method Swizzling in the old Objective-C runtime, Cocoa with Love by Matt Gallagher has an article about Method Swizzling in the new Objective-C 2.0 runtime and a simple replacement for it.
Alternatively, you could subclass the class and then either use the subclass or use + (void)poseAsClass:(Class)aClass to replace the superclass. Apple writes:
A method defined by a posing class
can, through a message to super,
incorporate the superclass method it
overrides.
Be aware that Apple has deprecated poseAsClass: in Mac OS X 10.5.
If you will be coding against this class, simply rename the selector to something your code can use, and call the original selector on self:
#implementation SampleClass (filePathResolver)
-(NSString*) myFullPathFromRelativePath:(NSString*) relPath
{
NSString *result = [self fullPathFromRelativePath: relPath];
... do some stuff with the old result
return result;
}
If you want to override the default implementation of this selector for that class, you'll need to use the method swizzling approach.
Not exactly in category but there is a workaround by adding the method dynamically at runtime. Samuel Défago in his article describes a neat way to create block IMP implementation calling super, his original article can be found here
The relevant code is:
#import <objc/runtime.h>
#import <objc/message.h>
const char *types = method_getTypeEncoding(class_getInstanceMethod(clazz, selector));
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) {
struct objc_super super = {
.receiver = self,
.super_class = class_getSuperclass(clazz)
};
id (*objc_msgSendSuper_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper;
return objc_msgSendSuper_typed(&super, selector, argp);
}), types);