How do properties work in Swift?

In Objective-C, when I declare a writeable property, the compiler generates a "set" method for that property, and I can use the "respondsToSelector" function to check if the property exists in a generic way (for example if I want to automatically set a bunch of properties from a dictionary).


In Swift this does not appear to be the case. If I just write "var name: String?" in my class, I don't get a "setName" function that I can call.


Is the naming convention different for Swift, or does it just not use methods to set properties? If the latter, is there some other way I can set the value of a property from its name?


Thanks,

Frank

Replies

There's no really direct way to do this, AFAIK.


For pure Swift properties, there is no setXXX method hiding behind the scenes. (For @objc properties, there is, since it has to follow the Obj-C rules.)


Instead, Swift has a KeyPath type, which is a way of getting something like a selector for properties. However, I don't think there's any way of asking a Swift type what KeyPaths it supports, or whether they representable writeable properties.


Depending on what you're Really Trying to Do™, a different approach might be better. For example, if you dictionary comes from generically decoded JSON, you may be able to leverage Codable conformance for a custom type to eliminate the dictionary completely.


Another sort of hacky approach is for the types with relevant properties to vend (say) dictionaries with property names (String) as keys, and either KeyPaths or closures as values.


But the better answer is that your current approach, while very suitable and natural for Obj-C, isn't very Swifty. You might want to investigate a different way of solving the problem.

Thanks for all the information.


Basically what I'm doing is assigning object properties from a Dictionary. So I have a lot of code that looks like this:


name = dict["name"] as? String

age = dict["age"] as? Int


In the past, when working with Objective-C, I was able to partially automate this process by using respondsToSelector:, methodSignatureForSelector:, and NSInvocation to set the properties automatically (assuming the property name matched the name in the dictionary, which it usually does).


I'd like to be able to do the same thing in Swift, and thought if anything it would be easier, but it's not turning out that way.


I did try marking my properties with @objC, and that does appear to generate the setName: methods for me. However, the next thing I need to do is call methodSignatureForSelector:, a function that appears to be unavailable in Swift. I'm guessing that method(for:) may be the equivalent, but that function returns something called an IMP which isn't even documented (and doesn't have methods I need to inspect it, such as numberOfArguments).


I'll probably end up just not doing this and being somewhat disappointed that Apple's bold new "modern" language isn't even as capable as Objective-C.


I work frequently in Java and C# and those languages have excellent reflection. You can serialize and deserialze entire complex object graphs without writing any custom code.


Frank

I found one solution -- I simply created an Objective-C base class and implemented my existing code!


I wasn't sure I could have a Swift class extend an Objective-C class, but it works fine.


As long as I declare my properties as @objc, the old ObjC code works fine.


Frank

I simply created an Objective-C base class and implemented my existing code!

You don’t need to use Objective-C here, you just need to inherit from

NSObject
. For example:
class Person: NSObject {
    convenience init(properties: [String:Any]) {
        self.init()
        for (k, v) in properties {
            if self.responds(to: setterForKey(k)) {
                self.setValue(v, forKey: k)
            }
        }
    }
    @objc dynamic var name: String = ""
    @objc dynamic var age: Int = 0
}

func setterForKey(_ key: String) -> Selector {
    let sel = "set\(String(key.first!).uppercased())\(key.dropFirst()):"
    return Selector(sel)
}

let p = Person(properties: ["name": "Quinn", "fullName": "unknown"])
print(p.name, p.age)    // -> Quinn 0

Note If you’re going all in on Objective-C dynamism, you should make sure to declare your properties

dynamic
. This ensures that references to the property always go through the Objective-C runtime. You can think of it as an extension to
@objc
.
@objc
means that references can be made via the Objective-C runtime;
dynamic
means that they must be made that way.

[I’m] somewhat disappointed that Apple's bold new "modern" language isn't even as capable as Objective-C.

Well, I hope the answers on this thread have reassured you that Swift is at least as capable as Objective-C in this regard. However, I want to step back and discuss the big picture some more.

From the discussion here it should be clear that Swift leans heavily on the Objective-C runtime for its dynamic features. This is not an accident. Swift must interoperate with Apple frameworks, and Apple frameworks have a long history of using Objective-C for dynamism.

Swift’s native dynamism story is only just beginning. This is important because the current approach, relying on Objective-C, only works an Apple platforms. All those Swift-on-server folks miss out )-:

However, you have to understand that one of Swift’s main goals is safety, and there’s often a tradeoff between dynamism and safety. Many of the best features of Swift allow you to do dynamic things in a safe way. For example, in Objective-C you might use dynamic features to decode JSON, but in Swift you can do that using

Codable
:
struct Person: Codable {
    var name: String
    var age: Int?
}

let jsonData = try JSONSerialization.data(withJSONObject: [
    "name": "Quinn", 
    "fullName": "unknown"
], options: [])
let p = try JSONDecoder().decode(Person.self, from: jsonData)
print(p.name, p.age)    // -> Quinn nil

The nice thing about this is that it’s type safe. The decoder will fail if

name
is not a string, or is missing (because the
name
property is not optional).

If your a fan of dynamism then I encourage you to explore this and other dynamic features in Swift, including:

And this is definitely not the end of the story. For example, Swift’s current reflection support is a known problem and there’s lots of enthusiasm for addressing that. If you want to get involved in these developments, hop on over to Swift Evolution.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, I appreciate the response and will look into your suggestions. I wasn't aware of the Codable interface, that looks promising.


My original Objective-C code uses a function called methodSignatureForSelector, which I couldn't figure out how to call from Swift. I use this and NSInvocation to check the number and type of parameters before setting the value. So that's why I decided it would be faster to just use the old code directly instead of trying to port it. I don't mind having a small amount of Obj-C code in my project.


My goal isn't to produce a generic serializer that I plan to distribute or even reuse in other projects, I just want to save myself some time.


Frank

I use this and

NSInvocation
to check the number and type of parameters before setting the value.

Wow,

NSInvocation
, that’s really tightly bound to the Objective-C world. I’m pretty sure that’s not even exposed to Swift.

I don't mind having a small amount of Obj-C code in my project.

Yeah, that’s cool. If you’re coming from the Objective-C world, it’s fine to lean on the Objective-C runtime for stuff like this. Swift integrates with it quite well, although there are some tricks to learn.

My hope is that the next time you write code like this you’ll take another look at

Codable
and key paths to see if you can achieve your goals in a type safe way.

Of course that may not be easy. There are still things in this space that Swift could do better. For example, writing your own coder (equivalent to

JSONDecoder
in the example I posted) is currently a lot harder than it should be.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"