Подкласс Swift NSManagedObject неправильно добавляет имя модуля в версии 10.13 и ниже

#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:

  1. Удалите @obc(LPProject) атрибут и используйте @objc LPProject вместо него. Это исправило macOS 10.11 -> 10.13, но прервало облегченную миграцию в версиях 10.14 и 10.15.

  2. Удалите «Модуль» для всех объектов в редакторе моделей 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.