#swift #cocoa #core-data #nsmanagedobject
#swift #cocoa #core-data #nsmanagedobject
Вопрос:
Контекст
Я создаю приложение на macOS Catalina (10.15.6), используя Xcode 12. Приложение использует Core Data. Я позволил Xcode генерировать подклассы моих объектов NSManagedObject с использованием Swift. Они выглядят следующим образом:
// LPProject CoreDataClass.swift
import Foundation
import CoreData
@objc(LPProject)
public class LPProject: NSManagedObject
{
@objc func printStuff() {
NSLog("This is a function to test the situation.")
}
}
И другой автоматически сгенерированный файл:
// LPProject CoreDataProperties.swift
import Foundation
import CoreData
extension LPProject {
@nonobjc public class func fetchRequest() -> NSFetchRequest<LPProject> {
return NSFetchRequest<LPProject>(entityName: "LPProject")
}
}
Я опустил свойства объекта, потому что они не имеют отношения к проблеме.
Проблема
Часть моего приложения по-прежнему Objective-C. В этой части я создаю LPProject
сущность и пытаюсь вызвать printStuff()
:
// Assume 'moc' is an NSManagedObjectContext defined elsewhere.
LPProject *project = [NSEntityDescription insertNewObjectForEntityForName:@"LPProject" inManagedObjectContext:moc];
[project printSomeStuff];
В macOS 10.14.6 и 10.15 это работает отлично.В более старых версиях это приводит к сбою «Нераспознанного селектора»:
-[NSManagedObject printStuff]: unrecognized selector sent to instance 0x01038202384929
Это происходит потому, что имя модуля добавляется к имени класса. Я вижу это сообщение в журнале:
warning: Unable to load class named 'MyApp.LPProject' for entity 'LPProject'.
Class not found, using default NSManagedObject instead.
Согласно всему, что я могу найти, объект правильно настроен в редакторе Core Data для работы с @objc()
объявлением:
Итак…чего я не понимаю? Почему имя модуля добавляется к имени класса в версии 10.14.5 и ниже, но не в версии 10.14.6 ?
Комментарии:
1. Обновление: Запуск команды ‘otool -ov’ в скомпилированном двоичном файле в macOS 10.13 показывает, что класс ObjC и метод -printStuff ОПРЕДЕЛЕНЫ . (Это имеет смысл, потому что, если бы это было не так, сбой произошел бы также в 10.14 и 10.15, и компилятор выдал бы ошибку.) Причина, по-видимому, в том, что по какой-то причине Core Data в версии 10.13 не возвращает надлежащую сущность.
Ответ №1:
Я много чего перепробовал, в том числе на основе других ответов SO:
-
Удалите
@obc(LPProject)
атрибут и используйте@objc LPProject
вместо него. Это исправило macOS 10.11 -> 10.13, но прервало облегченную миграцию в версиях 10.14 и 10.15. -
Удалите «Модуль» для всех объектов в редакторе моделей Xcode и разрешите Core Data использовать глобальное пространство имен. Это нарушило работу современной macOS и противоречит последним рекомендациям Apple.
Суть
Core Data в macOS 10.14.6 решает, MyApp.LPProject
чтобы найти класс LPProject
и, таким образом, создать экземпляр правильного подкласса NSManagedObject. Однако в более старых версиях macOS Core Data не удается соединить эти два, поэтому вместо создания экземпляра LPProject
объекта он возвращается к универсальному NSManagedObject
объекту.
Следовательно, в macOS 10.14.5 и ниже мы должны сообщить Core Data, что чертово имя класса для объекта с именем «LPProject» является LPProject
вместо MyApp.LPProject
. Мы должны повторить это для каждого объекта в нашей модели. Чтобы сделать это, мы можем перехватить точку загрузки модели:
- (NSManagedObjectModel *) managedObjectModel
{
if (_managedObjectModel) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"myDataFile" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
if (@available(macOS 10.14.6, *))
{
// Do nothing; not needed
}
else
{
NSArray<NSEntityDescription*> *entities = [_managedObjectModel entities];
NSMutableArray *fixedEntities = [NSMutableArray arrayWithCapacity:entities.count];
for (NSEntityDescription *des in entities)
{
if ([des.managedObjectClassName hasPrefix:@"MyApp."] amp;amp; des.managedObjectClassName.length > 6)
{
NSString *fixedName = [des.managedObjectClassName substringFromIndex:6];
des.managedObjectClassName = fixedName;
}
[fixedEntities addObject:des];
}
[_managedObjectModel setEntities:fixedEntities];
}
return _managedObjectModel;
}
Результат
Вышесказанное полностью решает проблему. В старых версиях macOS Core Data теперь корректно находит правильный подкласс NSManagedObject для каждого объекта. Это также работает в современных версиях macOS. Облегченная миграция продолжает работать и во всех версиях.
Примечание: Этот подход используется с @objc(LPProject)
атрибутом в подклассе NSManagedObject (автоматически сгенерированном Xcode 12) И установкой для «Модуля» каждого объекта значения «Текущий модуль продукта» в графическом интерфейсе редактора Core Data Model в Xcode.
Примечание 2: Я понятия не имею, нормально ли это. У него есть все признаки уродливого взлома, но (А) он РАБОТАЕТ и (Б) он ничего не меняет в macOS 10.14.6 , которая используется большинством пользователей.
Примечание 3: Если вы используете это, обязательно отрегулируйте 6
количество символов в имени ВАШЕГО модуля (включая точку). В противном случае у вас будет плохой день.
Комментарии:
1. Примечание: Проблема появляется в macOS 10.14.5 и ниже, поэтому я обновил свой ответ, указав правильную версию macOS для target.