Быстрый массив объектов, типизированных протоколом

#inheritance #swift #dynamic-linking

#наследование #быстрый #динамическое связывание

Вопрос:

У меня есть следующее наследование:

 protocol P {
    var a : Int { get set }
}

class C : P {
    var a : Int
    ...
}
  

И затем я хочу создать обобщенный массив и выполнить итерацию по нему с помощью некоторого действия:

 class Main {
    var array : Array<Proto>
    var inst : Proto
    ...
    func foo() {
        array.append(C(a:10))
        for obj in array {
            obj.a = 20      //Error: Cannot assign to the result of this expression
        }

        inst = C(a:10)
        inst.a = 20         //Works correctly

        for var i = 0; i < arr.count;   i {
            inst = arr[i]
            inst.a = 20     //No problem even here
        }
    }
}
  

Если я приведу: (obj как C).a = 20 — тогда все в порядке.
Может кто-нибудь объяснить это поведение?

Ответ №1:

Проблема в том, что цикл for-in не позволяет изменять итерированный элемент. Из документации:

ПРИМЕЧАНИЕ

Константа индекса существует только в пределах области действия цикла. Если вы хотите проверить значение index после завершения цикла или если вы хотите работать с его значением как с переменной, а не как с константой, вы должны объявить его самостоятельно перед его использованием в цикле.

Вы можете попробовать следующее:

     for obj in array {
        var c = obj
        c.a = 20      // This should work as c is a mutable copy of obj
    }
  

Дополнительный:

Обратите внимание, что @ikuramedia сказал правильно. Приведенный выше код маскирует случай, когда obj это тип значения, и в этом случае c это будет его копия, а значение obj в массиве фактически не будет изменено.

Комментарии:

1. Это не очень хороший ответ. Было бы лучше убедиться, что у нас есть тип класса. Это даже не приведет к сбою.

Ответ №2:

Протоколы могут применяться к классам, структурам и перечислениям одинаково, поэтому в вашем коде компилятор не может предполагать, что obj является типом объекта. Из-за этого он не знает, является ли obj ссылочным типом или типом значения. Если бы это был тип значения, вы не могли бы изменить объект без метода изменения, и поэтому предполагается, что ваш код неверен и не может быть скомпилирован.

Вы можете либо объявить массив как тип AnyObject, соответствующий Proto, либо вы можете понизить в итераторе цикла for: for obj as! C in array

Комментарии:

1. или протокол как @class_protocol .

2. @ikuramedia: Как бы вы поступили? declar[ing] array to be of type AnyObject conforming to Proto

3. @Senseful — На самом деле вы правы, я не думаю, что вы можете объявить это напрямую. Вы либо можете объявить Протокол протоколом класса, и тогда массив будет иметь типы, соответствующие протоколу. Или вы можете объявить ссылочный тип, соответствующий протоколу, и объявить массив этого типа (но тогда члены вашего массива должны быть экземплярами этого типа). Скорее всего, я бы предпочел понизить в цикле и позволить программе проверки типов выполнить работу по обеспечению получения ссылочных типов в моем массиве, а затем условно проверить, соответствуют ли они протоколу, прежде чем использовать их.

Ответ №3:

Проблема уже была описана другими — подведем итог:

  1. итератор всегда постоянен
  2. протокол может быть адаптирован как классами, так и структурами

Если бы протокол был адаптирован структурой, назначение завершилось бы неудачно, потому что мы не можем присваивать свойства постоянным структурам (а итератор obj является постоянным).

Есть два подхода к исправлению этого:

  1. Объявите протокол как протокол класса, чтобы он не мог быть адаптирован структурами:

     protocol P : class {
        var a : Int { get set }
    }
      

    Это гарантирует, что любые объекты, соответствующие P должны быть ссылочными типами.

  2. Измените итерацию, чтобы она работала как для структур, так и для классов

     for i in indices(array) {
        array[i].a = 20
    }
      

    Теперь мы можем изменять свойства независимо от того, являются ли элементы значениями или ссылочными типами. Однако обратите внимание, что такая итерация допустима только в том случае, если вы изменяете содержимое элементов на месте. Вставки и удаления приведут к аннулированию индексов, возвращаемых indices(array) .

    Кроме того, вы должны позаботиться о том, чтобы убедиться, что вы изменяете содержимое на месте. Конкретно, подобный код фактически не изменит содержимое массива, если элементы являются типами значений:

     for i in indices(array) {
        var element = array[i]
        element.a = 20
    }