2

Objective-C has a peculiar, but very helpful ability to make an initializer return a different instance than the one the initializer is being called on. You can read about that here.

It's great for things like class clusters, but even for things as simple as ensuring specific instances are returned based on specific conditions (i.e. newing up an Account class with an account number will always return the same instance of the Account class for that number.)

However, in our app, we're using Swift but I don't see any such provisions in this language. Is there such a thing?

Normally we'd simply create a class method to return the instance we're interested in, but while we own the class in question, we do not own the calling code which uses the standard initializer syntax. e.g.

let foo = OurClass()

We want to control which instance is handed back to them based on some external conditions (omitted for clarity here.)

So does Swift have any such mechanisms?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • At present that is not possible in Swift, you cannot create and return a (subclass) instance in the init method, if that is what you are looking for. – I *think* that has been discussed on swift-evolution, but I don't know what the current status is. – Martin R Nov 28 '16 at 21:23
  • No, that's not what we're doing. We're just returning a different class instance (but of the same type) as the one init is called against. I'm thinking the solution is to dip down into Objective-C then bridge back to swift. Would give us what we want, albeit in a three-lefts-to-go-right kinda way. – Mark A. Donohoe Nov 28 '16 at 21:24
  • 1
    Here is one thread that I was thinking of: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/003192.html. – At present you cannot return a different instance (as far as I know), subclass or not. – Martin R Nov 28 '16 at 21:27
  • 1
    Perhaps a class-level factory method would be better suited for this rather than an `init` method. – rmaddy Nov 28 '16 at 21:32
  • I agree, but as I pointed out in the question, we're not controlling where it's used. We're just trying to stop it from newing up and returning duplicates, the exact case that Apple talks about in their documentation in the link I provided. – Mark A. Donohoe Nov 28 '16 at 21:36
  • @Martin, that would be sweet! That's exactly what I'm hoping for (and true abstract classes too for that matter!) – Mark A. Donohoe Nov 28 '16 at 21:38
  • What would abstract classes be useful for? The only time I find them useful is in Java where interfaces can't require implementing classes to override constructors – Alexander Nov 28 '16 at 21:50
  • Because unlike interfaces and extensions to them, abstract classes can provide member variables and such. They're great candidates for base functionality of a 'family' of classes that share a lot of commonality which a) shouldn't be instantiated on their own, and b) extensions cannot reproduce. There are countless examples where abstract classes simplify the design and readability/understandability of class hierarchies where protocol-oriented programming doesn't fit well. – Mark A. Donohoe Nov 28 '16 at 21:52

2 Answers2

1

While not as easy or clean as objective-c, hopefully you should be able to achieve your goal using convenience initializers or failable initializers (or both).

A convenience initializer allows you to delegate initialization to another initializer, essentially giving you control of how the object is initialized:

  class RecipeIngredient {
    var quantity: Int

    init(quantity: Int) {
        self.quantity = quantity
    }
    convenience init() {
        // You control how the properties are initialized here
        if (/* Some external or internal condition */) {
           self.init(quantity: 1)
        }
        else {
           self.init(quantity: 2)
        }
    }
 }

 // The caller
 let mysteryFood = Food()

The failable initializer allows you to return nil in a designated initializer if a condition isn't met or if an error occurs during initialization:

class Account {
    var id: Int

    // Failable initializers have '?' after init
    init?(id: Int) {
        // You control if the initialization succeeds or not
        if (/* check your db if id is duplicate, for example */) {
          return nil
        }
        // Succeeds
        self.quantity = quantity
    }
 }

 // The caller
 let account = Account(id: 5)
 if (account == nil) { 
   // It failed 
 }
 else { 
   // It succeeded 
 }

In your case, based on your comments, it sounds like you would want to use a failable initializer that returns nil if the caller is attempting to create a duplicate instance.

Hopefully this is along the lines of what you are looking for!

mattyb
  • 1,011
  • 2
  • 10
  • 26
  • 3
    I'm aware of failable initializers and was actually excited about them when I first found them.... until I realized the only thing you can return is 'nil'. If they let you return a new/different instance, that would be on par with ObjC's solution and again allow class clusters and a more elegant class factory methodology without having an explicit method (as you'd just use the initializer.) – Mark A. Donohoe Nov 28 '16 at 21:58
  • 1
    Gotcha, it is a bummer, hopefully the swift-evolution proposal goes through. – mattyb Nov 28 '16 at 22:03
  • 2
    While this doesn't really answer the question, it provides helpful information for people who may have a similar issue. So, since the duplicate has the answer, I'm marking this here in good S.O. citizenship as to not have open questions. – Mark A. Donohoe Nov 30 '16 at 17:41
1

As far as I can tell it's not possible with classes, but you can do this with struct initializers (self = otherValue). If it's possible the class can be converted to a struct that would probably be the way to go.

The obvious alternative (and arguably better design) is a factory method, and when working with an already existing API, you could deprecate that initializer or make it fallible to indicate the factory method should instead be used. Otherwise there are a few options that come to mind.

  1. Copy the properties of the instance you would like to return to the new instance you're creating.
  2. As suggested, drop down to Objective-C, for that initializer.
  3. Use a subclass only for Swift and enforce usage of the constructor.
GetSwifty
  • 7,568
  • 1
  • 29
  • 46