6

I am working on developing an application in Swift. I wanted to design a system for the application that allowed for loose coupling between objects, and one strategy (which I have used successfully in other languages) was to create something I call an instance factory. It is pretty simple and here is the basic implementation I came up with in Swift:

import Foundation

private var typeGenerators = Dictionary<String, InstanceFactory.GeneratorCallback>()

public class InstanceFactory: NSObject {
    public typealias GeneratorCallback = () -> AnyObject!

    public class func registerGeneratorFor(typeName: String, callback: GeneratorCallback) {
        typeGenerators[typeName] = callback
    }

    public class func instanceOf(typeName: String) -> AnyObject! {
        return typeGenerators[typeName]?()
    }
}

The idea is that when an object instance needs access to another object instance, rather than creating that instance outright which would more tightly couple the two objects, the first object would defer to the factory to provide the needed instance by calling the instanceOf method. The factory would know how to provide various instance types because those types would register with the factory and provide a closure that could generate the instance.

The trick is how to get the classes to register with the factory. I had previously made a similar factory in Objective-C and the way I got registration to work was to override the +load method for each class that needed to register with the factory. This worked great for Objective-C, and I figured it could work for Swift as well since I would be restricting the factory to only provide objects that are derived from NSObject. It appeared I got this to work and I spent a significant about of effort designing classes to make use of the factory.

However, after upgrading to Xcode 6.3, I discovered Apple has disallowed the usage of the load class method in Swift. Without this, I am unaware of a mechanism to allow classes to automatically register themselves with the factory.

I am wondering if there some other way to get the registration to work.

What alternatives are available that could allow classes to register with the factory, or what other techniques could be use to accomplish the same kind of loose coupling the factory provides?

Tron Thomas
  • 871
  • 7
  • 20
  • You could continue using Objective-C for that part...? – matt Apr 18 '15 at 16:21
  • Yes I could use Objective-C for that. From what I can see the drawback of that would be introducing extra modules for every class that needs to register with the factory, which might not be that big of an issue. Of course I could just lump all the registration into a single +load method for a single class. However, if I did that I could just as easily do the registration in a Swift module as well, like the Application delegate. – Tron Thomas Apr 23 '15 at 03:19
  • If I were to use mulitple modules, I could maybe even do the registration in Objective-C++. I suspect all the registration modules would be doing pretty much the same thing so it might be nice to consolidate that functionality. I haven't thought it through completely but it seems like someone could maybe create a C++ template in Objective-C++ that could contain the boiler plate for the registration. Then again there is likely not too much code involved anyway, so maybe its not worth the effort. – Tron Thomas Apr 23 '15 at 03:19
  • 1
    have you managed to find any acceptable (or at least more/less elegant) workaround? I personally failed finding a solution. Since the only answer is quite off, wouldn't you answer yourself? – user1244109 Dec 22 '15 at 23:57
  • If you found a more elegant solution in the mean time I would appreciate it – Lucas van Dongen Dec 02 '16 at 18:33

2 Answers2

2

I've found a possible solution to your problem after I wanted to register all ViewControllers that would be implementing a certain Protocol in my application and I ran into both this question and a possible answer.

The original was posted here: How to list all classes conforming to protocol in Swift?

I adapted it to Swift 3 and made it a bit more Swift-y and generic:

import UIKit

class ContextRoute: NSObject {

}

@objc protocol ContextRoutable {
    static var route: ContextRoute { get }
}

class ContextRouter: NSObject {
    private static var storedRoutes: [ContextRoute]?
    static var routes: [ContextRoute] {
        get {
            if let storedRoutes = storedRoutes {
                return storedRoutes
            }

            let routables: [ContextRoutable.Type] = classes(implementing: ContextRoutable.self)
            let newRoutes = routables.map { routable in routable.route }

            storedRoutes = newRoutes

            return newRoutes
        }
    }

    private class func classes<T>(implementing objcProtocol: Protocol) -> [T] {
        let classes = classList().flatMap { objcClass in objcClass as? T }

        return classes
    }

    private class func classList() -> [AnyObject] {
        let expectedClassCount = objc_getClassList(nil, 0)
        let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
        let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
        let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)

        var classes = [AnyObject]()
        for i in 0 ..< actualClassCount {
            if let currentClass: AnyClass = allClasses[Int(i)],
                class_conformsToProtocol(currentClass, ContextRoutable.self) {
                    classes.append(currentClass)
            }
        }

        allClasses.deallocate(capacity: Int(expectedClassCount))

        return classes
    }
}

I tried it in my application and it works. I clocked it in the simulator and it takes 0.05s for an application that has about 12000 classes.

Community
  • 1
  • 1
Lucas van Dongen
  • 9,328
  • 7
  • 39
  • 60
0

Consider taking the Swift approach using a protocol instead. I think the solution is actually simpler than the Objective-C approach. There are variations of this with Self constraints which are even better if you have more control over the classes.

// define a protocol to create an instance of a class
protocol FactoryInstantiable {
    static func makeFactoryInstance() -> AnyObject
}

// Factory for generating new instances
public class InstanceFactory: NSObject {
    public class func instanceOf(typeName: String) -> AnyObject! {
        if let ProductType = NSClassFromString(typeName) as? FactoryInstantiable.Type {
            return ProductType.makeFactoryInstance()
        } else {
            return nil
        }
    }
}


// your class which generally could be defined somewhere else 
class MyClass {
    var counter : Int

    init(counter: Int) {
        self.counter = 0
    }
}

// extension of your class to conform to the FactoryInstantiable protocol
extension MyClass : FactoryInstantiable {
    static func makeFactoryInstance() -> AnyObject {
        return MyClass(counter: 0)
    }
}
Tom Pelaia
  • 1,275
  • 10
  • 9
  • 1
    Perhaps I am missing something, however, from looking over the proposed implementation, it appears to defeat the whole point of the InstanceFactory. The InstanceFactory is intended to allow for loose coupling between a class instance, and a user of that instance. With the original implementation, given some kind of identifier, the factory could produce an arbitrary instance. Using NSClassFromString appears to reintroduce the tight coupling the factory was trying to avoid because NSClassFromString will provide the one and only one class that matches the string identifier. – Tron Thomas Nov 26 '15 at 19:55
  • I have difficulty seeing how someone could configure the factory to provide a derived instance instead, or allow for the factory to provide an instance for a specified protocol. I’m wondering if there is an alternative to NSClassFromString that could be used instead. – Tron Thomas Nov 26 '15 at 19:55
  • Oh, I see. You want to have a custom type name identifier. In that case, I think your only option is to assign the identifiers to the classes from outside (e.g. in you factory or a helper class) rather than from inside your classes as you mentioned in one of your previous comments. Each time you add a new class you will need to modify the helper class to register it. Isn't this better anyway than the original Objective-C approach? It moves the registration to a single place and allows you to customize which classes are registered to which IDs for different use cases. – Tom Pelaia Nov 30 '15 at 14:40