Проблема
Как и многие iOS разработчики, я столкнулся с дилеммой: какой объект использовать для построения архитектуры проекта. Взять для примера реализацию паттерна фасад. Этот объект должен принять некоторое количество сущностей и реализовать методы для упрощенного доступа к ним. Если не вдаваться в подробности, то подойдет и класс, и структура: оба могут инкапсулировать объекты и функции. Так что же выбрать?
А что говорит Apple?
Если обратиться к статье на сайте Apple Choosing Between Structures and Classes, то следуя пункту «Use structures by default.», нужно использовать структуры в нашем случае. Если конечно не нужна Objective-C совместимость.
Тестовые объекты
Объекты наполнения для целевых объектов:
protocol ObjectForProperties { var queue: DispatchQueue { get } // reference type var bool: Bool { get } // value type } final class ClassForProperty: ObjectForProperties { var queue: DispatchQueue = .main var bool: Bool = true } struct StructForProperty: ObjectForProperties { var queue: DispatchQueue = .main var bool: Bool = true } var classForProperty = ClassForProperty() // reference type var structForProperty = StructForProperty() // value type var array = [1] // value type with Copy-on-Write
Целевые объекты:
// Используем класс без использования протоколов final class UseClassWithoutProtocols { var ref: ClassForProperty var value: StructForProperty var array: [Int] init(ref: ClassForProperty, value: StructForProperty, array: [Int]) { self.ref = ref self.value = value self.array = array } } // Используем класс с использованием протоколов final class UseClassWithProtocols { var ref: ObjectForProperties var value: ObjectForProperties var array: [Int] init(ref: ObjectForProperties, value: ObjectForProperties, array: [Int]) { self.ref = ref self.value = value self.array = array } } // Используем структуру без использования протоколов struct UseStructWithoutProtocols { var ref: ClassForProperty var value: StructForProperty var array: [Int] } // Используем структуру с использованием протоколов struct UseStructWithProtocols { var ref: ObjectForProperties var value: ObjectForProperties var array: [Int] } let useClassWithoutProtocols = UseClassWithoutProtocols( ref: classForProperty, value: structForProperty, array: array ) let useClassWithProtocols = UseClassWithProtocols( ref: classForProperty, value: structForProperty, array: array ) var useStructWithoutProtocols = UseStructWithoutProtocols( ref: classForProperty, value: structForProperty, array: array ) var useStructWithProtocols = UseStructWithProtocols( ref: classForProperty, value: structForProperty, array: array )
Размещение в памяти объектов
Структуры передаются копированием значения. У классов вместо копирования используется ссылка на существующий экземпляр. Для некоторых стандартных типов значения реализован механизм Copy-on-Write, который позволяет передавать объект передавая адрес и копировать только если объект изменяется. Но Copy-on-Write не реализован у структур по-умолчанию, то есть StructForProperty будет передаваться копированием в любом случае.
Проверим размещение объектов в памяти (опустим закрытые протоколом значения для чистоты эксперимента):
func address<T>(of o: UnsafePointer<T>) -> String { String(format: "%p", Int(bitPattern: o)) } func address<T: AnyObject>(of classInstance: T) -> String { String(format: "%p", unsafeBitCast(classInstance, to: Int.self)) } // classForProperty addresses print(address(of: classForProperty)) // 0x6000020e9600 print(address(of: useClassWithoutProtocols.ref)) // 0x6000020e9600 print(address(of: useStructWithoutProtocols.ref)) // 0x6000020e9600 // structForProperty addresses print(address(of: &structForProperty)) // 0x111a1bca8 print(address(of: &useClassWithoutProtocols.value)) // 0x600002edb6d8 print(address(of: &useStructWithoutProtocols.value)) // 0x111a1bcd8 // array addresses print(address(of: &array)) // 0x600002edb2f0 print(address(of: &useClassWithoutProtocols.array)) // 0x600002edb2f0 print(address(of: &useStructWithoutProtocols.array)) // 0x600002edb2f0
Что и требовалось доказать:
-
Ссылочный объект имеет только один экземпляр в памяти.
-
Тип значения имеет столько экземпляров в памяти, сколько раз он передавался.
-
Тип значения с Copy-on-Write имеет только один экземпляр в памяти.
Занимаемая память
В большинстве случаев структуры полностью хранятся в Stack, а классы хранятся в Heap, и ссылка на объект хранится в Stack. Stack имеет ограниченный размер. Heap не имеет ограничений по размеру. Из чего следует вывод, что память в Stack нужно беречь. Большая статья про память.
MemoryLayout.size(ofValue: useClassWithoutProtocols) // Размер в Stack 8 class_getInstanceSize(UseClassWithoutProtocols.self) // Общий размер 48 MemoryLayout.size(ofValue: useClassWithProtocols) // Размер в Stack 8 class_getInstanceSize(UseClassWithProtocols.self) // Общий размер 104 MemoryLayout.size(ofValue: useStructWithoutProtocols) // Размер в Stack 32 MemoryLayout.size(ofValue: useStructWithProtocols) // Размер в Stack 88
Если беречь память Stack, то очевидно нужно использовать классы. Если брать общий размер, то оптимальным решением будет использование структур без использования протоколов. Но в таком случае страдает тестируемость, принципы SOLID и конечно Протокольно-ориентированное программирование. Так же не стоит забывать про копирование: размер занимаемой память структурой прямо пропорционален количеству присваиваний объекта.
Время доступа
В теории время доступа к значениям класса дольше, так как переход по ссылкам требует большего времени. В то время как структуры находятся здесь и сейчас.
Замер получения булевой переменной в разных конфигурациях:
func countTime(_ workItem: () -> Void) -> Double { let start = CFAbsoluteTimeGetCurrent() workItem() return CFAbsoluteTimeGetCurrent() - start } // для примера // повторить для разных конфигураций countTime { let _ = useClassWithoutProtocols.ref.bool }
Я провел несколько тестов и получил следующие средние значения:
|
|
Время доступа к ссылочному типу |
Время доступа к типу значения |
|
useClassWithoutProtocols |
0,000009597 |
0,000007105 |
|
useClassWithProtocols |
0,000009501 |
0,000008715 |
|
useStructWithoutProtocols |
0,000008059 |
0,000008597 |
|
useStructWithProtocols |
0,000007011 |
0,000007714 |
Значения примерно равнозначны. Однако стоит отметить, что использования структуры со значениями ссылочных типов, закрытых протоколами, незначительно экономит время доступа.
Выводы
|
|
Stack |
Heap |
Время |
Принципы |
|
useClassWithoutProtocols |
+ |
— |
— |
— |
|
useClassWithProtocols |
+ |
— |
|
+ |
|
useStructWithoutProtocols |
— |
+ |
|
_ |
|
useStructWithProtocols |
— |
+ |
+ |
+ |
-
Использовать структуры с использованием протоколов плохо для Stack, но хорошо для скорости выполнения.
-
Использовать классы хорошо для Stack.
Так как важно следить за Stack и соблюдать принципы, а время доступа незначительно отличается, я бы советовал использовать классы и протоколы для построения архитектуры.
ссылка на оригинал статьи https://habr.com/ru/post/670790/
Добавить комментарий