2014-11-24 12 views
15

Quando si crea un helper di estensione su NSManagedObject per creare una nuova sottoclasse di oggetti gestiti, swift fornisce il tipo Self per simulare instancetype che è ottimo, ma non riesco a digitare da AnyObject. Il seguente codice non viene compilato con l'errore 'AnyObject' non è convertibile in 'Self'Come posso creare istanze di sottoclassi di oggetti gestiti in un'estensione NSManagedObject Swift?

Aiuto?

extension NSManagedObject 
{ 
    class func createInContext(context:NSManagedObjectContext) -> Self { 
     var classname = className() 
     var object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) 
     return object 
    } 


    class func className() -> String { 
     let classString = NSStringFromClass(self) 
     //Remove Swift module name 
     let range = classString.rangeOfString(".", options: NSStringCompareOptions.CaseInsensitiveSearch, range: Range<String.Index>(start:classString.startIndex, end: classString.endIndex), locale: nil) 
     return classString.substringFromIndex(range!.endIndex) 
    } 

} 

risposta

24

Il trucco è quello di utilizzare un metodo di supporto generico che deduce il tipo di self dal contesto. Il tuo metodo className() può anche essere semplificato un po ', e un nome migliore potrebbe da entityName():

extension NSManagedObject 
{ 
    class func createInContext(context:NSManagedObjectContext) -> Self { 
     return createInContextHelper(context) 
    } 

    private class func createInContextHelper<T>(context:NSManagedObjectContext) -> T { 
     let classname = entityName() 
     let object = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) as! T 
     return object 
    } 

    class func entityName() -> String { 
     let classString = NSStringFromClass(self) 
     // The entity is the last component of dot-separated class name: 
     let components = split(classString, { $0 == "." }) 
     return components.last ?? classString 
    } 
} 

Poi

let obj = YourEntity.createInContext(context) 

opere e il compilatore deduce il tipo di obj correttamente come YourEntity.


Aggiornamento: Utilizzando le idee da How to use generic types to get object with same type, questo può essere fatto anche con una funzione riutilizzabile globale invece del metodo di supporto al cast del valore restituito al tipo appropriato:

func objcast<T>(obj: AnyObject) -> T { 
    return obj as T 
} 

extension NSManagedObject 
{ 
    class func createInContext(context:NSManagedObjectContext) -> Self { 
     let classname = entityName() 
     let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) 
     return objcast(object) 
    } 

    class func entityName() -> String { 
     let classString = NSStringFromClass(self) 
     // The entity is the last component of dot-separated class name: 
     let components = split(classString, { $0 == "." }) 
     return components.last ?? classString 
    } 
} 

Aggiornamento per Swift 1.2/Xcode 6.3 e Swift 2/Xcode 7:

func objcast<T>(obj: AnyObject) -> T { 
    return obj as! T 
} 

extension NSManagedObject 
{ 
    class func createInContext(context:NSManagedObjectContext) -> Self { 
     let classname = entityName() 
     let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) 
     return objcast(object) 
    } 

    class func entityName() -> String { 
     let classString = NSStringFromClass(self) 
     // The entity is the last component of dot-separated class name: 
     let components = classString.componentsSeparatedByString(".") 
     return components.last ?? classString 
    } 
} 

O con unsafeBitCast invece di un metodo di supporto, come suggerito nei commenti:

extension NSManagedObject 
{ 
    class func createInContext(context:NSManagedObjectContext) -> Self { 
     let classname = entityName() 
     let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) 
     return unsafeBitCast(object, self) 
    } 

    class func entityName() -> String { 
     let classString = NSStringFromClass(self) 
     // The entity is the last component of dot-separated class name: 
     let components = classString.componentsSeparatedByString(".") 
     return components.last ?? classString 
    } 
} 
+1

2 ¢: Quella funzione generica è un caso di utilizzo piuttosto buono per la parola chiave 'private'. – Mazyod

+0

@Mazyod: buon suggerimento, fatto. –

+0

In Swift 2, split non è più una funzione globale. Possiamo invece utilizzare classString.componentsSeparatedByString ("."). –

7

Ecco un approccio diverso per risolvere il problema, implementando un metodo inizializzatore (testato con Xcode 7.1):

extension NSManagedObject { 

    // Returns the unqualified class name, i.e. the last component. 
    // Can be overridden in a subclass. 
    class func entityName() -> String { 
     return String(self) 
    } 

    convenience init(context: NSManagedObjectContext) { 
     let eName = self.dynamicType.entityName() 
     let entity = NSEntityDescription.entityForName(eName, inManagedObjectContext: context)! 
     self.init(entity: entity, insertIntoManagedObjectContext: context) 
    } 
} 

metodi Init hanno un tipo di rendimento implicito di Self e non fusioneSono necessaritrucchi.

let obj = YourEntity(context: context) 

crea un oggetto del tipo YourEntity.

3

In Swift 2 è una soluzione molto intelligente utilizzando un protocollo ed un prolungamento protocollo

protocol Fetchable 
{ 
    typealias FetchableType: NSManagedObject 

    static var entityName : String { get } 
    static func createInContext(context: NSManagedObjectContext) -> FetchableType 
} 

extension Fetchable where Self : NSManagedObject, FetchableType == Self 
{ 
    static func createInContext(context: NSManagedObjectContext) -> FetchableType 
    { 
    return NSEntityDescription.insertNewObjectForEntityForName(entityName, inManagedObjectContext: context) as! FetchableType 
    } 
} 

In ogni NSManagedObject sottoclasse aggiungere il protocollo Fetchable e implementare la proprietà entityName.

Ora la funzione MyEntity.createInContext(…) restituirà il tipo corretto senza ulteriori tipi di trasmissione.

Problemi correlati