Как использовать общие типы для получения объекта того же типа

У меня есть расширение для NSManagedObject, которое должно помочь мне передавать объекты между контекстами:

extension NSManagedObject {

    func transferTo(#context: NSManagedObjectContext) -> NSManagedObject? {

        return context.objectWithID(objectID)
    }

}

на данный момент он возвращает объект NSManagedObject, и я должен привести его к классу, который я хочу, например:

let someEntity: MyEntity = // ...create someEntity
let entity: MyEntity = someEntity.transferTo(context: newContext) as? MyEntity

Есть ли способ в Swift избежать этого бесполезного приведения, и если я вызову transferTo(context: ...) из объекта класса MyEntity, чтобы он возвращал тип MyEntity?


person Oleg_Korchickiy    schedule 30.01.2015    source источник


Ответы (3)


Обновление: лучшее решение см. в ответе Роба.


Точно так же, как в Как я могу создавать экземпляры подклассов управляемых объектов в расширении NSManagedObject Swift?, это можно сделать с помощью универсального вспомогательного метода:

extension NSManagedObject {

    func transferTo(context context: NSManagedObjectContext) -> Self {
        return transferToHelper(context: context)
    }

    private func transferToHelper<T>(context context: NSManagedObjectContext) -> T {
        return context.objectWithID(objectID) as! T
    }
}

Обратите внимание, что я изменил тип возвращаемого значения на Self. objectWithID() не возвращает необязательное значение (в отличие от objectRegisteredForID(), поэтому здесь нет необходимости возвращать необязательное значение.

Обновление: Жан-Филипп Пелле предложил определить глобальную повторно используемую функцию вместо вспомогательного метода для привести возвращаемое значение к соответствующему типу.

Я бы предложил определить две (перегруженные) версии, чтобы это работало как с необязательными, так и с необязательными объектами (без нежелательного автоматического переноса в необязательный):

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

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

extension NSManagedObject {

    func transferTo(context context: NSManagedObjectContext) -> Self {
        let result = context.objectWithID(objectID) // NSManagedObject
        return objcast(result) // Self
    }

    func transferUsingRegisteredID(context context: NSManagedObjectContext) -> Self? {
        let result = context.objectRegisteredForID(objectID) // NSManagedObject?
        return objcast(result) // Self?
    }
}

(Я обновил код для Swift 2/Xcode 7. Код для более ранних версий Swift можно найти в истории редактирования.)

person Martin R    schedule 30.01.2015
comment
Интересный! Это, вероятно, оправдывает использование глобальной функции, подобной этой, вместо того, чтобы воссоздавать вспомогательные методы, когда возникает необходимость? func cast<T>(obj: Any, type: T.Type) -> T? { return obj as? T } Я обновил свой ответ, чтобы предложить его. - person Jean-Philippe Pellet; 30.01.2015
comment
@Jean-PhilippePellet: Хорошая идея, но аргумент obj, возможно, должен быть Any?, в противном случае кажется, что он работает неправильно при приведении необязательных параметров, таких как NSManagedObject?, возвращаемый objectRegisteredForID(). - person Martin R; 30.01.2015
comment
О да, вы правы, конечно. Я все еще слишком много думаю о Scala, где необязательные параметры не упаковываются автоматически. - person Jean-Philippe Pellet; 30.01.2015
comment
Попользовавшись этим некоторое время, я обнаружил увлекательную проблему. Если вы используете его для передачи объекта, за которым наблюдалось КВО, он падает. Self в этом случае является подклассом KVO, а результат objectWithID является его суперклассом. Таким образом, вы получаете сообщение об ошибке, например Не удалось преобразовать значение типа «myapp.Thing» (0xdeadbeef) в «myapp.Thing» (0xfdfdfdfd). Все еще работаю над элегантным решением. - person Rob Napier; 14.06.2016
comment
Нашел решение; добавлено в качестве ответа. - person Rob Napier; 14.06.2016

Мне давно нравилось решение Мартина, но недавно я столкнулся с ним. Если за объектом наблюдали КВО, то произойдет сбой. Self в этом случае является подклассом KVO, а результат objectWithID не является этим подклассом, поэтому вы получите сбой типа «Не удалось привести значение типа 'myapp.Thing' (0xdeadbeef) к 'myapp.Thing' (0xfdfdfdfd )". Есть два класса, которые называют себя myapp.Thing, а as! использует реальный объект класса. Так что Swift не обмануть благородной ложью классов KVO.

Решение состоит в том, чтобы заменить Self параметром статического типа, переместив его в контекст:

extension NSManagedObjectContext {
    func transferredObject<T: NSManagedObject>(object: T) -> T {
        return objectWithID(object.objectID) as! T
    }
}

T полностью определяется во время компиляции, поэтому это работает, даже если object тайно является подклассом T.

person Rob Napier    schedule 14.06.2016
comment
@MartinR Немного более длинную версию см. в разделе gist.github.com/rnapier/5835b4014ec23663512e7daea61f217d. - person Rob Napier; 14.06.2016
comment
Я могу подтвердить как проблему, так и ваше решение! - person Martin R; 14.06.2016
comment
Я пытался решить эту же проблему, и мой и ваш код — с использованием статических параметров, а не Self — столкнулись с проблемой в оптимизированных сборках. Вы видели что-нибудь подобное? stackoverflow.com/questions/39109373/ - person Sixten Otto; 24.08.2016

Это поможет:

func transferTo(#context: NSManagedObjectContext) -> Self?

В месте вызова Self разрешается в статически известный тип объекта, для которого вы вызываете этот метод. Это также особенно удобно использовать в протоколах, когда вы не знаете окончательный тип, который будет соответствовать протоколу, но все же хотите сослаться на него.

Обновление: Ответ Мартина Р. указывает, что вы не можете сразу преобразовать полученный объект. Я бы тогда сделал что-то вроде этого:

// top-level utility function
func cast<T>(obj: Any?, type: T.Type) -> T? {
    return obj as? T
}

extension NSManagedObject {

    func transferTo(#context: NSManagedObjectContext) -> NSManagedObject? {
        return cast(context.objectWithID(objectID), self.dynamicType)
    }

}
person Jean-Philippe Pellet    schedule 30.01.2015
comment
Это то, что я попробовал первым, но вам каким-то образом нужно преобразовать возвращаемое значение из context.objectWithID(objectID), которое равно NSManagedObject, в фактический тип возвращаемого значения, а as? Self или подобное не компилируется (или я не мог понять, как это сделать). - person Martin R; 30.01.2015
comment
Теперь у нас есть цикл сохранения между нашими ответами :) В этом конкретном случае, когда мы уже знаем, что объект имеет желаемый тип, unsafeBitCast() также будет работать. - person Martin R; 30.01.2015
comment
Хорошо!, но IMO, параметр должен быть AnyObject по двум причинам. 1) Any занимает 32-битную память, преобразование экземпляра класса в Any требует ЦП и памяти. 2) В отличие от Any, мы не можем передать Optional в параметр AnyObject, это безопаснее, чем Any. - person rintaro; 30.01.2015
comment
Мне кажется, что на самом деле нужны две функции: func cast<T>(obj: AnyObject?, type: T.Type) -> T? и func cast<T>(obj: AnyObject, type: T.Type) -> T. - person Martin R; 30.01.2015
comment
исправление: Any занимает 32 бита → Any занимает 32байта - person rintaro; 30.01.2015