I had a version of this in Objective-C that worked fine, because of Obj-C's lax type checking. But now I'm trying to implement it in Swift, and I can't figure out how to make it specific to the called type. I want call this like so:
let result = MyEntityType.all(inMOC: moc, predicateFormat: "foo == %d", someInt)
But if I do that, the type of result isn't [MyEntityType], but rather [NSManagedObject]. It should be able to infer [MyEntityType], but I don't know how to write the method properly. I wish I could use Self in place of T : NSManagedObject.
extension
NSManagedObject
{
class
func
all<T : NSManagedObject>(inMOC: NSManagedObjectContext,
sortKey inSortKey: String? = nil,
ascending inAscending: Bool = true,
predicateFormat inPredicateFormat: String,
_ inArgs: CVarArg...)
throws
-> [T]
{
let entity = self.entity(inMOC: inMOC)
let req = NSFetchRequest<T>()
req.entity = entity
let pred = NSPredicate(format: inPredicateFormat, argumentArray: inArgs)
req.predicate = pred
if let sortKey = inSortKey
{
let sd = NSSortDescriptor(key: sortKey, ascending: inAscending)
req.sortDescriptors = [sd]
}
do
{
let results = try inMOC.fetch(req)
return results
}
catch let e
{
debugLog("Error fetching entities \(String(describing: type(of: self))) with sort key \(String(describing: inSortKey)): \(e)")
throw e
}
}
}
Is this possible? What's the right generic syntax to use? Thanks.
To work with the limitations of Self usage with Swift, the first thing is to understand that it does not work well with parent classes. So extension to NSManagedObject is not going to work.
Instead you want to create a protocol (that uses Self) and then conform to that protocol in your own (final) subclass. This is the approach @lassejansen showed to you.
For your particular code, it would look something like this:
protocol Fetchable where Self: NSManagedObject {}
extension Fetchable {
static func all(in moc: NSManagedObjectContext) throws -> [Self] {
let req = NSFetchRequest<Self>()
req.entity = self.entity()
return try moc.fetch(req)
}
}
// add @objc if this class needs to be visible to Objective-C
final class MyManagedObject: NSManagedObject, Fetchable {}
let results = try? MyManagedObject.all(in: someMoc)
Also @lassejansen's example could be simpler by declaring the init in the protocol, then it reduces to:
@objc for the ManagedObject class should work, but I don't think that it works for the Child class, as there is no "final" keyword equivalent in Objective-C I think.
For Objective-C frameworks like UIKit or Coredata, which are bridged to Swift, you do have the possibility to create your own subclass in Swift which is declared final.
Even if Objective-C does not know about final, the Swift can use it and expose the class to Objective-C without it, as it has no impact in how the class operates. So this compiles as well:
@objc final class MyManagedObject: NSManagedObject, Fetchable {}
The approach @Moximillian is essentially what I use. I manually declare the class with @objc and final and tell Xcode to do Category/Extension CodeGen for each Core Data entity.
Broadening this discussion (and I'm sure it's been discussed at length before), what would be the downsides of introducing Self into class definitions? I'd really like to do a couple of things: write code a bit more generically, so it can be copied and pasted into other classes (minor benefit), and more than that, I'd like to refer to the instance type in a clean way. Objective-C has instancetype, although its use in Obj-C is limited (eg, I can't write NSArray<instancetype>*).