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...
Related
Given the following Objective-C methods
#import Foundation;
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
// Variant 1. Notice that arg1 is nullable, and that all closure arguments are nullable.
+ (void)test:(NSString *_Nullable)arg1
action:(nullable void (^)(void))action;
// Variant 2. Notice that arg2 is non null, there is an additional, nullable, closure argument
+ (void)test:(NSString *)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
I find that when I attempt to call the first method from Swift with fully specified args, it actually calls the second variant when I use a trailing closure
// Calls first variant (This is an erroneous claim, hence Matt's answer)
TestClass.test("arg1", action: {})
// Calls second variant
TestClass.test("arg2") {}
I was expecting Variant 1 to be called in both cases. I'm unclear if I'm doing something wrong or not. I also seem to have missed the fact that Swift could provide generate arguments at all when calling Obj-C methods and am struggling to find the relevant documentation on it.
If I replace the Obj-C TestClass with the equivalent
class TestClass {
class func test(_ arg1: String?, action: (() -> ())? = nil) {
}
class func test(_ arg1: String!, action: (() -> ())? = nil, action2: (() -> ())? = nil) {
// Should not get here
assert(false)
}
}
Then I get a compiler warning about ambiguous use of 'test' in both calls.
Tested on Xcode 12.3 and 12.4.
Obviously, I expect Variant 1 to be called in both cases.
Actually, I find that Variant 2 is called in both cases.
First generate the Swift interface for your Objective-C interface. You get this:
open class func test(_ arg1: String?, action: (() -> Void)? = nil)
open class func test(_ title: String, action: (() -> Void)?, action2: (() -> Void)? = nil)
We have now eliminated the Objective-C component from the story and can just use these methods in our testing:
typealias VoidVoid = () -> Void
func test(_ arg1: String?, action: VoidVoid? = nil) { print("1") }
func test(_ title: String, action: VoidVoid?, action2: VoidVoid? = nil) { print("2") }
func f() {
test("arg1", action: {})
test("arg2") {}
}
If we call f(), the console prints "2" twice. I don't get any compile error, but it does appear that the first variant is unreachable unless you omit both function arguments.
In matt’s answer, he shared his experience where the compiler would resolve both Swift calls to the second Objective-C variant of the method (the one with the action2 parameter). We should note that this behavior is unique to the fact that the two methods have a different nullability for the first argument, being an nullable in the first variant, and not nullable in the second variant.
Switch the nullability of the first parameter of the two methods, and the behavior changes, favoring the first variant. Likewise, if both of the renditions use the same nullability, then, again, the first variant will be used. Consider:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
That this translates to the following Swift interface:
open class TestClass : NSObject {
open class func test(_ title: String?, action: (() -> Void)? = nil)
open class func test(_ title: String?, action: (() -> Void)?, action2: (() -> Void)? = nil)
}
Now, both Swift calls will call the first variant, not the second:
TestClass.test("foo", action: {}) // variant 1
TestClass.test("foo") {} // variant 1
In your example (where the first variant made the first argument nullable, but the second variant did not), it is resolving to the second variant because we passed a non-optional string.
Given that (hopefully) the first variant is just an Objective-C convenience method to the second variant, it probably does not matter which is called, but the moral of the story is that the resolution to the appropriate Objective-C method is subject to very subtle considerations that may not be obvious at a glance.
At the risk of premature optimization, if you really are concerned about ensuring that Swift always call the second Objective-C variant regardless of the nullability/optionality of the first parameter in these two variations, one could make the intent explicit with NS_REFINED_FOR_SWIFT as discussed in Improving Objective-C API Declarations for Swift. For example:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action NS_REFINED_FOR_SWIFT;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2 NS_REFINED_FOR_SWIFT;
#end
NS_ASSUME_NONNULL_END
And then manually declare your own explicit Swift interface:
extension TestClass {
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)? = nil) {
__test(title, action: action, action2: nil)
}
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)?, action2: (() -> Void)? = nil) {
__test(title, action: action, action2: action2)
}
}
That will always call the second variant of the Objective-C method. This eliminates method resolution behaviors that might be otherwise not be obvious, making your intent explicit.
I want to pass a closure through another function via a selector. Here is what I am trying to do ideally:
#objc private func aFunction(_ firstParam: String, onComplete: (String) -> Void) {
//..some internal codes
onComplete("Done.")
}
func functionCaller(_ selectorString: String, paramString: String, onComplete: (String) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
self.perform(selector, with: printString, with: onComplete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString String) -> Void in
print(myString)
})
Here the problem is when you try to compile this, Swift gives an error called "Segmentation Fault: 11"
I found the problematic line which is:
self.perform(selector, with: printString, with: onComplete)
when I change last with: onComplete parameter to a String (also changed related functions params) it is working. As far as I understand that the problem is sending closure via self.perform call doesn't work because the first function is an '#objc' marked function (I put this because otherwise perform selector did not work on Swift 3).
So any idea how can I pass a function or closure into a '#objc' marked function via performing selector?
Try to use Any instead of String in your function
func functionCaller(_ selectorString: String, paramString: String, onComplete: (Any) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
let complete : Any = onComplete("complete")
self.perform(selector, with: complete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString ) -> Void in
let string = myString as! String
print(string)
})
I tested it and works in Swift 3
let completionHandler: (Any) -> Void = { value in
let js = "try {(callBackKey)('(value)'); delete window.(callBackKey);} catch(e){};"
(jsContext as? TYWKWebView)?.evaluateJavaScript(js, completionHandler: nil)
}
let handlerDict: [String: Any] = [TYJSBridge.COMPLETION_HANDLER_KEY: completionHandler]
let _ = jsInterfaceObject.perform(sel, with: parameters, with: handlerDict)
}
I'm looking for the equivalent of the following Obj-C code in Swift:
- newInstanceOf:(id)classRef {
return [classRef new];
}
and then to use it:
id instance = [whatever newInstanceOf:NSArray.class]
[instance isKindOfClass:NSArray.class] == YES
I have tried using a Swift template:
func newSomething<T>(classRef:T.Type) -> T {
return classRef()
}
I get the error: error: 'T' cannot be constructed because it has no accessible initializers
You could create a protocol to act as a type constraint for objects initializable by a void-argument initializer, and thereafter extend your types of choice to this protocol.
protocol SimplyInitializable {
init()
}
extension Int : SimplyInitializable { }
extension Double : SimplyInitializable { }
extension String : SimplyInitializable { }
struct MyStruct {
var myInt : Int
init() {
myInt = 0
}
}
extension MyStruct : SimplyInitializable { }
func newSomething<T: SimplyInitializable>(classRef: T.Type) -> T {
return classRef.init()
}
/* Examples */
var a = newSomething(Int)
var b = newSomething(Double)
var c = newSomething("".dynamicType)
var d = newSomething(MyStruct)
var e = newSomething(a.dynamicType)
print(a.dynamicType) // Int
print(b.dynamicType) // Double
print(c.dynamicType) // String
print(d.dynamicType) // MyStruct
print(e.dynamicType) // Int
Actually in Swift, not all classes are guaranteed to have an init() initializer. It can work with NSObject classes though because NSObject does have that requirement.
func newInstanceOf<T:NSObject>(aClass:T.Type) -> NSObject
{ return aClass.init() }
let string = newInstanceOf(NSString) // ""
let date = newInstanceOf(NSDate) // 2016-01-15 05:27:29 +0000
I just modified it. Another problem is that if I want to have a subclass inherit from BaseParticipant, may I re-implement func performEvent inside the subclass?
For example:
class CyclingParticipant: BaseParticipant, Participant
{
init(name: String)
{
super.init(name: name, preferredEvent: Event.CYCLING)
}
func performEvent(event: Event, distance: Distance) throws
{
}
}
but the compiler said "redundant conformance of CyclingParticipant to protocol Participant .
class BaseParticipant: Participant
{
var name: String
var preferredEvent: Event
var raceTime: Int
var couldNotFinish: Bool
//var performedEvent: Event
// in swift, the class accepts protocol must impletment all funcs inside protocol
init(name: String, preferredEvent: Event)
{
self.name = name
self.preferredEvent = preferredEvent
self.raceTime = 0
self.couldNotFinish = false
}
func getName() -> String
{
return self.name
}
func getPreferredEvent() -> Event
{
return self.preferredEvent
}
func isDisqualified() -> Bool
{
return self.couldNotFinish
}
func addTime(addtionalRaceTime:Int) -> Void
{
self.raceTime += addtionalRaceTime
}
func setCouldNotFinish() -> Void
{
self.couldNotFinish = true
}
func performEvent(event: Event, distance: Distance) throws -> Int
{
return 1
}
func getTime() throws
{
}
}
The code of protocol Participant:
protocol Participant
{
func getName() -> String
func getPreferredEvent() -> Event
func isDisqualified() -> Bool
func performEvent(event: Event,distance: Distance) throws ->Int
func addTime(addtionalRaceTime: Int)
func setCouldNotFinish()
func getTime() throws
}
You're missing an implementation of the getTime() function as listed in your Protocol. Also, you should post such questions on Piazza. :P
[Updating to answer reworded question]
The BaseParticipant class already adopts the Participant protocol, so the CyclingParticipant subclass should not declare that it adopts it also, this is causing the redundant conformance error. Because BaseParticipant is already a Participant, any subclass of BaseParticipant will also be a Participant.
Change:
class CyclingParticipant: BaseParticipant, Participant
to:
class CyclingParticipant: BaseParticipant
All declared methods in a Swift protocol are required by default.
getTime() is not implemented
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]