12

I would like to know what the protocol equivalent is for an initializer in a simple class that only contains initializing functionality and is only intended to be extended in a concrete class.

So probably the easiest is to show the code - I'm looking for the protocol extension equivalent of the following:

import UIKit

class Thing {
    var color:UIColor
    init(color:UIColor) {
        self.color = color
    }
}
class NamedThing:Thing {
    var name:String
    init(name:String,color:UIColor) {
        self.name = name
        super.init(color:color)
    }
}
var namedThing = NamedThing(name: "thing", color: UIColor.blueColor())

I was expecting the code to look something like:

protocol Thing {
    var color:UIColor {get set}
}
extension Thing {
    init(color:UIColor) {
        self.color = color
    }
}
class NamedThing:Thing {
    var name:String
    var color:UIColor
    init(name:String,color:UIColor) {
        self.name = name
        self.init(color:color)
    }
}

I've seen solutions suggested in other StackOverflow questions(eg. How to define initializers in a protocol extension?) but I'm not sure they work nor specifically address this problem of additional parameters in the class initializer.

Community
  • 1
  • 1
Craig Grummitt
  • 2,945
  • 1
  • 22
  • 34

4 Answers4

15

You have to provide a valid chain of init for creating an instance of a class and that limits your options for initializers in protocols.

Since your protocol can't be certain to cover all members of the class that uses it, any initializer you declare in your protocol will need to delegate initialization of the "unknown" members of the class to another initializer provided by the class itself.

I adjusted your example to illustrate this using a basic init() as the delegation initializer for the protocol.

As you can see, this requires that your class implement initial values for all members when init() is called. In this case I did that by providing default values in each member's declaration. And, since there isn't always an actual initial value for some members, I changed them to auto-unwrap optionals.

And to make thinks more interesting, your class cannot delegate initialization to a protocol supplied initializer unless it does so through a convenience initializer.

I wonder if all these restrictions are worth the trouble. I suspect you're trying to use a protocol because you need a bunch of common variables to be initialized consistently between classes that implement the protocol. Perhaps using a delegate class would provide a less convoluted solution than protocols (just a thought).

protocol Thing:AnyObject
{
    var color:UIColor! { get set }
    init()
}

extension Thing 
{    
    init(color:UIColor)
    {  
       self.init()
       self.color = color
    }
}

class NamedThing:Thing 
{
    var name:String!   = nil
    var color:UIColor! = nil

    required init() {}

    convenience init(name:String,color:UIColor) 
    {
        self.init(color:color)
        self.name = name
    }
}
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • 2
    conformance to AnyObject us redundant, implicitly unwrapped name and color is danger and not required (set default values instead) – user3441734 Jan 15 '16 at 03:56
  • Thanks for your insightful comments, you seem to have run into similar roadblocks that I had. I agree with your comment re whether it's worth the trouble. I guess I'm looking for the best practices implementation of the above problem in a protocol-oriented approach. Needing to either give vars default values or make them implicitly unwrapped doesn't seem too convenient nor best practices to me, and makes me wonder whether this smells like using protocols just isn't the correct approach for this problem. @alain-t Can you fill out your comment re 'a delegate class' with a code example? – Craig Grummitt Jan 15 '16 at 16:42
  • @CraigGrummitt All variables (and / or constants) in swift must have some value before they are used (for reference type, which in Swift is represented as a class, the value is reference, for value types it is the value self). direct equivalent of null doesn't exists is Swift. var i = Optional() has default value nil. var j = ImplicitlyUnwrappedOptional() gives you the same result (both are enum 'type') .... (continue below) – user3441734 Jan 15 '16 at 19:37
  • @CraigGrummitt (continue) .... but compiler will not complain if you use it somewhere and your code most likely will crash. That is, why var name:String! = nil is not the best solution. In your example the best is use non optional value. var name: String = "" should works and for me it seems to be very convenient :-) and the best practices as well. – user3441734 Jan 15 '16 at 19:38
15
protocol Thing {
    var color: UIColor {get set}
}

Awesome, no problems.

extension Thing {
    init(color: UIColor) {
        self.color = color
    }
}

No. That's never going to work. This breaks way too many rules. The first and most important is that this doesn't necessarily set all the properties. Consider your NamedThing. What is name in this case? What happens if the color setter fetches other properties that haven't been set yet? The compiler can't see every possible implementation of this yet, so it has no idea if color is just an ivar or something wildly more complicated. No, this isn't going to work.

The real problem is "an abstract class that can be extended in a concrete class." Forget classes. Forget inheritance. Swift is all about composition and protocols, not inheritance.

So let's think about the example you describe in the comments (though in Cocoa, there are no "abstract classes," either). Let's assume that setting color is in fact a lot of code that you don't want to duplicate. That's no problem. You just need a function.

import UIKit

protocol Thing {
    var color: UIColor {get set}
}

private extension Thing {
    static func colorForColor(color: UIColor) -> UIColor {
        // We don't really use the color directly. We have some complicated code that we don't want to repeat
        return color
    }
}

final class NamedThing: Thing {
    var name: String
    var color: UIColor

    init(name: String, color: UIColor) {
        self.name = name
        self.color = NamedThing.colorForColor(color)
    }
}

Since the point of your extension is to handle partial initialization, just let it calculate the part you need. Don't try to make it an initializer in an extension, since then it would have to be responsible for initializing everything, and that is very hard to do correctly when you mix it with inheritance.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks for your anwer Rob. I have reduced my code to a bare bones example for simplicity, without going into why it needs to be a class, but in the real life example, it is a class that needs to implement the protocol as it is going to subclass a UIKit class. The boilerplate code was intended to be illustrated by the protocol extension handling the setting of the color. – Craig Grummitt Jan 15 '16 at 19:52
  • By the way, I've just realized by your comment "There are no abstract classes in Swift" that we may be working off different understandings of "abstract class", apologies if I've confused the issue with an incorrect understanding of the term. I've edited the question to be clearer. – Craig Grummitt Jan 15 '16 at 20:05
  • What UIKit class is this that always has to be subclassed? (We agree on what an abstract class is; I just can't think of the class you're talking about. `UIGestureRecognizer`?) I strongly suspect that the actual problem you're facing is much more easily solved another way. That's the classic problem with over-simplifying the question. – Rob Napier Jan 15 '16 at 20:09
  • This is all just a bit of a thought experiment, but the class I was subclassing was UIView. Thanks for updating your code, a few thoughts/comments: 1. I suppose following the strategy you're suggesting NamedThing doesn't need to be final. 2. I guess setting up a static method in the Thing extension is pretty much the same as setting up a computed property on color and any additional calculations could be performed there. 3. If we're using computed properties, the easiest solution might be just to leave out any initialization of the protocol, they're just too problematic. – Craig Grummitt Jan 15 '16 at 20:27
  • Agreed. In my experience, a lot of problems come up when you try to run down these paths with pure thought experiments, because they almost always assume a solution and work back to a problem. Start with some real problems that you encounter, and try to build solutions to those. You'll often find that concrete problems are much easier to solve than abstract problems. (I constantly build clever "solutions" that when I start applying to real problems discover that the plain, obvious, just-do-it answer works much better. I've finally learned to start with the real problem…) – Rob Napier Jan 15 '16 at 20:36
  • 1
    As for final, IMO you should always mark things final unless there is a specific reason you plan to subclass it. It just solves a lot of potential headaches. Swift really hates inheritance. ObjC didn't *like* inheritance, but it handled it mostly by ignoring most corner cases and hoping you write the code correctly. Swift more often demands that the code be correct, so the corner cases that ObjC ignored come back in and bite you. – Rob Napier Jan 15 '16 at 20:38
  • Partial initialization in a protocol extension can also be combined with a failable initializer in a class/struct for a validating creational pattern: `init?(colorCode: String) { guard let color = NamedThing.colorFor(code: colorCode) else { return nil }; self.color = color }`. – Gary Aug 17 '17 at 08:58
1

Here's what I had in mind for the "delegate class".

It's a technique I use to add stored variables to a class using protocols.

class ManagedColors
{
   var color:UIColor
   // other related variables that need a common initialisation
   // ...
   init(color:UIColor)
   {
      self.color = color
      // common initialisations for the other variables
   }
}

protocol ManagedColorClass
{
    var managedColors:ManagedColors { get }
}

extension ManagedColorClass 
{    
    // makes properties of the delegate class accessible as if they belonged to the
    // class that uses the protocol
    var color:UIColor { 
                        get { return managedColors.color } 
                        set { managedColors.color = newValue }
                      }    
}


// NamedThing objects will be able to use .color as if it had been
// declared as a variable of the class
//
// if you add more properties to ManagedColors (and the ManagedColorHost protocol)
// all your classes will inherit the properties as if you had inherited from them through a superclass
// 
// This is an indirect way to achive multiple inheritance, or add additional STORED variables with
// a protocol
//
class NamedThing:ManagedColorClass 
{
    var name:String
    var managedColors:ManagedColors 

    init(name:String,color:UIColor) 
    {
        managedColors = ManagedColors(color:color)
        self.name = name
    }
}

let red = NamedThing(name:"red", color:UIColor.redColor())
print(" \(red.name)  \(red.color)")
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • After getting my head around that your protocol name had the word 'class' in it, I follow your example, thanks for the input. To put your suggestion another way, you're using a factory class to build up any properties that were in my original simple 'Thing' class right? I guess the problem I have with it as a solution to this question is that at its base it's using a class again. But it's an interesting example, thanks. – Craig Grummitt Jan 15 '16 at 20:54
  • It does indeed use a class but the benefits here is that you can use it outside of your inheritance hierarchy like you would with a protocol. I'm using this to implement "multiple inheritance", that's why I named my protocol with the word Class in it. For a single variable, it looks like an overkill but when you want to add data and behaviour to multiple classes that have their own hierarchy it then starts to make more sense. For your specific needs I agree that it is probably not the best approach. – Alain T. Jan 15 '16 at 23:19
-1

I have come to the conclusion that the question is sort of unanswerable, as a protocol extension can not dynamically define properties(unless it provides default values for properties, or declares them as implicitly unwrapped). To resolve this in a more protocol oriented fashion, it requires a different approach, which still involves declaring and initializing all variables in the concrete class, something like:

import UIKit
protocol Colorable {
    var color: UIColor {get set}
}
protocol Nameable {
    var name: String {get set}
}
class ColoredNamedThing: Colorable, Nameable {
    var name: String
    var color: UIColor

    init(name: String, color: UIColor) {
        self.name = name
        self.color = color
    }
}

var coloredNamedThing = ColoredNamedThing(name: "Name", color: UIColor.redColor())

Thank you to @alain-t for the answer I'm going to accept as it most closely finds a solution to my question, despite it including implicitly unwrapped properties.

Thank you to @rob-napier also for your contribution also.

Craig Grummitt
  • 2,945
  • 1
  • 22
  • 34