How to Have a Swift Protocol Without Functions

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.

Can I have a Swift protocol without functions

Persons are not the only not the only things that you may want to name. Pets have names, roads have names, heck, some people name their cars.

What if we want to name each object in a collection of different objects? If we store those objects in a collection of Any, we don't have any way to guarentee that all objects have names.

This is where protocols come in. By creating a Nameable protocol, we can create a collection of Nameable objects, and be certain that all the objects within are guaranteed to have a name.

Here's an example:

protocol Nameable {
var name: String {get}
}

struct Person : Nameable {
let name: String
let age: Int
// other properties of a Person
}

struct Pet : Nameable {
let name: String
let species: String
// other properties of a Pet
}

struct Car : Nameable {
let name: String
let horsepower: Double
// other properties of a Car
}

let namableItems: [Nameable] = [
Person(name: "Steve", age: 21),
Pet(name: "Mittens", species: "Cat"),
Car(name: "My Pride and Joy", horsepower: 9000)
]

for nameableItem in namableItems {
print("\(nameableItem.name) is a \(nameableItem.dynamicType).")
}

Which prints:

Steve is a Person.

Mittens is a Pet.

My Pride and Joy is a Car.

You can try it here.

Optional Protocol methods in swift without using @objc

You can define default func implementation by:

protocol Opt {
func requiredFunc()
func optionalFunc()
}

extension Opt {
func optionalFunc() {}
}

With this you don't have to implement optionalFunc() in classes conforming to Opt, because they already have their default implementation.

Add a function to existing protocol without function declaration using extension

You cannot extend an existing protocol by adding new requirements (aka methods) to it. However you can derive the existing protocol, and add the new method to that protocol:

protocol MyTableViewDelegate: UITableViewDelegate {
optional func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize)
}

, and call this new method from the table view:

class MyTableView: UITableView {
override open var contentSize: CGSize {
didSet {
(self.delegate as? MyTableViewDelegate)?.tableView?(self, didUpdateContentSize: contentSize)
}
}
}

Note that the above code subclasses UITableView instead of extending it, as overriding methods in extensions is not recommendable (though if you really want you can add the code in the extension over UITableView). This should not cause too many headaches, as the table view class can be easily changed in storyboards/xibs/code.

You can then have your view controller conform to the extended delegate, and you should be good to go:

extension MyViewController: MyTableViewDelegate {
func tableView(_ tableView: UITableView, didUpdateContentSize contentSize: CGSize) {
// do your stuff
}
}

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?

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.

Protocol function implementation without actually conforming to a protocol

You must conform to CustomStringConvertible if you want string interpolation to use your description property.

You use string interpolation in Swift like this:

"Here's my linked list: \(linkedList)"

The compiler basically turns that into this:

String(stringInterpolation:
String(stringInterpolationSegment: "Here's my linked list: "),
String(stringInterpolationSegment: linkedList),
String(stringInterpolationSegment: ""))

There's a generic version of String(stringInterpolationSegment:) defined like this:

public init<T>(stringInterpolationSegment expr: T) {
self = String(describing: expr)
}

String(describing: ) is defined like this:

public init<Subject>(describing instance: Subject) {
self.init()
_print_unlocked(instance, &self)
}

_print_unlocked is defined like this:

internal func _print_unlocked<T, TargetStream : TextOutputStream>(
_ value: T, _ target: inout TargetStream
) {
// Optional has no representation suitable for display; therefore,
// values of optional type should be printed as a debug
// string. Check for Optional first, before checking protocol
// conformance below, because an Optional value is convertible to a
// protocol if its wrapped type conforms to that protocol.
if _isOptional(type(of: value)) {
let debugPrintable = value as! CustomDebugStringConvertible
debugPrintable.debugDescription.write(to: &target)
return
}
if case let streamableObject as TextOutputStreamable = value {
streamableObject.write(to: &target)
return
}

if case let printableObject as CustomStringConvertible = value {
printableObject.description.write(to: &target)
return
}

if case let debugPrintableObject as CustomDebugStringConvertible = value {
debugPrintableObject.debugDescription.write(to: &target)
return
}

let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}

Notice that _print_unlocked only calls the object's description method if the object conforms to CustomStringConvertible.

If your object doesn't conform to CustomStringConvertible or one of the other protocols used in _print_unlocked, then _print_unlocked creates a Mirror for your object, which ends up just printing the object's type (e.g. MyProject.LinkedList) and nothing else.

Accessing optional method in protocol returns scope error


class MyClass: NSObject, MyProtocol { ... }

This says that MyClass implements MyProtocol, which allows, but does not require myFunction. The compiler can clearly see that MyClass does not implement myFunction, so self.myFunction() isn't a thing. It could be a thing, but it's clearly not a thing. The protocol doesn't play into this.

You could "work around" this by saying "I don't mean MyClass; I mean MyProtocol":

func doSomething() {
(self as MyProtocol).myFunction?()
}

But this is kind of silly IMO. self doesn't implement myFunction and this is known at compile time.

If the point is to implement this via subclassing, the correct way to do that is to just implement it as empty in the superclass:

class MyClass: NSObject, MyProtocol {
func myFunction() {}

func doSomething() {
myFunction()
}
}

This is precisely equivalent to "do nothing" while allowing overrides in subclasses.

Why does adding a Swift protocol in some situations not require all the normally required methods?


Why does my code compile without fulfilling all the protocol requirements?

What you are seeing here are optional protocol requirements. They are here because Swift code needs to interact with Objective-C, and Objective-C has them.

All methods except centralManagerDidUpdateState declared in CBCentralManagerDelegate are optional, from here:

The only required method is centralManagerDidUpdateState(_:); the central manager calls this when its state updates, thereby indicating the availability of the central manager.

And SFSpeechRecognizerDelegate, only contains one optional method, from here:

Use this protocol's optional method to track those changes and provide an appropriate response.


why does the code not compile if you remove the superclasses then?

This is because both of those protocols in your question also inherit from NSObjectProtocol, so they actually also have the additional requirements of NSObjectProtocol. UIViewController and CBCentralManager both inherits from NSObject, so they satisfy the NSObjectProtocol requirements, but your Swift class without a superclass doesn't.

Protocols with optional requirements don't have to inherit from NSObjectProtocol though. It's just that most of them are that way in the framework. You can for example do:

@objc protocol Foo {
@objc optional func foo()
}

class FooClass : Foo {
// compiles fine!
}

Swift optional method in protocol without objc

What I do in this situation is return a class (compatible with Objective-C) which wraps the Swift struct. In other words, just make it so that everything Objective-C sees is compatible with Objective-C, and inside that you can do anything you like.



Related Topics



Leave a reply



Submit