2015-04-18 26 views
6

Sto lavorando allo sviluppo di un'applicazione in Swift. Volevo progettare un sistema per l'applicazione che consentisse un accoppiamento lento tra gli oggetti, e una strategia (che ho usato con successo in altri linguaggi) era creare qualcosa che io chiamo una fabbrica di istanze. E 'abbastanza semplice e qui è l'implementazione di base mi è venuta a Swift:Alternativa al metodo di caricamento 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]?() 
    } 
} 

L'idea è che quando un'istanza oggetto ha bisogno di accedere a un'altra istanza oggetto, piuttosto che la creazione di tale istanza a titolo definitivo che sarebbe più strettamente paio i due oggetti, il primo oggetto si rimanda alla fabbrica per fornire l'istanza necessaria chiamando il metodo instanceOf. La fabbrica saprebbe come fornire vari tipi di istanze perché quei tipi sarebbero registrati con la fabbrica e fornirebbero una chiusura che potrebbe generare l'istanza.

Il trucco è come ottenere le classi per registrarsi in fabbrica. In precedenza avevo creato uno stabilimento simile in Objective-C e il modo in cui ho ottenuto la registrazione al lavoro era di sovrascrivere il metodo di caricamento + per ogni classe che aveva bisogno di registrarsi con la fabbrica. Questo ha funzionato alla perfezione con Objective-C, e ho pensato che potesse funzionare anche per Swift dato che limitavo la factory a fornire solo oggetti derivati ​​da NSObject. Mi è sembrato che riuscissi a far funzionare tutto questo e ho dedicato uno sforzo significativo alla progettazione di corsi per far uso della fabbrica.

Tuttavia, dopo l'aggiornamento a Xcode 6.3, ho scoperto che Apple non ha consentito l'utilizzo del metodo della classe di carico in Swift. Senza questo, non sono a conoscenza di un meccanismo che consenta alle classi di registrarsi automaticamente con la fabbrica.

Mi chiedo se ci sia un altro modo per far funzionare la registrazione.

Quali alternative sono disponibili per consentire alle classi di registrarsi in fabbrica o quali altre tecniche potrebbero essere utilizzate per realizzare lo stesso tipo di accoppiamento libero fornito dalla fabbrica?

+0

Si può continuare ad usare Objective-C per quella parte ...? – matt

+0

Sì, potrei usare Objective-C per questo. Da quello che posso vedere, lo svantaggio di questo sarebbe l'introduzione di moduli aggiuntivi per ogni classe che ha bisogno di registrarsi con la fabbrica, il che potrebbe non essere un grosso problema. Ovviamente potrei semplicemente mettere tutte le registrazioni in un unico metodo di caricamento + per una singola classe. Tuttavia, se l'avessi fatto, avrei potuto facilmente fare anche la registrazione in un modulo Swift, come il delegato dell'applicazione. –

+0

Se dovessi usare moduli multipli, potrei forse anche fare la registrazione in Objective-C++.Sospetto che tutti i moduli di registrazione farebbero praticamente la stessa cosa, quindi sarebbe bello consolidare quella funzionalità. Non ci ho pensato fino in fondo, ma sembra che qualcuno potrebbe forse creare un modello C++ in Objective-C++ che potrebbe contenere la piastra della caldaia per la registrazione. Quindi, di nuovo, è probabile che non sia coinvolto troppo codice, quindi forse non ne vale la pena. –

risposta

0

Considerare di adottare l'approccio Swift utilizzando invece un protocollo. Penso che la soluzione sia in realtà più semplice dell'approccio Objective-C. Ci sono variazioni di questo con vincoli di auto che sono ancora meglio se hai più controllo sulle classi.

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

Forse mi manca qualcosa, tuttavia, esaminando l'implementazione proposta, sembra che vada a capo dell'intero punto di InstanceFactory. L'InstanceFactory ha lo scopo di consentire l'accoppiamento lento tra un'istanza di classe e un utente di quell'istanza. Con l'implementazione originale, data una sorta di identificatore, la fabbrica potrebbe produrre un'istanza arbitraria. L'utilizzo di NSClassFromString sembra reintrodurre l'accoppiamento stretto che la factory stava cercando di evitare perché NSClassFromString fornirà l'unica classe che corrisponde all'identificatore di stringa. –

+0

Ho difficoltà a vedere come qualcuno potrebbe configurare la fabbrica per fornire un'istanza derivata, oppure consentire alla fabbrica di fornire un'istanza per un protocollo specificato. Mi chiedo se esiste un'alternativa a NSClassFromString che potrebbe essere utilizzata. –

+0

Oh, capisco. Si desidera avere un identificativo del nome del tipo personalizzato. In tal caso, ritengo che la tua unica opzione sia quella di assegnare gli identificativi alle classi dall'esterno (ad es. In fabbrica o in una classe helper) piuttosto che dall'interno delle tue classi come menzionato in uno dei tuoi commenti precedenti. Ogni volta che aggiungi una nuova classe dovrai modificare la classe helper per registrarla. Non è meglio comunque rispetto all'approccio Objective-C originale? Sposta la registrazione in un singolo posto e ti consente di personalizzare quali classi sono registrate a quali ID per diversi casi d'uso. –

2

ho trovato una possibile soluzione al vostro problema dopo che ho voluto registrare tutti ViewControllers che sarebbe l'attuazione di un certo protocollo nella mia richiesta e mi sono imbattuto in questa domanda e sia una possibile risposta.

L'originale è stato postato qui: How to list all classes conforming to protocol in Swift?

mi sono adattato a Swift 3 e ne ha fatto un po 'più Swift-y e generica:

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

ho provato nella mia richiesta e funziona. L'ho cronometrato nel simulatore e ci vogliono 0,05 secondi per un'applicazione che ha circa 12000 classi.