Compile Error in Swift 4 on Parameter Passing

Compile error in Swift 4 on parameter passing

In Swift 4, tuples are treated more stricter than ever.

This closure type: (Void)->Void means a closure which

  • takes a single argument, of which the type is Void
  • returns Void, meaning returns no value

So, try any of the followings:

Pass a value of type Void to the closure. (An empty tuple () is the only instance of Void.)

completion?(())

Or else:

Change the type of the parameter completion.

func openAnimation(_ completion: (() -> Void)?) {
//...
}

Remember, two types (Void)->Void and ()->Void are different even in Swift 3. So the latter would be appropriate, if you intend to represent closure type with no arguments.

This change is part of SE-0029 Remove implicit tuple splat behavior from function applications which is said to be implemented in Swift 3, but it seems Swift 3 has not implemented it completely.


Here, I show you a simplified checking code which you can check the difference on the Playground.

import Foundation

//### Compiles in Swift 3, error and warning in Swift 4
class MyClass3 {

func openAnimation(_ completion: ((Void) -> Void)?) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {

completion?()
}
}

}

//### Compiles both in Swift 3 & 4
class MyClass4 {

func openAnimation(_ completion: (() -> Void)?) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {

completion?()
}
}

}

Compile error for passing nil to an optional parameter with a generic type constraint

As in the comments, this seems like a bug with the Swift compiler. For now, a quick, clean fix I like is to provide nil as a default value in the function signature.

public func append<T: MQOperation>(operation: T,
validator: (Any? -> Bool)?,
configurator: ((T, Any?) -> Void)? = nil)

pass error parameter in swift

I cannot test it currently, but it should be

var err : NSString? = nil
let service = DaService(name: "My Name", &err)

More information can be found in the "Autoreleasing Pointers" section in Interacting with C APIs.

Unable to run function with arguments in Swift

The problem is that you are executing the function myFunc by putting the parentheses after it, which will result in a Void value rather than a function of type () -> Void, which the Button initialiser expects.

You can fix this by wrapping your function call in a closure.

ToolbarItem {
Menu {
Button("Label", action: { myFunc("My string") })
} label: {
Label("Scripts", systemImage: "applescript")
}
}

This way you really are passing a closure of type () -> Void to the Button init (which it expects), which will only be executed on the button tap. With your previous implementation, the function was executed as soon as the Button is initialised rather than when it it tapped.

Is there shorthand to specify a specific overload when passing a function as a closure-based argument?

In Swift, the "base name" of a method is the method name without any arguments:

  • Foo.init(x:y:z:)Foo.init
  • Foo.bar(_:)Foo.bar
  • Foo.baz(baz:)Foo.baz
  • Foo.quux()Foo.quux

When referring to a method by name (rather than calling it), Swift will allow you to refer to it by its base name, so long as the usage is not ambiguous:

struct Foo {
func f(intValue: Int) {}
}

let f = Foo().f

However, when there are multiple methods with the same base name, you can run into situations with ambiguity:

struct Foo {
func f(intValue: Int) {}
func f(stringValue: String) {}
}

let f = Foo().f // error: ambiguous use of 'f'

When this happens, you can either:

  1. Use explicit typing to help disambiguate the methods, if possible:

    let f1: (Int) -> Void = Foo().f
    let f2: (String) -> Void = Foo().f

    Or,

  2. Refer to the method by its fully-qualified name (name with parameter names included):

    let f1 = Foo().f(intValue:)
    let f2 = Foo().f(stringValue:)

In your case, because both methods have the same type signature, you can't use approach (1) to disambiguate between the calls, and will have to resort to (2).

The issue you ran into is that the parameter name you were using was slightly off:

// ❌ Foo.init(doubleValue:)
// ✅ Foo.init(valueToDouble:)

let foos = values.map(Foo.init(valueToDouble:)) // ✅

This does work, and will work as a shorthand instead of having to call the method directly inside of a closure argument.

Swift - optional parameter not working as expected

You should be able to identify that you are using trailing closures in this call:

iterateMyCategoriesItems { (category) in

// doing something here with every category...
}

Swift compiler detects that this is a trailing closure and so it matches it to the last argument of the method, which is the completion handler.

Now that it has done that, it continues to look for other arguments that match the first argument. None is found so it complains.

To fix this, you have to give up on using a trailing closure and pass the argument properly:

iterateMyCategoriesItems(items: { (category) in

// doing something here with every category...
})

You can also remove the argument label to make this cleaner.

How do I resolve ambiguous use of compile error with Swift #selector syntax?

[NOTE This answer was originally formulated under Swift 2.2. It has been revised for Swift 4, involving two important language changes: the first method parameter external is no longer automatically suppressed, and a selector must be explicitly exposed to Objective-C.]

You can work around this problem by casting your function reference to the correct method signature:

let selector = #selector(test as () -> Void)

(However, in my opinion, you should not have to do this. I regard this situation as a bug, revealing that Swift's syntax for referring to functions is inadequate. I filed a bug report, but to no avail.)


Just to summarize the new #selector syntax:

The purpose of this syntax is to prevent the all-too-common runtime crashes (typically "unrecognized selector") that can arise when supplying a selector as a literal string. #selector() takes a function reference, and the compiler will check that the function really exists and will resolve the reference to an Objective-C selector for you. Thus, you can't readily make any mistake.

(EDIT: Okay, yes you can. You can be a complete lunkhead and set the target to an instance that doesn't implement the action message specified by the #selector. The compiler won't stop you and you'll crash just like in the good old days. Sigh...)

A function reference can appear in any of three forms:

  • The bare name of the function. This is sufficient if the function is unambiguous. Thus, for example:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
    let selector = #selector(test)
    }

    There is only one test method, so this #selector refers to it even though it takes a parameter and the #selector doesn't mention the parameter. The resolved Objective-C selector, behind the scenes, will still correctly be "test:" (with the colon, indicating a parameter).

  • The name of the function along with the rest of its signature. For example:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
    let selector = #selector(test(_:))
    }

    We have two test methods, so we need to differentiate; the notation test(_:) resolves to the second one, the one with a parameter.

  • The name of the function with or without the rest of its signature, plus a cast to show the types of the parameters. Thus:

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
    let selector1 = #selector(test as (Int) -> Void)
    // or:
    let selector2 = #selector(test(_:) as (Int) -> Void)
    }

    Here, we have overloaded test(_:). The overloading cannot be exposed to Objective-C, because Objective-C doesn't permit overloading, so only one of them is exposed, and we can form a selector only for the one that is exposed, because selectors are an Objective-C feature. But we must still disambiguate as far as Swift is concerned, and the cast does that.

    (It is this linguistic feature that is used — misused, in my opinion — as the basis of the answer above.)

Also, you might have to help Swift resolve the function reference by telling it what class the function is in:

  • If the class is the same as this one, or up the superclass chain from this one, no further resolution is usually needed (as shown in the examples above); optionally, you can say self, with dot-notation (e.g. #selector(self.test), and in some situations you might have to do so.

  • Otherwise, you use either a reference to an instance for which the method is implemented, with dot-notation, as in this real-life example (self.mp is an MPMusicPlayerController):

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
    target: self.mp, action: #selector(self.mp.pause))

    ...or you can use the name of the class, with dot-notation:

    class ClassA : NSObject {
    @objc func test() {}
    }
    class ClassB {
    func makeSelector() {
    let selector = #selector(ClassA.test)
    }
    }

    (This seems a curious notation, because it looks like you're saying test is a class method rather than an instance method, but it will be correctly resolved to a selector nonetheless, which is all that matters.)



Related Topics



Leave a reply



Submit