Generic method in class extension


(Rick M) #1

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.


[Pitch] Adding a Self type name shortcut for static member access
(Lasse Jansen) #2

You can work around this with a protocol, an extension and by making the subclass final. You also need a static func instead of a class func.

class ManagedObject {
  static func fetch() -> [Any] {
    return [Child(name: "Test")]
  }
}

final class Child: ManagedObject, TypedFetchable {
  let name: String
  
  init( name: String) {
    self.name = name
    super.init()
  }
}

protocol TypedFetchable where Self: ManagedObject {
  static func all() -> [Self]
}

extension TypedFetchable {
  static func all() -> [Self] {
    return fetch() as! [Self]
  }
}

Example use:

let all = Child.all()
let name = all.first!.name // -> "Test"

(Rick M) #3

Hmm. The Child class (and ManagedObject) are @objc; is this still possible?


(Mox) #4

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:

class ManagedObject {}

protocol TypedFetchable where Self: ManagedObject {
  init(name: String)
}

extension TypedFetchable {
  static func all() -> [Self] {
    return [Self(name: "Test")]
  }
}

(Lasse Jansen) #5

@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.


(Mox) #6

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

(Dave Reed) #7

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.


(Rick M) #8

Ah, one thing I haven't mentioned: the Child class is defined in Objective-C (old code).


(Rick M) #9

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>*).


(Xiaodi Wu) #10

This is SE-0068, approved years ago. Implementation is ongoing.


(Mox) #11

Swift 5.1 will have much improved Self usage