Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative to load method in Swift

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?

like image 434
Tron Thomas Avatar asked Apr 18 '15 16:04

Tron Thomas


1 Answers

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.

like image 184
Lucas van Dongen Avatar answered Nov 03 '22 08:11

Lucas van Dongen