Supposing I have a class in Objective-c with a static method like this:
+ (NSError *)executeUpdateQuery:(NSString *)query, ...;
How do I call that from Swift? The autocomplete doesn't recognise it, and the compiler is unhappy with:
MyClassName.executeUpdateQuery("")
Complaining that 'MyClassName.Type does not have a member named executeUpdateQuery'
Write a va_list version of your variadic method;
+ (NSError *)executeUpdateQuery:(NSString *)query, ...
{
va_list argp;
va_start(argp, query);
NSError *error = [MyClassName executeUpdateQuery: query args:argp];
va_end(argp);
return error;
}
+ (NSError *)executeUpdateQuery:(NSString *)query args:(va_list)args
{
NSLogv(query,args);
return nil;
}
This can then be called from Swift
MyClassName.executeUpdateQuery("query %d, %d %d", args: getVaList([1,2,3,4]))
Add an extension to support native Swift variadic args:
protocol CFormatFunction {
class func executeUpdateQuery(_ format: String, _ args: CVarArg...) -> NSError?
}
extension MyClassName : CFormatFunction {
class func executeUpdateQuery(_ format: String, _ args: CVarArg...) -> NSError?
{
return withVaList(args) { MyClassName.executeUpdateQuery(format, args: $0) }
}
}
MyClassName.executeUpdateQuery("query %d %# %.2f", 99, "Hello", 3.145)
Be careful, Swift doesn't provide NS_FORMAT_FUNCTION warnings (-Wformat)
MyClassName.executeUpdateQuery("query %#", 99)
CVArgType is useful in presenting C "varargs" APIs natively in
Swift. (Swift Docs)
If you have
+ (int)f1:(int)n, ...;
you first need to make a va_list version:
+ (int)f2:(int)n withArguments:(va_list)arguments
This can be done without duplicating code by calling the va_list version from the variadic version. If you didn't write the original variadic function it may not be possible (explained in this reference).
Once you have this method, you can write this Swift wrapper:
func swiftF1(x: Int, _ arguments: CVarArgType...) -> Int {
return withVaList(arguments) { YourClassName.f2(x, withArguments :$0) }
}
Note the omitted external parameter name (_ before arguments), which makes the call syntax for swiftF1 just like a normal C variadic function:
swiftF1(2, some, "other", arguments)
Note also that this example doesn't use getVaList because the docs say it is "best avoided."
You can further put this function in a Swift extension of the original class, if you want.
In Objective C
MyClassName.h
+ (BOOL)executeSQL:(NSString *)sql args:(va_list)args;
MyClassName.m
+ (BOOL)executeSQL:(NSString *)sql args:(va_list)arguments
{
NSLogv(sql, arguments);
sql = [[NSString alloc] initWithFormat:sql arguments:arguments];
va_end(arguments);
}
Swift - add in its class
Works perfect
protocol CFormatFunction {
class func executeSQLArg(format: String, _ args: CVarArgType...) -> Bool
}
extension MyClassName : CFormatFunction {
class func executeSQLArg(format: String, _ args: CVarArgType...) -> Bool
{
return MyClassName(format, args:getVaList(args))
}
}
How to use
Swift
MyClassName.executeSQLArg(query, "one","two",3)
Objetive C
[MyClassName executeSQLArg:query, #"one",#"two",#3]
Related
Based on this issue about using NSString formatting I try to implement multiplatform implementation for formatting when using vararg, with no luck so far.
What I did
added FoundationInterop.def
language = Objective-C
---
#import <Foundation/NSString.h>
NSString* format(NSString* format, ...) {
va_list args;
va_start(args, format);
NSString* result = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
return result;
}
compiled it in gradle
targets {
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
// https://kotlinlang.org/docs/reference/mpp-dsl-reference.html#native-targets
fromPreset(iOSTarget, 'ios') {
binaries {
}
compilations.main.cinterops {
FoundationInterop {
}
}
}
}
Created StringExtensions.kt in commonMain
expect class StringType
expect fun String.format(format: String, vararg args: Any?): StringType?
in iosMain
actual typealias StringType = String
/**
* https://github.com/JetBrains/kotlin-native/issues/1834
*/
actual fun String.format(format: String, vararg args: Any?): StringType? {
return FoundationInterop.format(format, args as Any)
}
example
val fmt = "http://geomag.bgs.ac.uk/web_service/GMModels/igrf/13/?latitude=%f&longitude=%f&altitude=0&date=%d-%02d-%02d&format=json"
val url = fmt.format(urlFmt, 59.127934932762166, 38.00503518930868, 2020, 1, 3)
output - as you see no values substitutions occured for some reason
http://geomag.bgs.ac.uk/web_service/GMModels/igrf/13/?latitude=0.000000&longitude=0.000000&altitude=0&date=43344272-198763328-00&format=json
Edit
stringWithFormat gives the same result
actual fun String.format(format: String, vararg args: Any?): StringType? {
return NSString.stringWithFormat(format, args as Any)
}
Edit 2
Created issue https://youtrack.jetbrains.com/issue/KT-42925
I confirm what you say about NSString.stringWithFormat. The feature is missing as we read in the JB offical answer from
Svyatoslav Scherbina and we could follow the issue from you here: KT-42925
As an awful workaround, I was suggesting something like that (WARNING: not exhaustive, without many index count checks...)
import platform.Foundation.NSString
import platform.Foundation.stringWithFormat
actual typealias StringType = String
actual fun String.format(format: String, vararg args: Any?): StringType? {
var returnString = ""
val regEx = "%[\\d|.]*[sdf]|[%]".toRegex()
val singleFormats = regEx.findAll(format).map {
it.groupValues.first()
}.asSequence().toList()
val newStrings = format.split(regEx)
for (i in 0 until args.count()) {
val arg = args[i]
returnString += when (arg) {
is Double -> {
NSString.stringWithFormat(newStrings[i] + singleFormats[i], args[i] as Double)
}
is Int -> {
NSString.stringWithFormat(newStrings[i] + singleFormats[i], args[i] as Int)
}
else -> {
NSString.stringWithFormat(newStrings[i] + "%#", args[i])
}
}
}
return returnString
}
But look if it could be a valid workaround for you.
You can't forward Kotlin variadic arguments to C or Objective-C. C variadics are compile time.
It is not a Kotlin-specific limitation. You can't call a variadic C function from another variadic C function with forwarding all the arguments.
NSString.stringWithFormat(format, args as Any)
This is not correct.
This line takes args (which is an Array), casts it to Any and passes as a single argument.
So this is mostly equivalent to something like
[NSString stringWithFormat:format, createKotlinArray(/* all arguments here */)]
which doesn't do what you expect.
So KT-42925 is not valid.
Your problem could possibly be solved by one of the missing features:
Common String.format in stdlib (KT-25506). But this is not easy.
Support for building va_list dynamically, e.g. from Kotlin Array (KT-42973). In this case it would be easy to use this variant: https://developer.apple.com/documentation/foundation/nsstring/1407827-initwithformat
I have a function in my objective c file (lets say class MyBlockExecutor):
+ (void) runBlockFromDictionary: (NSDictionary*) blocksDict andKey: (NSString*) key
{
if ( [blocksDict objectForKey: key] != nil )
{
((MyBlock)[blocksDict objectForKey: key])();
}
}
Now, I want to call this function from Swift. Here is my swift call:
MyBlockExecutor.runBlock(from: [
"key1":{ ()->Void in
print("block for key1 called")
}
], andKey: "key1")
This crashes my app. I am getting EXC_BAD_ACCESS error on this line:
((MyBlock)[blocksDict objectForKey: key])();
Although, calling the same function from Objective-C works perfectly fine.
Also, I've defined MyBlock as :
typedef void (^MyBlock)(); //defined in MyBlockExecutor.h file
How do I resolve this?
Edit:
I am open to changes in the objective c function, I just somehow need to pass a collection of closures from swift to my objective c function and run the block.
You can use a similar approach as in Swift blocks not working: Annotate the block with #convention(block)
to use the Objective-C block calling convention, and (explicitly) cast
it to AnyObject before putting it into the dictionary:
let myBlock: #convention(block) () -> Void = {
print("block for key1 called")
}
let dict = ["key1": myBlock as AnyObject]
MyBlockExecutor.runBlock(from: dict, andKey: "key1")
This worked as expected in my test.
It is also similar to what Quinn “The Eskimo!” suggested in
the Apple developer forum as a method
to pass a closure (defined in Swift) as an Objective-C compatible
object through pointers, only that I replaced the unsafeBitCast
by the simpler as AnyObject.
You can also write everything inline:
MyBlockExecutor.runBlock(from: ["key1": {
print("block for key1 called")
} as #convention(block) () -> Void as AnyObject
], andKey: "key1")
or define a helper function:
func objcBlock(from block: #convention(block) () -> Void) -> AnyObject {
return block as AnyObject
}
MyBlockExecutor.runBlock(from: ["key1": objcBlock {
print("block for key1 called")
}], andKey: "key1")
try to break the code in segments and check from where the error is coming.. although its nearly same what you have done we have just break the code in multiple line for debugging easily
//1. create the block instance separately
let myBlockForKey1:MyBlock = { () in
print("block for key1 called")
}
//2. create dic of blocks as
let dicOfBlocks:[String:MyBlock] = ["key1":myBlockForKey1]
//3. call your function
MyBlockExecutor.runBlock(from: dicOfBlocks, andKey: "key1")
I have the following method
static func t(key: String, params: AnyObject...) -> String{
let string = .......
if (params.count == 0){
return string
} else {
return String(format: string, params)
}
}
The problem is that i need to make this method available in Objective C which is impossible with variadic params. What I tried to do is create another method for objective c where params is an array. But then I also need to have third one for when there is no params.
#objc static func t(key: String, params: [AnyObject]) -> String
#objc static func t(key: String) -> String
But now swift complains that method t is ambiguous because 1st and 3rd method can take just the key.
How to make this work? I know I could use different names, but I would like to keep things consistent.
UPDATE:
Another problem is that I can't call String(format:...) correctly once inside the function since params is an array and not a group of params. Any nice way to solve this?
You can do the following:
#objc(C)
public class C : NSObject {
public static func t(key: String, params firstParam: AnyObject, _ params: AnyObject...) -> String {
return t(key, params: [firstParam] + params)
}
#objc
public static func t(key: String, params: [AnyObject]) -> String {
return "TODO"
}
#objc
static func t(key: String) -> String {
return t(key, params: [])
}
}
... calling from Swift:
C.t("")
C.t("", params: 1)
C.t("", params: 1, 2)
C.t("", params: [1, 2, 3])
This is bridged as follows:
SWIFT_CLASS_NAMED("C")
#interface C : NSObject
+ (NSString * _Nonnull)t:(NSString * _Nonnull)key params:(NSArray * _Nonnull)params;
+ (NSString * _Nonnull)t:(NSString * _Nonnull)key;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
And can be messaged as follows:
#import "DarkSide.h"
#import "ObjCInterOp-Swift.h"
#implementation DarkSide
- (instancetype)init
{
self = [super init];
if (self) {
[C t:#"" params:#[]];
[C t:#""];
}
return self;
}
#end
Regarding the update to the question, there is a version of the formatted String initialiser that takes [CVarArgType] rather than CVarArgType...:
func f(format format: String, _ params: [CVarArgType]) -> String {
return String(format: format, arguments: params)
}
f(format: "%d %d %d", [1, 2, 3]) // "1 2 3"
Only CVarArgType cannot be represented in Objective C, which really complicates things. In fact, if you really must use String.init(format: String, arguments: [CVarArgType]) and get to it from Objective C then I don't see at the moment how to avoid casting from AnyObjects to CVarArgTypes along the lines of the following murky code:
#objc(C)
public class C : NSObject {
public static func t(key: String, params: CVarArgType...) -> String {
return t(key, params: params)
}
public static func t(key: String, params: [CVarArgType]) -> String {
return String(format: key, arguments: params)
}
#objc(t:params:)
public static func t_objc(key: String, params: [AnyObject]) -> String {
let args = params.map{
// here you'll probably need to parse `key` so as to
// know what to cast into!
($0 as! NSNumber).integerValue as CVarArgType
}
return t(key, params: args)
}
#objc(t:)
static func t_objc(key: String) -> String {
return t(key)
}
}
My suggestion is to either wean off things like String.init(format: String, arguments: [CVarArgType]), or to implement the base method in Objective C...
Okay, here is some Objective C code I've been using in the past to quickly check if a dictionary contains certain key/value pairs and if the values are of the right type (have the expected class).
First I need a little helper class:
#interface TypeCheck : NSObject
#property NSString * name;
#property Class type;
#end
#implementation TypeCheck
+ (instancetype)typeCheckWitName:(NSString *)name type:(Class)type {
TypeCheck * tc = [[self alloc] init];
tc.name = name;
tc.type = type;
return tc;
}
#end
And then I can define keys and types like this:
NSArray<TypeCheck *> * model = #[
[TypeCheck typeCheckWitName:#"firstName" type:[NSString class]],
[TypeCheck typeCheckWitName:#"lastName" type:[NSString class]],
[TypeCheck typeCheckWitName:#"age" type:[NSNumber class]],
[TypeCheck typeCheckWitName:#"image" type:[NSImage class]]
// ... Many more follow ...
];
I was able to use structs for all that prior to ARC but with ARC the compiler doesn't like objects in structs, so I now use objects for everything. The final check code just looks like this:
for (TypeCheck * typeCheck in model) {
id value = dict[typeCheck.name];
if (!value) {
// BAD... Value must be there
// Throw error
return;
}
if (![value isKindOfClass:typeCheck.type]) {
// BAD... Value must be of right type
// Throw error
return;
}
// Do something with value
}
This used to be pretty nice code IMHO. Gets even nicer if you use a C function to create the TypeCheck objects:
#define TypeString [NSString class]
#define TypeNumber [NSNumber class]
#define TypeImage [NSImage class]
static TypeCheck * makeTypeCheck ( NSString * name, Class type ) {
return [TypeCheck typeCheckWitName:name type:type];
}
And then:
NSArray<TypeCheck *> * model = #[
makeTypeCheck(#"firstName", TypeString),
makeTypeCheck(#"lastName", TypeString),
makeTypeCheck(#"age", TypeNumber),
makeTypeCheck(#"image", TypeImage)
// ... and so on ...
One could even make makeTypeCheck a macro.
And now I'm trying to do the same or similar in Swift... and I fail horribly! I tried with a struct but how can I store class types there? I don't want to use Any as a type qualifier. Then I tried with a generic struct, like struct TypeCheck<T> so I could set the type, but I cannot put multiple of these into a single array as when T is different, as these are effectively different types (and again, I don't want to use Array<Any> despite that how can I cast? I cannot cast to TypeCheck without a generic type). I don't have to use a struct, an object will be fine but that doesn't really solve any of my problems. I cannot believe that this is so hard to do in Swift. I'm not really a Swift expert yet, so I guess i must be missing something important here.
While Obj-C was good at dynamic type inspection, Swift is much better of you allow the compiler to do the type checking at compile time. Since I don't know what your specific use case is, I'd encourage you to look for a more Swifty way to do what you want instead of just converting Obj-C to Swift. But since I don't know your use case, I'll just answer your question as written.
The only way I could get something like you describe was to make the struct conform to a protocol. Then when you put it in an array, you set the arrays type to contain instances of the protocol.
protocol TypeCheckable {
var name: String { get }
func matches(thing: Any) -> Bool
}
struct TypeCheck<T>: TypeCheckable {
let name: String
init(name: String, type: T.Type) {
self.name = name
}
func matches(thing: Any) -> Bool {
return thing is T
}
}
let array: [TypeCheckable] = [
TypeCheck(name: "test", type: String.self),
TypeCheck(name: "other", type: Int.self)
]
If you define this:
typealias TypeChecker = ([String: AnyObject]) -> Bool
struct makeTypeCheck<T> {
let name: String
func checker() -> TypeChecker {
return { (dict: [String: AnyObject]) -> Bool in
guard let v = dict[self.name] else {
return false
}
if let _ = v as? T {
return true
}
return false
}
}
}
func checkDictionary(dict: [String: AnyObject], model: [TypeChecker]) -> Bool {
for m in model {
if !m(dict) {
return false
}
}
return true
}
This test passes
func testChecker() {
let model = [
makeTypeCheck<String>(name: "firstName").checker(),
makeTypeCheck<Int>(name: "age").checker(),
]
XCTAssertTrue(checkDictionary(["firstName": "Jane", "age": 35], model: model))
XCTAssertFalse(checkDictionary(["firstName": 21, "age": 35], model: model))
XCTAssertFalse(checkDictionary(["age": 35], model: model))
}
I have a simple Swift extension on NSManagedObject, in which I have a parametrized method for finding a single object - the signature looks like:
public class func findFirst<T:NSManagedObject>(inContext context : NSManagedObjectContext? = .None) -> T?
I'm trying to call this from Objective-C, but it seems like it cannot be seen. If I create a non-parameterized version I can see and call it just fine from Objective-C:
public class func findFirstUntypedWithPredicate(predicate:NSPredicate?, inContext context : NSManagedObjectContext? = .None) -> NSManagedObject?
Is there any way for ObjectiveC to be able to reach the parameterized version of the call?
I would use Self like so:
public class func findFirst(inContext context : NSManagedObjectContext? = .None) -> Self?
using the technique found here:
How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
However, that causes the Swift compiler to segfault when compiling the code (Xcode 6.3.1, or Xcode 6.4 beta 2).
Edit: Here's a link with the full source of the framework I'm trying to build, including bonus Swift compiler crashes caused by templated methods:
https://www.dropbox.com/s/fixaj9ygdoi4arp/KiGiCoreData.zip?dl=0
Generic methods are not visible from Objective-C. However you can use
the ideas from How to use generic types to get object with same type to define a findFirst() class method
which returns Self? (the Swift equivalent of instancetype) without
being generic:
// Used to cast `AnyObject?` to `Self?`, `T` is inferred from the context.
func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}
extension NSManagedObject
{
class func entityName() -> String {
let classString = NSStringFromClass(self)
// The entity is the last component of dot-separated class name:
let components = split(classString) { $0 == "." }
return components.last ?? classString
}
// Return any matching object, or `nil` if none exists or an error occurred
class func findFirst(context : NSManagedObjectContext, withPredicate pred : NSPredicate?) -> Self? {
let name = entityName()
let request = NSFetchRequest(entityName: name)
request.predicate = pred
var error : NSError?
let result = context.executeFetchRequest(request, error: &error)
if let objects = result {
return objcast(objects.first)
} else {
println("Fetch failed: \(error?.localizedDescription)")
return nil
}
}
}
This can be used from Swift
if let obj = YourEntity.findFirst(context, withPredicate: nil) {
// found
} else {
// not found
}
and from Objective-C:
YourEntity *obj = [YourEntity findFirst:context withPredicate:nil];