Передаются ли элементы массива протокола по значению или ссылке?

Я знаю, что структуры передаются по значению, а классы передаются по ссылке в Swift.

Интересно, создам ли я элементы хранилища массива, которые предоставляют протокол. Эти элементы передаются по значению или ссылке?

Основано ли это на определении модели как класса или структуры?

class ClassA: ProtocolA {
// something
}

struct StructA: ProtocolA {
// something
}

var arr: [ProtocolA] = [ClassA(), StructA()]

exampleFunction(arr[0]) // is it passed by reference
exampleFunction(arr[1]) // is it passed by value


person excE    schedule 29.01.2021    source источник


Ответы (3)


Протоколы следует рассматривать как типы значений, потому что вам нужно явно указать компилятору, что это ссылочный тип, определив его как соответствующий AnyObject (которому соответствуют все классы).

Итак, если у вас есть

protocol ProtocolB: AnyObject

тогда любой тип, соответствующий протоколу B, будет отправлен по ссылке, а в противном случае — нет.

Вот упрощенный пример

protocol ProtocolA {
    var x: Int { get set }
}

protocol ProtocolB: AnyObject {
    var y: Int { get set }
}

class ClassA: ProtocolA, ProtocolB {
    var x = 0
    var y = 0
}

func exampleFunction(_ object: ProtocolA) {
    object.x += 2 // <-- This will generate a compilation error
}

func exampleFunction(_ object: ProtocolB) {
    object.y += 2 // This is fine
}
person Joakim Danielson    schedule 29.01.2021
comment
Почему первая функция exampleFunction генерирует ошибку? - person excE; 29.01.2021
comment
Поскольку object объявлено (константа) - person Joakim Danielson; 29.01.2021
comment
Если я запишу это как func exampleFunction(_ object: inout ProtocolA), это все еще константа? - person excE; 29.01.2021
comment
@excE Почему бы не попробовать себя? - person Joakim Danielson; 29.01.2021
comment
Спасибо за все ответы. Теперь это кристально ясно. Я попробовал это, и это сработало как для структуры, так и для класса с & и inout - person excE; 29.01.2021

Когда вы сохраняете переменную как тип протокола, компилятор обрабатывает ее, как если бы это был тип значения. Итак, если exampleFunction принимает входной аргумент типа ProtocolA, чтобы вы могли изменить свойство входного аргумента, вам нужно объявить его как inout.

Это не означает, что значения обязательно передаются по значению, это просто означает, что компилятор понятия не имеет, является ли входной аргумент значением или ссылочным типом, поэтому во время компиляции он обрабатывает его как тип значения.

Если вы хотите иметь возможность обрабатывать переменную протокола как ссылочный тип, вам необходимо сделать привязанным класс протокола. protocol ProtocolA: class {}

person Dávid Pásztor    schedule 29.01.2021

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

protocol ProtocolA {
    var title: String { get set}
}

class ClassA: ProtocolA {
    var title = "ClassA"
}

struct StructA: ProtocolA {
    var title = "StructA"
}

var arr: [ProtocolA] = [ClassA(), StructA()]

print(arr[0].title) // ClassA
print(arr[1].title) // StructA

func exampleFunction(_ obj: ProtocolA) {
    var obj = obj // Create local mutable variable
    obj.title = obj.title + "!!!"
}

exampleFunction(arr[0])
exampleFunction(arr[1])

print(arr[0].title) // ClassA!!!
print(arr[1].title) // StructA
person andruvs    schedule 29.01.2021
comment
Вы не обновляете объект, переданный функции для структуры, поэтому я не уверен, что доказывает этот пример? - person Joakim Danielson; 29.01.2021
comment
Вопрос был в том, передается ли объект по ссылке или по значению, а не в том, как изменить переданный объект внутри функции. В примере видно, что класс передается по ссылке, так как изменения в title происходят после завершения функции. - person andruvs; 29.01.2021
comment
Хорошо, тогда я немного запутался в вашей формулировке, но обратите внимание, что вопрос действительно касается протоколов, а не объектов. - person Joakim Danielson; 29.01.2021