2014-11-24 13 views
15

Beim Erstellen einer Erweiterung Helper zu NSManagedObject, um eine neue Unterklasse des verwalteten Objekts zu erstellen, bietet Swift die Self Art zu imitieren instancetype, die groß ist, aber ich kann nicht scheinen, von AnyObject typecast. Der folgende Code kompiliert nicht mit Fehler 'AnyObject' ist nicht konvertierbar zu 'Self'Wie kann ich Instanzen von verwalteten Objektunterklassen in einer NSManagedObject Swift-Erweiterung erstellen?

Hilfe?

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

} 

Antwort

24

Der Trick ist, eine generische Hilfsmethode zu verwenden, die die Art der self aus dem Kontext folgert. Ihre className() Verfahren kann auch leicht vereinfacht werden und ein besserer Name könnte von 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 
    } 
} 

Dann

let obj = YourEntity.createInContext(context) 

Werke und der Compiler folgert die Art der obj korrekt als YourEntity.


Update:

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

:
Mit den Ideen von How to use generic types to get object with same type kann dies auch mit einer globalen wiederverwendbaren Funktion anstelle des Hilfsmethode zu werfen den Rückgabewert in dem entsprechenden Typen durchgeführt werden

Update für Swift 1.2/Xcode 6.3 und 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 
    } 
} 

Oder mit unsafeBitCast anstelle einer Hilfsmethode, wie sie in den Kommentaren vorgeschlagen:

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 ¢: Diese generische Funktion ist ein ziemlich gutes Anwendungsbeispiel für das Schlüsselwort 'private'. – Mazyod

+0

@Mazyod: Guter Vorschlag, fertig. –

+0

In Swift 2 ist Split keine globale Funktion mehr. Wir können stattdessen classString.componentsSeparatedByString (".") Verwenden. –

7

Hier ist ein anderer Ansatz, um das Problem zu lösen, indem ein initializer Verfahren implementiert (mit Xcode getestet 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) 
    } 
} 

Init Methoden haben einen impliziten Rückgabetyp Self und kein Gießen Tricks sind notwendig.

let obj = YourEntity(context: context) 

erzeugt ein Objekt des YourEntity Typs.

3

In Swift 2 ist eine sehr intelligente Lösung, die ein Protokoll und eine Protokollerweiterung

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 jeder NSManagedObject Unterklasse fügen Sie das Protokoll Fetchable und implementieren die Eigenschaft entityName verwenden.

Jetzt wird die Funktion MyEntity.createInContext(…) den richtigen Typ ohne weiteren Typ Casting zurückgeben.