在Swift中替代load方法的方法

6

我正在开发一个Swift应用程序。我希望为应用程序设计一个系统,使对象之间的耦合度较低。其中一种策略(我在其他语言中成功使用过)是创建我所谓的实例工厂。它非常简单,以下是我在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]?()
    }
}

这个想法是,当一个对象实例需要访问另一个对象实例时,第一个对象不会直接创建该实例,这将使两个对象之间的耦合更紧密。相反,第一个对象将通过调用instanceOf方法来委托工厂提供所需的实例。工厂将知道如何提供各种实例类型,因为这些类型将向工厂注册,并提供一个可以生成实例的闭包。
关键是如何让类与工厂注册。我之前在Objective-C中制作了一个类似的工厂,我让每个需要向工厂注册的类重写了+load方法。这对于Objective-C很有效,我觉得它也可以适用于Swift,因为我将限制工厂仅提供从NSObject派生的对象。看起来我已经做到了这一点,我花费了大量的精力设计类来利用工厂。
但是,在升级到Xcode 6.3后,我发现苹果禁止在Swift中使用load类方法。没有这个,我不知道有什么机制可以允许类自动向工厂注册。
我想知道是否有其他方法可以使类与工厂注册,或者还有哪些其他技术可以实现工厂提供的松散耦合?

你可以继续使用Objective-C来完成那部分吗? - matt
是的,我可以使用Objective-C来实现。从我所看到的情况来看,这样做的缺点是为每个需要在工厂中注册的类引入额外的模块,但这可能并不是什么大问题。当然,我也可以将所有的注册都放在单个类的+load方法中。然而,如果我这样做了,我同样可以在Swift模块中进行注册,比如应用程序代理。 - Tron Thomas
如果我使用多个模块,甚至可以在Objective-C++中完成注册。我怀疑所有的注册模块都会做同样的事情,所以将这个功能合并起来可能是不错的选择。我还没有完全想清楚,但似乎有人可以在Objective-C++中创建一个C++模板,其中包含注册的样板代码。然而,也许涉及的代码并不太多,所以可能不值得这样做。 - Tron Thomas
1
你有没有找到任何可接受的(或至少更/不太繁琐)解决方法?我个人没能找到解决方案。由于唯一的答案相当偏离,你为什么不自己回答呢? - user1244109
如果您在此期间找到了更优雅的解决方案,我将不胜感激。 - Lucas van Dongen
2个回答

2

在我想要注册所有实现了某个协议的 ViewControllers 的应用程序中,我找到了一个可能的解决方案,并遇到了这个问题和一个可能的答案。

原始帖子发布在这里:How to list all classes conforming to protocol in Swift?

我将其改编为Swift 3,并使其更具有Swift风格和通用性:

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
    }
}

我在我的应用程序中尝试过它,它可以正常工作。我在模拟器中测试了一下,一个大约有12000个类的应用程序只需要0.05秒。


0
考虑使用协议来采用 Swift 的方法。我认为这个解决方案比 Objective-C 的方法更简单。如果您对类有更多控制,可以使用带有 Self 约束的变体,效果甚至更好。
// 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)
    }
}

1
也许我漏掉了什么,但是从审查所提出的实现方案来看,它似乎违背了InstanceFactory的初衷。InstanceFactory旨在允许类实例与使用该实例的用户之间松散耦合。在原始实现中,给定某种标识符,工厂可以生成任意实例。使用NSClassFromString似乎重新引入了工厂试图避免的紧密耦合,因为NSClassFromString将提供与字符串标识符匹配的唯一一个类。 - Tron Thomas
我很难想象有人如何配置工厂以提供派生实例,或允许工厂为指定的协议提供实例。我在想是否有替代NSClassFromString的方法可以使用。 - Tron Thomas
哦,我明白了。你想要一个自定义类型名称标识符。在这种情况下,我认为你唯一的选择是从外部分配类的标识符(例如,在工厂或助手类中),而不是像你在之前的评论中提到的那样从内部分配。每次添加新类时,您都需要修改助手类以注册它。这难道不比原始的Objective-C方法更好吗?它将注册移动到一个单独的位置,并允许您自定义哪些类针对不同的用例注册到哪些ID。 - Tom Pelaia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接