Make Property of Type and Also Conform to Protocol in Swift

Make property of type and also conform to protocol in Swift

I can't think of a good way to express this in Swift. The syntax for a type is:

type → array-type­ | dictionary-type­ | function-type­ | type-identifier­ |
tuple-type­ | optional-type­ | implicitly-unwrapped-optional-type­ |
protocol-composition-type­ | metatype-type­

What you're looking for would be a kind of protocol-combination-type that also accepts a base class. (Protocol-combination-type is protocol<Proto1, Proto2, Proto3, …>.) However, this is not currently possible.

Protocols with associated type requirements are allowed to have typealiases that specify a required base class and required protocols, but these also require the types to be known at compile-time, so it's unlikely to be a viable option for you.

If you're really into it, you can define a protocol with the parts of the interface of UIViewController that you use, use an extension to add conformance, and then use protocol<UIViewControllerProtocol, CustomProtocol>.

protocol UIViewControllerProtocol {
func isViewLoaded() -> Bool
var title: String? { get set }
// any other interesting property/method
}

extension UIViewController: UIViewControllerProtocol {}

class MyClass {
var controller: protocol<UIViewControllerProtocol, CustomProtocol>
}

Swift Property that conforms to a Protocol and Class

Update for Swift 4

Swift 4 has added support for representing a type as a class that conforms to a protocol. The syntax is Class & Protocol. Here is some example code using this concept from "What's New in Swift" (session 402 from WWDC 2017):

protocol Shakeable {
func shake()
}
extension UIButton: Shakeable { /* ... */ }
extension UISlider: Shakeable { /* ... */ }

// Example function to generically shake some control elements
func shakeEm(controls: [UIControl & Shakeable]) {
for control in controls where control.isEnabled {
control.shake()
}
}

As of Swift 3, this method causes problems because you can't pass in the correct types. If you try to pass in [UIControl], it doesn't have the shake method. If you try to pass in [UIButton], then the code compiles, but you can't pass in any UISliders. If you pass in [Shakeable], then you can't check control.state, because Shakeable doesn't have that. Swift 4 finally addressed the topic.

Old Answer

I am getting around this problem for the time being with the following code:

// This class is used to replace the UIViewController<UITableViewDelegate> 
// declaration in Objective-C
class ConformingClass: UIViewController, UITableViewDelegate {}

class AClass: UIViewController {
@IBOutlet weak var anotherThing: UILabel!
var thing: ConformingClass!
}

This seems hackish to me. If any of the delegate methods were required, then I would have to implement those methods in ConformingClass (which I do NOT want to do) and override them in a subclass.

I have posted this answer in case anyone else comes across this problem and my solution helps them, but I am not happy with the solution. If anyone posts a better solution, I will accept their answer.

Swift protocol property of type Set Self

The way to put constraints in protocols is similar to how you specify protocol conformance (they're the same thing after all)

protocol Groupable: Hashable
{
var parent: Self? { get }
var children: Set<Self> { get }
}

How to make a swift class conform to a protocol that requires a protocol-conform type for one of its properties?

You can do this with associated types.

Declare an associated type in ProtocolA:

protocol ProtocolA {
associatedtype BType: ProtocolB
var b: BType? { get } // Note the change in the type of b
}

This is saying that b is an optional of some type that conforms to ProtocolB. What type exactly? That depends on the conformers of ProtocolA.

In A's extension, you specify what BType is:

extension A: ProtocolA {
typealias BType = B
}

And you're done!

As a result of this, you won't be able to use ProtocolA as the type of a variable:

var protocolA: ProtocolA? // error

Because you don't know what b's type is.

How to make a struct conforms to a protocol which has a property conforms to another protocol in swift 4?

You are asking to describe the kind of type that data can hold, rather than the actual type. That means it needs to be an associatedtype:

protocol XFNovelApiResponse: Decodable {
associatedtype DataType: Decodable
var data: DataType {get}
var error: NovelApiError {get}
}

Note that protocols with associated types can generate a lot of complexity, so you should carefully consider if this protocol is really necessary, or if XFNovelApiResponse could, for example, be generic instead. It depends on what other types implement this protocol.

For example, another implementation of a similar set of data structures without protocols would be:

struct XFNovelApiResponse<DataType: Decodable>: Decodable {
var data: DataType
var error: NovelApiError
}

struct NovelsData: Decodable {
}

struct NovelApiError: Decodable {

let msg: String
let errorCode: String
}

let novels = XFNovelApiResponse(data: NovelsData(),
error: NovelApiError(msg: "", errorCode: ""))

Alternately, you can implement this with classes and subclasses, which allow inheritance. Structs do not inherit from protocols, they conform to protocols. If you really mean inheritance, classes are the right tool. (But I expect generics are the better solution here.)

Swift - Conform third-party type to my own protocol with conflicting requirement

Inspired by Jonas Maier's comment, I found what I believe to be an architecturally adequate solution to this problem. As Jonas said, function overloading exhibits the behavior that I'm looking for. I'm starting to think that maybe protocol requirements should only ever be functions, and not properties. Following this line of thinking, my protocol will now be:

protocol CursorInput {
func getCursorLocation () -> CGPoint
func setCursorLocation (_ newValue: CGPoint)
}

(Note that in this answer I'm making it settable as well, unlike in the original post.)

I can now retroactively conform AATrackpad to this protocol without conflict:

extension AATrackpad: CursorInput {
func getCursorLocation () -> CGPoint {
return CGPoint(x: self.cursorLocation.x, y: self.cursorLocation.y)
}
func setCursorLocation (_ newValue: CGPoint) {
self.cursorLocation = AAPoint(newValue)
}
}

Important - This will still compile even if AATrackpad already has a function func getCursorLocation () -> AAPoint, which has the same name but a different type. This behavior is exactly what I was wanting from my property in the original post. Thus:

The major problem with including a property in a protocol is that it can render certain concrete types literally incapable of conforming to that protocol due to namespace collisions.

After solving this in this way, I have a new problem to solve: there was a reason I wanted cursorLocation to be a property and not a function. I definitely do not want to be forced to use the getPropertyName() syntax all across my app. Thankfully, this can be solved, like this:

extension CursorInput {
var cursorLocation: CGPoint {
get { return self.getCursorLocation() }
set { self.setCursorLocation(newValue) }
}
}

This is what is so cool about protocol extensions. Anything declared in a protocol extension behaves analogously to a default argument for a function - only used if nothing else takes precedence. Because of this different mode of behavior, this property does not cause a conflict when I conform AATrackpad to CursorInput. I can now use the property semantics that I originally wanted and I don't have to worry about namespace conflicts. I'm satisfied.


"Wait a second - now that AATrackpad conforms to CursorInput, doesn't it have two versions of cursorLocation? If I were to use trackpad.cursorLocation, would it be a CGPoint or an AAPoint?

The way this works is this - if within this scope the object is known to be an AATrackpad then Alice's original property is used:

let trackpad = AATrackpad()
type(of: trackpad.cursorLocation) // This is AAPoint

However, if the type is known only to be a CursorInput then the default property that I defined gets used:

let cursorInput: CursorInput = AATrackpad()
type(of: cursorInput.cursorLocation) // This is CGPoint

This means that if I do happen to know that the type is AATrackpad then I can access either version of the property like this:

let trackpad = AATrackpad()
type(of: trackpad.cursorLocation) // This is AAPoint
type(of: (trackpad as CursorInput).cursorLocation) // This is CGPoint

and it also means that my use case is exactly solved, because I specifically wanted not to know whether my cursorInput happens to be an AATrackpad or a BBMouse - only that it is some kind of CursorInput. Therefore, wherever I am using my cursorInput: CursorInput?, its properties will be of the types which I defined in the protocol extension, not the original types defined in the class.


There is one possibility that a protocol with only functions as requirements could cause a namespace conflict - Jonas pointed this out in his comment. If one of the protocol requirements is a function with no arguments and the conforming type already has a property with that name then the type will not be able to conform to the protocol. This is why I made sure to name my functions including verbs, not just nouns (func getCursorLocation () -> CGPoint) - if any third-party type is using a verb in a property name then I probably don't want to be using it anyway :)



Related Topics



Leave a reply



Submit