简体   繁体   中英

Swift/iOS: How to use inout parameters in functions with AnyObject/Any or Pointers

I'm trying to write a function that takes a variable pointer and a descriptor/key and sets a new value for the variable. Ideally the pointer should be either of an object or a primitive, but I could also live with separate functions (or an additional parameter). In my code I retrieve the new value from a database also using the key, but in the following example I simplified it with dummy values so that it can be used easily in a playground:

import UIKit

func setValue(inout object: AnyObject, key: String) {
    switch key {
    case "String":
        object = "A String"
    case "UIColor":
        object = UIColor.whiteColor()
    case "Bool":
        object = true
    default:
        println("Unhandled key: \(key)")
    }
}

var string: String = "Default String"
var color: UIColor = UIColor.blackColor()
var bool: Bool = false

setValue(&string, "String")
setValue(&color, "UIColor")
setValue(&bool, "Bool")

I get the following error:

"Cannot invoke 'setValue' with an argument list of type '(inout String, key String)'"

I understand that I'm mixing Objects and Primitives here. I also tried to break it down and separate these into two functions, but even that fails:

func setValue(inout object: AnyObject, key: String) {
    switch key {
    case "UIColor":
        object = UIColor.whiteColor()
    default:
        println("Unhandled key: \(key)")
    }
}

var color: UIColor = UIColor.blackColor()
setValue(&color, "UIColor")

This also gives the same error:

"Cannot invoke 'setValue' with an argument list of type '(inout UIColor, key String)'"

If I change 'AnyObject' to 'UIColor' it works, but the point of the function is, that it takes any variable type or at least any object type (I'd write a second function using "Any" for primitives then or add another parameter)

In Objective-C I was using pointers, transferring the approach to Swift also doesn't work, same result:

func setValue(object: UnsafeMutablePointer<AnyObject>, key: String) {
    switch key {
    case "String":
        object.memory = "A String"
    case "UIColor":
        object.memory = UIColor.whiteColor()
    case "Bool":
        object.memory = true
    default:
        println("Unhandled key: \(key)")
    }
}

Does anybody have an idea what I'm missing here? Any help is much appreciated!

Thanks!

Better you can create a generic method like below:

func setValue<T>(inout object:T, key: String) {
    switch key {
    case "String":
        object = ("A String" as? T)!
    case "UIColor":
        object = (UIColor.whiteColor() as? T)!
    case "Bool":
        object = (true as? T)!
    default:
        println("Unhandled key: \(key)")
    }
}

And calling will be like this:

setValue(&string, key: "String")
setValue(&color, key: "UIColor")
setValue(&bool, key: "Bool")

Hope it helps!

The right way to do this is to use overloading, and letting the compiler choose the appropriate bit of code at compile time, instead of switching off a string at runtime:

func setValue(inout object: String) {
    object = "A String"
}

func setValue(inout object: UIColor) {
    object = UIColor.whiteColor()
}

func setValue(inout object: Bool) {
    object = true
}

func setValue(inout object: Any) {
    println("Unhandled key: \(key)")
}

This approach wouldn't work when you have an Any and you want to indicate to the function what type is contained in the Any … but in this case, the reason you have problems is that the compiler does know what the types are, so you can take advantage of that.

-Use Any instead of AnyObject to cover value types and structs.

-You may need to cast to Any before calling your method.

Example:

func swapAny( inout obj1: Any, inout obj2: Any )
{
    let temp = obj1
    obj1 = obj2
    obj2 = temp
}

use:

var fooString: Any = "Foo"
var barString: Any = "Bar"

swapAny(&fooString, obj2: &barString)
println(fooString)//prints "Bar"

Protocol support for inout

When you cast a class to a protocol you end up with an immutable reference, which cant be used in inout function parameters. So you can:

  1. Use Method overloading (which can duplicate your code and make it congenitally harder to read, even if you refactor)
  2. Not use inout (this is what you should do)
  3. Or you can do this:

-

protocol IPositional{
    func setPosition(position:CGPoint)
}
extension IPositional{
    var positional:IPositional {get{return self as IPositional}set{}}
}
class A:IPositional{
    var position:CGPoint = CGPoint()
    func setPosition(position:CGPoint){
        self.position = position
    }
}
func test(inout positional:IPositional){
    positional.setPosition(CGPointMake(10,10))
}
var a = A()
test(&a.positional)
a.position//output: (10.0, 10.0)

Conclusion:
The benefit of doing it this way: Is that you can now have one "inout method" for all classes that implements IPositional But I recommend going with option 2. (Not using inout)

The type of object should match the type of parameter, including AnyObject :

func setValue(inout object: AnyObject, key: String) {
    switch key {
    case "String":
        object = "A String"
    case "UIColor":
        object = UIColor.whiteColor()
    case "Bool":
        object = true
    default:
        print("Unhandled key: \(key)")
    }
}

var string: AnyObject = "Default String"
var color: AnyObject = UIColor.blackColor()
var bool: AnyObject = false

setValue(&string, key: "String")
setValue(&color, key: "UIColor")
setValue(&bool, key: "Bool")

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM