Переполнение стека при определении индекса в CKRecord в Swift

Этот вопрос спрашивает, можно ли использовать подписку с CKRecord в Swift. Хотя я уже знал, как сделать то, что хотел вопрошавший, каждая его перестановка приводит к переполнению стека:

subscript(key: String) -> CKRecordValue? {
    get {
        return objectForKey(key) as CKRecordValue?
    }
    set {
        setObject(newValue, forKey: key)
    }
}

Переполнение стека происходит в геттере. (Я никогда не пробовал сеттер, так что это может произойти и там.) Я пытался реализовать с помощью objectForKey:, objectForKeyedSubscript: и valueForKey:. Все дают один и тот же результат: переполнение стека.

Это очень странно, так как CKRecord наверняка написан на Objective-C. Зачем ему рекурсивно вызывать метод Swift subscript? Это не имеет никакого смысла. Нейт Кук в своем ответе на вопрос задается вопросом, почему Свифт не соединяет objectForKeyedSubscript: автоматически. Что ж, возможно, код для этого не полностью запечен, но вызывает эту проблему. Мне придется попробовать это с другим классом, у которого есть objectForKeyedSubscript:.

ОБНОВИТЬ

Похоже, что objectForKeyedSubscript: обычно подключается мостом. Я создал класс в Objective-C с соответствующими методами, добавил его в заголовок моста, и индексаторы были там и скомпилированы без проблем. Более того, он работал без переполнения стека.

Это означает, что с CKRecord происходит что-то очень необычное.

ТЕОРИЯ

Если вы создаете класс в Swift, который происходит от NSObject и реализует в нем метод subscript с ключом String, он становится objectForKeyedSubscript:. (Я подозреваю, что для «чистых классов Swift» это не так.) Вы можете проверить это, импортировав свой класс Swift в Objective-C и убедившись, что objectForKeyedSubscript: там есть.

Поскольку CKRecord происходит от NSObject, реализация subscript переопределяет реализацию по умолчанию. Кроме того, кажется, что все objectForKey: и valueForKey: в конечном итоге вызывают objectForKeyedSubscript:, что приводит к (читай: «то же самое, что и») вызову subscript, что вызывает переполнение стека.

Это может объяснить, почему происходит переполнение стека. Это по-прежнему не объясняет, почему objectForKeyedSubscript: не было автоматически соединено мостом, но, возможно, это связано с тем, что определение setObject:forKeyedSubscript: имеет сигнатуру типа, немного отличающуюся от канонической: - (void)setObject:(id <CKRecordValue>)object forKeyedSubscript:(NSString *)key;. Это не имеет значения для Objective-C, но может привести к срабатыванию «кода моста». В конце концов, Swift довольно новый.


person Gregory Higley    schedule 26.11.2014    source источник
comment
Я не знаком с CKRecord, но мне кажется, что отчет об ошибке в Apple не будет неправильным шагом.   -  person matt    schedule 27.11.2014
comment
Согласовано. Теперь, когда я думаю, что понял это, это именно то, что я собираюсь сделать.   -  person Gregory Higley    schedule 27.11.2014


Ответы (3)


После некоторого тестирования и отладки (через подкласс) я обнаружил, что для CKRecord objectForKey: действительно вызывает objectForKeyedSubscript:. Кроме того, реализация subscript в классе Swift, который помечен @objc неявно (по происхождению от NSObject) или явно означает, что subscript реализован как objectForKeyedSubscript:.

Это означает, что реализация subscript на CKRecord в расширении скрывает реализацию по умолчанию, что вызывает переполнение стека.

person Gregory Higley    schedule 27.11.2014

Вот простое расширение для CKRecord, чтобы упростить подписку.

extension CKRecord {
    struct Sub {
        let record: CKRecord

        subscript(key: String) -> CKRecordValue? {
            get {
                return record.objectForKey(key) as? CKRecordValue
            }
            set {
                record.setObject(newValue, forKey: key)
            }
        }
    }

    var sub: Sub {
        return Sub(record: self)
    }

    var ????: Sub {
        return sub
    }
}

Применение:

var sub = record.sub
sub["name"] = name

/* Or */

// One does not simply subscript CKRecord
record.????["name"] = name

(кстати, я шучу про ????)

person Patrick Smith    schedule 20.05.2015

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

import CloudKit

protocol MyCKRecordValueType {
    var asObject: CKRecordValue { get }
}

extension CKRecord {
    func set<ValueType>(value: ValueType, forKey key: String) where ValueType : MyCKRecordValueType {
        let object = value.asObject
        self.setObject(object, forKey: key)
    }
    subscript(key : String) -> MyCKRecordValueType? {
        set {
            self.setObject(newValue?.asObject, forKey: key)
        }
        get {
            return object(forKey: key) as? MyCKRecordValueType
        }
    }
}

extension String : MyCKRecordValueType {
    var asObject: CKRecordValue { return self as NSString }
}
extension Bool : MyCKRecordValueType {
    var asObject: CKRecordValue { return self as NSNumber }
}
extension Int : MyCKRecordValueType {
    var asObject: CKRecordValue { return self as NSNumber }
}
extension Data : MyCKRecordValueType {
    var asObject: CKRecordValue { return self as NSData }
}

затем вы можете вызвать индекс, как и ожидалось:

let firstRecordID = CKRecordID(recordName: "0")

        let record = CKRecord(recordType: "Foo", recordID: firstRecordID)

        record["title"] = "Hello World"

        record["year_established"] = 2000
person Hobbes the Tige    schedule 11.10.2017