#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:
Проблема уже была описана другими — подведем итог:
- итератор всегда постоянен
- протокол может быть адаптирован как классами, так и структурами
Если бы протокол был адаптирован структурой, назначение завершилось бы неудачно, потому что мы не можем присваивать свойства постоянным структурам (а итератор obj
является постоянным).
Есть два подхода к исправлению этого:
-
Объявите протокол как протокол класса, чтобы он не мог быть адаптирован структурами:
protocol P : class { var a : Int { get set } }
Это гарантирует, что любые объекты, соответствующие
P
должны быть ссылочными типами. -
Измените итерацию, чтобы она работала как для структур, так и для классов
for i in indices(array) { array[i].a = 20 }
Теперь мы можем изменять свойства независимо от того, являются ли элементы значениями или ссылочными типами. Однако обратите внимание, что такая итерация допустима только в том случае, если вы изменяете содержимое элементов на месте. Вставки и удаления приведут к аннулированию индексов, возвращаемых
indices(array)
.Кроме того, вы должны позаботиться о том, чтобы убедиться, что вы изменяете содержимое на месте. Конкретно, подобный код фактически не изменит содержимое массива, если элементы являются типами значений:
for i in indices(array) { var element = array[i] element.a = 20 }