Swift: Setting an Optional Property of a Protocol

Optional Variables in protocol is possible?

protocol TestProtocol {
var name : String {set get}
var age : Int {set get}
}

Provide a default extension for the protocol. Provide the default implementation for all the variables set and get which u want them to be optional.

In below protocol, name and age are optional.

 extension TestProtocol {

var name: String {
get { return "Any default Name" } set {}
}
var age : Int { get{ return 23 } set{} }
}

Now if I am conforming above protocol to any other class, like

class TestViewController: UIViewController, TestProtocol{
var itemName: String = ""

**I can implement the name only, and my objective is achieved here, that the controller will not give a warning that "TestViewController does not conform to protocol TestProtocol"**

var name: String {
get {
return itemName ?? ""
} set {}
}
}

Swift: setting an optional property of a protocol

It's impossible in Swift (yet?). Referenced from an ADF thread:

Optional property requirements, and optional method requirements that return a value, will always return an optional value of the appropriate type when they are accessed or called, to reflect the fact that the optional requirement may not have been implemented.

So it's no surprise to get optional values easily. However, setting a property requires implementation to be guaranteed.

Override swift protocol property to optional

The existence of this property in XMLBaseMappable protocol is crucial in order to function correctly the whole library.

Having said that, you can't omit the implementation of this property in your structs and classes but you can "hide" it in a super class. Using this:

class BasicXMLMappable: XMLMappable {
var nodeName: String!

required init(map: XMLMap) {

}

func mapping(map: XMLMap) {

}
}

You can have XMLMappable objects that extends BasicXMLMappable and they don't have to implement nodeName property:

class TestBasicXMLMappable: BasicXMLMappable {

// Your custom properties

required init(map: XMLMap) {
super.init(map: map)
}

override func mapping(map: XMLMap) {
// Map your custom properties
}
}

Edit:
As of version 1.5.1 you can use XMLStaticMappable for implementing XMLMapper in an extension. For example:

class CustomClass {
var property: String?
}

extension CustomClass: XMLStaticMappable {
var nodeName: String! {
get {
return "default"
}
set(newValue) {

}
}

static func objectForMapping(map: XMLMap) -> XMLBaseMappable? {
// Initialize CustomClass somehow
return CustomClass()
}

func mapping(map: XMLMap) {
property <- map["property"]
}
}

Hope this helps.

Swift protocol with properties that are not always used

Unlike in Objective-C, you cannot define optional protocol requirements in pure Swift. Types that conform to protocols must adopt all the requirements specified.

One potential way of allowing for optional property requirements is defining them as optionals, with a default implementation of a computed property that just returns nil.

protocol Nameable {
var name : String? { get }
var fullName : String? { get }
var nickname : String? { get }
}

extension Nameable {
var name : String? { return nil }
var fullName : String? { return nil }
var nickname : String? { return nil }
}

struct Person : Nameable {

// Person now has the option not to implement the protocol requirements,
// as they have default implementations that return nil

// What's cool is you can implement the optional typed property requirements with
// non-optional properties – as this doesn't break the contract with the protocol.
var name : String
}

let p = Person(name: "Allan")
print(p.name) // Allan

However the downside to this approach is that you potentially pollute conforming types with properties that they don't implement (fullName & nickName in this case).

Therefore if it makes no logical sense for a type to have these properties (say you wanted to conform City to Nameable – but cities don't (really) have nicknames), you shouldn't be conforming it to Nameable.

A much more flexible solution, as you say, would be to define multiple protocols in order to define these requirements. That way, types can choose exactly what requirements they want to implement.

protocol Nameable {
var name : String { get }
}

protocol FullNameable {
var fullName : String { get }
}

protocol NickNameable {
// Even types that conform to NickNameable may have instances without nicknames.
var nickname : String? { get }
}

// A City only needs a name, not a fullname or nickname
struct City : Nameable {
var name : String
}

let london = City(name: "London")

// Person can have a name, full-name or nickname
struct Person : Nameable, FullNameable, NickNameable {
var name : String
var fullName: String
var nickname: String?
}

let allan = Person(name: "Allan", fullName: "Allan Doe", nickname: "Al")

You could even use protocol composition in order to define a typealias to represent all three of these protocols for convenience, for example:

typealias CombinedNameable = Nameable & FullNameable & NickNameable

struct Person : CombinedNameable {
var name : String
var fullName: String
var nickname: String?
}

Swift Protocol Optional conformance via Non-Optional

If the protocol provides a default implementation that returns an optional:

protocol SomeProtocol {
var foo: Int? { get }
}

extension SomeProtocol {
var foo: Int? { return nil }
}

protocol-conforming types can then provide an overriding non-optional version of the variable/function:

struct StructB: SomeProtocol {
let foo: Int
}

I found this discussed on the Swift Evolution forum:

At the first glance I thought there is a rule that allows us to satisfy protocol requirements with non-optional types, but this resulted in an error. Only after further investigation I noticed that a default implementation must exist in order to 'kind of override' the requirement with a non-optional version.

https://forums.swift.org/t/how-does-this-rule-work-in-regard-of-ambiguity/19448

This Swift team also discusses allowing non-optional types to satisfy optional-value protocols:

Would it make any sense to allow protocol requirement satisfaction with non-optional types, like with failable init's? (Probably with some implicit optional promotion.)

Yep, totally! Except for the part where this changes the behavior of existing code, so we'd have to be very careful about it. This is considered part of [SR-522] Protocol funcs cannot have covariant returns

which is tracked on Stack Overflow here:

Why can't a get-only property requirement in a protocol be satisfied by a property which conforms?

swift protocol conformance when same property name is optional

There is no elegant solution to your problem other than renaming the conflicting property on the conforming type.

Swift doesn't allow 2 properties of the same name to exist on a type even if their types are different. On the other hand, Int? and Int are completely different types, so you cannot have trackNumber: Int fulfil the protocol requirement of trackNumber: Int?.

The only solution (other than changing the type in either the protocol or the struct) is to rename the non-Optional property in SpotifyTrack and make an optional computed property of the same name returning the non-optional one.

protocol Track {
var trackNumber: Int? { get }
}
struct SpotifyTrack {
private let _trackNumber: Int
}
extension SpotifyTrack: Track {
var trackNumber: Int? { _trackNumber }
}

How to define optional methods in Swift protocol?

1. Using default implementations (preferred).

protocol MyProtocol {
func doSomething()
}

extension MyProtocol {
func doSomething() {
/* return a default value or just leave empty */
}
}

struct MyStruct: MyProtocol {
/* no compile error */
}

Advantages

  • No Objective-C runtime is involved (well, no explicitly at least). This means you can conform structs, enums and non-NSObject classes to it. Also, this means you can take advantage of powerful generics system.

  • You can always be sure that all requirements are met when encountering types that conform to such protocol. It's always either concrete implementation or default one. This is how "interfaces" or "contracts" behave in other languages.

Disadvantages

  • For non-Void requirements, you need to have a reasonable default value, which is not always possible. However, when you encounter this problem, it means that either such requirement should really have no default implementation, or that your you made a mistake during API design.

  • You can't distinguish between a default implementation and no implementation at all, at least without addressing that problem with special return values. Consider the following example:

    protocol SomeParserDelegate {
    func validate(value: Any) -> Bool
    }

    If you provide a default implementation which just returns true — it's fine at the first glance. Now, consider the following pseudo code:

    final class SomeParser {
    func parse(data: Data) -> [Any] {
    if /* delegate.validate(value:) is not implemented */ {
    /* parse very fast without validating */
    } else {
    /* parse and validate every value */
    }
    }
    }

    There's no way to implement such an optimization — you can't know if your delegate implements a method or not.

    Although there's a number of different ways to overcome this problem (using optional closures, different delegate objects for different operations to name a few), that example presents the problem clearly.


2. Using @objc optional.

@objc protocol MyProtocol {
@objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
/* no compile error */
}

Advantages

  • No default implementation is needed. You just declare an optional method or a variable and you're ready to go.

Disadvantages

  • It severely limits your protocol's capabilities by requiring all conforming types to be Objective-C compatible. This means, only classes that inherit from NSObject can conform to such protocol. No structs, no enums, no associated types.

  • You must always check if an optional method is implemented by either optionally calling or checking if the conforming type implements it. This might introduce a lot of boilerplate if you're calling optional methods often.

Class With Non-Optional Property Conforming To Protocol With Optional Property

Unfortunately, you can't redeclare the same variable in MyClass with a different type.

What Dennis suggests will work, but if you want to keep your variable in MyClass non-Optional, then you could use a computed property to wrap around your stored variable:

protocol MyProtocol {
var a: String? { get set }
}

class MyClass {
// Must use a different identifier than 'a'
// Otherwise, "Invalid redeclaration of 'a'" error
var b: String
}

extension MyClass: MyProtocol {
var a: String? {
get {
return b
}
set {
if let newValue = newValue {
b = newValue
}
}
}
}

Why do properties modified by @optional become immutable?

From Jordan Rose (who worked on Swift at the time that SE-0070 was implemented) on the forums:

Normally optional requirements add an extra level of optionality:

  • Methods become optional themselves (f.bar?())
  • Property getters wrap the value in an extra level of Optional (if let bar = f.bar)

But there's nowhere to put that extra level of Optional for a property setter. That's really the entire story: we never figured out how to expose optional property setters in a safe way, and didn't want to pick any particular unsafe solution. If someone can think of something that'd be great!

So the answer appears to be: at the time that optional protocol requirements were intentionally limited to Objective-C protocols in Swift (SE-0070), no spelling for an explicit implementation of this was decided on, and it appears that this functionality is uncommon enough that this hasn't really come up since.

Until (and if) this is supported, there are two potential workarounds:

  1. Introduce an explicit method to Playback which assigns a value to assetURL

    • Sadly, this method cannot be named -setAssetURL: because it will be imported into Swift as if it were the property setter instead of a method, and you still won't be able to call it. (This is still true if you mark assetURL as readonly)
    • Also sadly, this method won't be able to have a default implementation, since Objective-C doesn't support default protocol implementations, and you can't give the method an implementation in a Swift extension because you still can't assign to the protocol
  2. Do like you would in Swift and introduce a protocol hierarchy, where, for example, an AssetBackedPlayback protocol inherits from Playback and offers assetURL as a non-@optional-property instead:

    @protocol Playback <NSObject>
    // Playback methods
    @end

    @protocol AssetBackedPlayback: Playback
    @property (nonatomic, nonnull) NSURL *assetURL;
    @end

    You would then need to find a way to expose PlayerController.currentPlayerManager as an AssetBackedPlayback in order to assign the assetURL.


Some additional alternatives from Jordan:

I think the original recommended workaround was "write a static inline function in Objective-C to do it for you", but that's not wonderful either. setValue(_:forKey:) can also be good enough in practice if it's not in a hot path.

The static inline function recommendation can function similarly to a default protocol implementation, but you do need to remember to call that function instead of accessing the property directly.

setValue(_:forKey:) will also work, but incurs a noticeable performance penalty because it supports a lot of dynamism through the Objective-C runtime, and is significantly more complicated than a simple assignment. Depending on your use-case, the cost may be acceptable in order to avoid complexity!

why do I have to mark both the protocol & contained optional functions in a swift protocol as @objc?

UICollectionViewDataSource is imported from ObjC. The auto-generated Swift header doesn't insert @objc on every element. It is common for these headers to be invalid Swift (for example, they define structs and classes without implementations, which isn't valid Swift).

When you're writing Swift (rather than looking at auto-generated headers), you need to tell the compiler that it needs to bridge certain things to ObjC, and you do that with @objc. Imported ObjC doesn't have to be bridged.



Related Topics



Leave a reply



Submit