Swift универсальный табличный источник данных и делегат, поддерживаемые протоколом NSManagedObjects, не могут получить атрибут сущности, ошибка времени выполнения - PullRequest
0 голосов
/ 13 ноября 2018

Проблема

break point screenshot

*** Завершение работы приложения из-за необработанного исключения «NSUnknownKeyException», причина: «[valueForUndefinedKey:]: этот класс не совместим со значением ключа для активного ключа. '

Благодаря @GrahamPerks ответу на этот ТАК * вопрос я вставил точку прерывания исключения в свой код, которая теперь приостанавливает выполнение в этой строке ...

static var entityActive: Bool {
    return entity.value(forKey: "active") as! Bool // <-- PAUSES AT THIS LINE
}

Это, очевидно, требует дальнейшего объяснения ...

Фон

Я пишу приложение Core Data, в котором используется общий источник данных табличного представления и делегат, более или менее настроенный в соответствии с книгой Флориана Куглера «Основные данные», опубликованной в 2017 году objc.io .

Я успешно связал три отдельных UITableViewController с этим общим источником данных / делегатом. Я использую одну основную раскадровку, и эти три контроллера связаны с тремя UISplitViewController основными / подробными представлениями.

Я удалил автоматически сгенерированные соединения dataSource и делегата внутри раскадровки UITableView s (хотя, если я оставлю или удалю эти соединения, похоже, не имеет значения).

Если я закомментирую код для static var entityActive выше, проект будет успешно построен и запущен.

Мой код использует метод UITableViewDelegate tableView(_, willDisplay:, forRowAt:) для изменения .textColor текста и .backgroundColor ячеек на основе атрибута «active», хранящегося в виде логического значения скалярного типа для каждой сущности.

Для ясности я пытаюсь получить атрибут "активный" (у каждого объекта в моей модели данных есть общий атрибут "активный") для каждого NSManagedObject для объекта. Значение этого «активного» атрибута (Bool true или false) для каждого объекта затем используется для форматирования ячейки в операторе if ... else в следующем коде.

Чтобы попытаться максимизировать повторное использование кода и свести к минимуму повторение кода, я также поместил этот метод делегата в мой общий источник данных / класс делегата.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

    let entityObjectActive = T.entityActive

    if entityObjectActive == true {
        cell.textLabel?.textColor = UIColor.black
        cell.detailTextLabel?.textColor = UIColor.darkGray
        cell.backgroundColor = UIColor.white
    } else if entityObjectActive == false {
        cell.textLabel?.textColor = UIColor.lightGray
        cell.detailTextLabel?.textColor = UIColor.lightGray
        cell.backgroundColor = UIColor.clear
    }

}

Компилятор не жалуется.

Кажется, я могу использовать универсальный тип T (представляющий сущности базовых данных), чтобы связать статическое свойство entityActive с моим экземпляром entityObjectActive - так что, похоже, это работает ...

let entityObjectActive = T.entityActive

Для подтверждения у меня есть следующее:

общий класс источника данных ....

class MyDataSource<T: Managed, 
                   Delegate: TableViewDataSourceDelegate>: 
                   NSObject, 
                   UITableViewDataSource, 
                   UITableViewDelegate, 
                   NSFetchedResultsControllerDelegate {

    // lots of code...
}

протокол ...

protocol Managed: class, NSFetchRequestResult {
    static var entity: NSEntityDescription { get }
    static var entityActive: Bool { get } }
}

и расширение ...

extension Managed where Self: NSManagedObject {
    static var entity: NSEntityDescription { return entity()  }
    static var entityActive: Bool {
        return entity.value(forKey: "active") as! Bool
    }
}

Попытки решения проблемы

Я немного читал, пытаясь решить мою проблему, включая обзор многих блогов (о том, как настроить универсальные источники в общих источниках данных и источниках представления общих таблиц), а также множество вопросов и ответов SO, в частности ...

Как исправить ошибку: этот класс не совместим со значением ключа для tableView ключа. '

Неопределенное исключение: этот класс не соответствует кодировке значения ключа

setValue: forUndefinedKey: этот класс не соответствует значению ключа для кода

Кажется, что все вопросы и ответы по SO напрямую связаны с проблемой соединений IB внутри раскадровок. Может быть, это тоже моя проблема, но если это так, то мне кажется, что она очень хорошо спрятана.

Любая помощь или совет, пожалуйста?

PS: я изучаю Swift после того, как я долгое время работал программистом в Obj-C, и я действительно борюсь со смещением парадигмы Generics Protocols Extensions, так что может случиться так, что мое использование универсальных типов неверно?

1 Ответ

0 голосов
/ 14 ноября 2018

Спасибо тем, чьи комментарии указали мне на это решение ...

Мой ПЕРЕСМОТРЕННЫЙ протокол Managed ...

protocol Managed: class, NSFetchRequestResult {
    static var entity: NSEntityDescription { get }
    var attributeActive: Bool { get }  // <-- REMOVED static 
}

Мое пересмотренное расширение Managed ...

ОБНОВЛЕНИЕ - добавлено var attributeActive в Managed расширение ...

extension Managed where Self: NSManagedObject {
    static var entity: NSEntityDescription { return entity()  }

    var attributeActive: Bool {
         guard let attribute = self.value(forKey: "active") as? Bool else {
             return false // in case key "active" is not set
         }
         return attribute
    }
}

ОБНОВЛЕНИЕ - удалено var attributeActive из расширений управляемого объекта, поскольку больше не требуется ...

Мои пересмотренные расширения для каждого из моих трех основных объектов данных (данные которых отображаются в каждом из трех отдельных UITableViewController с) ...

extension <<DataModelEntity>>: Managed {    
    public var attributeActive: Bool {
        return self.active
    }

    @NSManaged public var active: Bool
    @NSManaged public var <<OTHER DATA MODEL ENTITY ATTRIBUTES>> //...
    // ...etc.
}

Возможно, для ясности стоит отметить, что значение Codegen для каждого объекта в модели данных установлено на Ручной / Нет , поэтому у меня есть вручную 1 подготовленные классы и расширения для каждой сущности.

1 Когда я пишу вручную, я имею в виду, что я использовал функцию Создать управляемый объект подкласс ... в меню Редактор в XCode, а затем вручную вошел в мои заглушки протокола Managed.

Наконец, мой ОБЩИЙ класс делегата источника общих данных ...

class MyDataSource<T: Managed, 
                   Delegate: TableViewDataSourceDelegate>: 
                   NSObject, 
                   UITableViewDataSource, 
                   UITableViewDelegate, 
                   NSFetchedResultsControllerDelegate {

    // lots of code...

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        let object = fetchedResultsController.object(at: indexPath)
        let objectActive = object.attributeActive

        if objectActive == true {
            cell.textLabel?.textColor = UIColor.black
            cell.detailTextLabel?.textColor = UIColor.darkGray
            cell.backgroundColor = UIColor.white
        } else if entityObjectActive == false {
            cell.textLabel?.textColor = UIColor.lightGray
            cell.detailTextLabel?.textColor = UIColor.lightGray
            cell.backgroundColor = UIColor.clear
        }
    }

    // lots more code...

}

Если кто-то еще читает это эссе, может быть, вас интересуют причины этого решения?

Честно говоря, я все еще выясняю детали и планирую добавить более краткую / точную причину в будущем, поскольку мое понимание улучшается в отношении быстрых обобщений, протоколов и расширений, но сейчас я предоставляю следующее ...

Как указано в комментариях, я неправильно пытался получить свойство сущности на основе атрибута модели данных из описания сущности. Это все равно что пытаться получить цвет или размер кирпича Lego, спрашивая у блока Lego характеристики одного из его кирпичей. "Какой кирпич?" может ли коробка спросить, может ли коробка Lego спросить такую ​​вещь?

По сути, я сделал пару ошибок. Я не понимал, какое влияние статическое определение оказало на мои переменные, и я не понимал, как мой протокол и расширение Managed взаимодействовали с любыми классами, которые приняли этот протокол.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...