Home » Swift » Swift Property that conforms to a Protocol and Class

Swift Property that conforms to a Protocol and Class

Posted by: admin November 30, 2017 Leave a comment

Questions:
@property (strong, nonatomic) UIViewController<UITableViewDelegate> *thing;

I want to implement a property like in this Objective-C code in Swift. So here is what I’ve tried:

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    var thing: T!
}

This compiles. My problem comes when I add properties from the storyboard. The @IBOutlet tag generates an compiler error.

class AClass<T: UIViewController where T: UITableViewDelegate>: UIViewController {
    @IBOutlet weak var anotherThing: UILabel!  // error
    var thing: T!
}

The error:

Variable in a generic class cannot be represented in Objective-C

Am I implementing this right? What can I do to fix or get around this error?

EDIT:

Swift 4 finally has a solution for this problem. See my updated answer.

Answers:

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.state.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.

Questions:
Answers:

It’s not the ideal solution, but you can use a generic function instead of a generic class, like this:

    class AClass: UIViewController {
      @IBOutlet weak var anotherThing: UILabel!
      private var thing: UIViewController?

      func setThing<T: UIViewController where T: UITableViewDelegate>(delegate: T) {
        thing = delegate
      }
    }

Questions:
Answers:

I came across the same issue, and also tried the generic approach. Eventually the generic approach broke the entire design.

After re-thinking about this issue, I found that a protocol which cannot be used to fully specify a type (in other words, must come with additional type information such as a class type) is unlikely to be a complete one. Moreover, although the Objc style of declaring ClassType<ProtocolType> comes handy, it disregards the benefit of abstraction provided by protocol because such protocol does not really raise the abstraction level. Further, if such declaration appears at multiple places, it has to be duplicated. Even worse, if multiple declarations of such type are interrelated (possibly a single object will be passed around them ), the programme becomes fragile and hard to maintain because later if the declaration at one place needs to be changed, all the related declarations have to be changed as well.

Solution

If the use case of a property involves both a protocol (say ProtocolX) and some aspects of a class (say ClassX), the following approach could be taken into account:

  1. Declare an additional protocol that inherits from ProtocolX with the added method/property requirements which ClassX automatically satisfy. Like the example below, a method and a property are the additional requirements, both of which UIViewController automatically satisfy.

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var navigationController: UINavigationController? { get }
        func performSegueWithIdentifier(identifier: String, sender: AnyObject?)
    }
    
  2. Declare an additional protocol that inherits from ProtocolX with an additional read-only property of the type ClassX. Not only does this approach allow the use of ClassX in its entirety, but also exhibits the flexibility of not requiring an implementation to subclass ClassX. For example:

    protocol CustomTableViewDelegate: UITableViewDelegate {
        var viewController: UIViewController { get }
    }
    
    // Implementation A
    class CustomViewController: UIViewController, UITableViewDelegate {
    
        var viewController: UIViewController { return self }
    
        ... // Other important implementation
    }
    
    // Implementation B
    class CustomClass: UITableViewDelegate {
    
        private var _aViewControllerRef: UIViewController // Could come from anywhere e.g. initializer
        var viewController: UIViewController { return _aViewControllerRef } 
    
        ... // UITableViewDelegate methods implementation
    }
    

PS. The snippet above are for demonstration only, mixing UIViewController and UITableViewDelegate together is not recommended.

Edit for Swift 2+: Thanks for @Shaps’s comment, the following could be added to save having to implement the desired property everywhere.

extension CustomTableViewDelegate where Self: UIViewController { 
    var viewController: UIViewController { return self } 
}

Questions:
Answers:

you can declare a delegate in Swift like this:

weak var delegate : UITableViewDelegate?

It will work with even hybrid(Objective-c and swift) project. Delegate should be optional & weak because its availability is not guaranteed and weak does not create retain cycle.

You are getting that error because there are no generics in Objective-C and it will not allow you to add @IBOutlet property.

Edit: 1. Forcing a type on delegate

To force that delegate is always a UIViewController you can implement the custom setter and throw exception when its not a UIViewController.

weak var _delegate : UITableViewDelegate? //stored property
var delegate : UITableViewDelegate? {
    set {
        if newValue! is UIViewController {
            _delegate = newValue
        } else {
            NSException(name: "Inavlid delegate type", reason: "Delegate must be a UIViewController", userInfo: nil).raise()
        }
    }
    get {
        return _delegate
    }
}