-
값 유형을 사용할 때 모든 mutations은 (기본적으로) 작업 중인 값의 로컬 복사본에만 수행되고 실제로 mutations을 수행하는 API는 mutations으로. 명확하게 표시되어야 합니다.
-
Mutating으로 표시된 함수는 포함하는 값 내의 모든 속성을 변경할 수 있습니다. Swift의 structured mutations 개념은 참조 유형이 아니라 값 유형에만 적용되기 때문에 "값"이라는 단어가 여기서 매우 중요합니다.
-
Mutating을 켜야 하는 경우 값 유형의 변경이 어디에서 오는지 더 쉽게. 찾아낼 수 있고 값에 대한 우발적인 변경으로 인해 발생하는 오류를 줄이는 데 도움이 됩니다. 또한 우리는 어떤 메소드가 변경을 할 수 있는지 정확히 알고 있고 어떤 조건/상황에서 돌연변이 메소드가 실행될지 알기 때문에 값에 대해 추론하고 예측하는 것이 여전히 상대적으로 간단합니다.
-
단, 값 내에서 참조유형을 사용하면 쓰레드 세이프를 보장하지 못함
속성을 수정하는 것 외에도 컨텍스트를 변경하면 self에 완전히 새로운 값을 할당할 수 있습니다. 이는 enum에 변경 메서드를 추가할 때 정말 유용할 수 있습니다. 예를 들어, 여기에서 하나의 작업을 다른 작업에 쉽게 추가할 수 있도록 API를 만들고 있습니다.
enum Operation {
case add(Item)
case remove(Item)
case update(Item)
case group([Operation])
}
extension Operation {
mutating func append(_ operation: Operation) {
self = .group([self, operation])
}
}
기본 속성 세트로 다시 재설정하려는 경우
struct Canvas {
var backgroundColor: Color?
var foregroundColor: Color?
var shapes = [Shape]()
var images = [Image]()
mutating func reset() {
self = Canvas()
}
}
재설정하려는 값의 유형을 몰라도 아래와 같은 코드가 가능하다
protocol Resettable {
init()
mutating func reset()
}
extension Resettable {
mutating func reset() {
self = Self()
}
}
struct Canvas: Resettable {
var backgroundColor: Color?
var foregroundColor: Color?
var shapes = [Shape]()
var images = [Image]()
}
이니셜라이저는 유형의 속성에 초기 값을 할당하는 것 외에도 작업을 수행하기 위해 변형 메서드를 호출할 수도 있습니다(self가 사전에 완전히 초기화된 경우).
struct ProductGroup {
var name: String
private(set) var products = [Product]()
private(set) var totalPrice = 0
init(name: String, products: [Product]) {
self.name = name // 초기화 하고 나서
products.forEach { add($0) }
}
mutating func add(_ product: Product) {
products.append(product)
totalPrice += product.price
}
}
mutating
을 선언한 메서드는 메서드 내에서 property를 변경할 수 있고, 메서드가 종료될 때 변경한 모든 내용을 원래struct
에 다시 기록한다.swift 벨류 타입의 핵심은 불변성이라서 그걸 통채로 갈아 엎습니다. 그렇게 되면 다중 쓰레드에서 세이프하게 작동할 수 있습니다.(?????)
- 표준 라이브러리의 모든 가변 크기 collection과 마찬가지로 배열은 copy-on-write(COW) 최적화를 사용한다.
- Array의 여러 복사본은 복사본 중 하나를 수정할 때까지 동일한 저장소를 공유한다.
- 수정되는 Array는 원본의 복사본으로 교체한 다음 제자리에서 수정된다.
- 복사량을 줄이기 위해 최적화가 적용되는 경우가 있다.
- 즉, Array가 다른 복사본과 저장소를 공유하는 경우 해당 Array의 첫번째
mutating
으로 Array 복사가 발생한다.
var numbers = [1, 2, 3, 4, 5]
var firstCopy = numbers
var secondCopy = numbers
// The storage for 'numbers' is copied here
numbers[0] = 100
numbers[1] = 200
numbers[2] = 300
// 'numbers' is [100, 200, 300, 4, 5]
// 'firstCopy' and 'secondCopy' are [1, 2, 3, 4, 5]
위의 경우에서도 numbers, firstCopy, secondCopy가 처음에는 같은 저장소를 공유하고 있다. numbers의 값이 바뀔 때, 저장소가 복사되고 numbers는 복사된 저장소를 가지고 있게 된다.
위에서 살펴본 것처럼 Swift는 값이 변경될 때 복사를 하는 COW로 최적화를 진행한다.
struct
의 property 값이 변경될 때에는 Swift가 직관적으로 알 수 있다.
(이는 let
, var
키워드를 통해 컴파일러가 이미 알고 있다.)
mutating
키워드 없이 메서드가 property를 변경하게 되면 언제 실제로 복사를 해야하는지 알 수가 없다.
따라서, mutating
키워드는 해당 메서드가 호출된다면 실제 복사를 해야한다고 알려주는 역할이다.
let queue = DispatchQueue.global(qos: .default)
class Bird {}
var single = Bird()
queue.async {
while true { single = Bird() }
}
while true { single = Bird() }
https://velog.io/@wonhee010/mutating
https://www.swiftbysundell.com/articles/mutating-and-nonmutating-swift-contexts/
https://medium.com/commencis/stop-using-structs-e1be9a86376f