---
# System prepended metadata

title: '230403 구조체 내부 mutating메서드에서의 escaping closure, 구조체는 왜 thread safe하다고 말하는 걸까?, 구조체 사용은 항상 safe하지는 않는다, UICalendarView'

---

230403 구조체 내부 mutating메서드에서의 escaping closure, 구조체는 왜 thread safe하다고 말하는 걸까?, 구조체 사용은 항상 safe하지는 않는다, UICalendarView
===
학습내용
- 구조체 내부 mutating메서드에서의 escaping closure
- 구조체는 왜 thread safe하다고 말하는 걸까?
- 구조체 내부 mutating메서드에서의 escaping closure, 구조체 사용은 항상 safe하지는 않는다.
- UICalendarView
## 구조체 내부 mutating메서드에서의 escaping closure
프로젝트 진행 중 구조체의 mutating메서드에서 escaping closure에서 self를 참조했을때 컴파일 에러가 발생하는 문제가 발생했다.
<img src="https://i.imgur.com/XQ7fHv0.png" width="500"/><br/>
![](https://i.imgur.com/HKmOUq5.png)

escaping closure는 현재 context를 빠져나간 뒤에 실행되는 메서드이기 때문에 `self`를 참조하고 있어야 한다. 하지만 구조체는 인스턴스를 참조하는 개념이 아니라 복사하는 개념이기 때문에 escaping closure에서 self를 참조하는 것이 값타입의 개념과 충돌하는 것이다. 

반면에 non-escaping 클로저에서 self를 참조하는 것은 클로저가 호출되는 동안만 유효하기 때문에 self를 사용할 수 있다. 이때는 참조하는 것이 아니라 복사하는 것과 같다.

그리고 [self] in을 사용할 수도 있는데 이는 클로저의 생성시점에 `self`를 복사해놓는 것이기 때문에 생성시점의 `self`만 사용하기 때문에 컴파일에러가 보이지 않는다. 하지만 실제 인스턴스의 프로퍼티가 변경되더라도 클로저에서 캡처하고 있는 `self`는 생성시점만 캡처하고 있기 때문에 이 `self`의 프로퍼티는 변하지 않는다.

## 구조체는 왜 thread safe하다고 말하는 걸까?
구조체는 값이 복사되는 형태인 값타입이기 때문에 여러 스레드에서 구조체에 접근할 때 인스턴스를 복사한 후 접근하게 된다. 즉 원본데이터에는 영향을 주지 않기 때문에 여러 스레드에서 접근하더라도 결과가 같기에 thread-safe한 것이다. 
그런데 여러 스레드에서 구조체에 접근한다는 의미는 DispatchQueue를 이용한 escaping closure에서 접근한다는 의미일텐데 escaping closure에서는 값타입을 참조를 하지 못하지 않지 못하고, 참조가 아닌 클로저 캡처리스트를 이용해 `[self] in`으로 값을 복사해서 인스턴스의 프로퍼티를 변경하더라도 원본 인스턴스는 변화되지 않기 때문에 race condition을 논하기에는 거리가 조금 있지않나 생각이 들었다.

참조가 불가능한 형태로 사용한다는 것 자체로 thread-safe하다고 말을 하는걸까? 
이 점이 궁금해서 구조체의 주소를 통해 실제 인스턴스의 값이 변경될 수 있게 만드는 `inout`을 사용해서 다음의 코드를 작성했다.
```swift
import Foundation

struct Point {
    var x = 0
    var y = 0

    mutating func update() {
        x += 1
        y += 1
    }

    func printPoint() {
        print("(\(x), \(y))")
    }
}

func modifyPoint(point: inout Point) {
    point.update()
    point.printPoint()
}

let queue = DispatchQueue(label: "thread-test", attributes: .concurrent)

let point = Point(x: 1, y: 1)

for _ in 0..<10 {
    queue.async {
        modifyPoint(point: &point)
    }
}

sleep(1)
```
위 코드에서 `&point`는 `point`의 주소를 넣는 것이기 때문에 `point`의 값이 `thread-safe`하지 못하게 동시에 증가가 되는 경우가 생긴다. 
이를 아래로 바꿔주면 `thread-safe`한 코드로 만들 수 있게 된다.
```swift
for _ in 0..<10 {
    queue.async {
        var copy = point
        modifyPoint(point: &copy)
    }
}
```
`copy`는 `point`의 값을 복사한 것이기 때문에 원본을 수정하지 않고 copy를 수정하기 때문에 thread-safe하다. 
즉 구조체도 thread-safe하게 사용하려면 lock을 걸어놓는다거나 값을 복사해놓고 사용하는 등(위 코드처럼 `var copy = point`로 하거나 [self in]을 사용하는 등)의 로직이 필요하다고 느꼈다.

## 구조체 내부 mutating메서드에서의 escaping closure, 구조체 사용은 항상 safe하지는 않는다.

### 사람들은 왜 클래스보다 구조체를 사용하도록 말할까?
스위프트 개발자들의 일반적인 조언은 기본적으로 구조체를 사용하라고 하는 것이다. 그리고 클래스는 꼭 사용해야하는 경우에 사용하라고 한다. 왜 그런지 이유를 살펴보고 모든 상황에서 이게 옳은 것인지를 확인해보자.

### 구조체는 값타입이다.
값타입인 구조체는 각각 고유한 복사본이기 때문에 메모리 측면에서 관리하기가 더 쉽다. 반면에 참조타입은 메모리의 일부지점에 대한 참조이다.

### 구조체는 thread-safe하다
구조체는 고유한 복사본이기 때문에 여러 상황에서 한번에 하나의 스레드만 관리할 수 있다. 이는 여러개의 스레드에서 동시에 접근해서 버그를 발생시키는 것을 예방한다.

반대로 참조타입은 거의 모든 스레드에서 접근할 수 있다. 만약 두 개의 스레드에서 동시에 객체에 접근한다면 side-effect가 생길것이다. 이는 이상한 결과를 보여줄것이고 크래시를 만들 수 있다.

### 구조체는 변경가능성을 쉽게 제어한다.

구조체는 var로 선언하지 않으면 인스턴스를 변경시킬 수 없게하는 특별한 규칙이 있다. 나아가 구조체의 함수에서 프로퍼티를 변경시키려면 `mutating` 키워드를 붙여야 한다.

반면에 참조 타입은 mutating키워드 없이 프로퍼티를 변경할 수 있다.

### 사실 구조체의 변경가능성과 thread-safe는 좋은 규칙을 필요로한다.
이론적으로 위의 내용들은 모두 사실이지만 현실이 이를 어떻게 쉽게 구조체를 unsafe하게 방해하는지 살펴보자. 구조체 안에 참조타입을 넣게되면 구조체 내부에서 변경가능성을 제공하게 된다. 

```swift
struct ValueType {
    let name: String
    let referenceType: ReferenceType
}

class ReferenceType {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

let valueType = ValueType(name: "Kenny", referenceType: ReferenceType(name: "Test"))
valueType.referenceType.name = "Tom"
```

위 코드를 통해 구조체는 반드시 thread-safe하지는 않다는 것을 알 수 있다. 만약에 여러개의 스레드에서 구조체 내부의 프로퍼티에 접근한다면 실행할때마다 값이 변경될 수 있는 race condition상황에 놓일 수 있게 되는 것이다. 그렇기에 구조체를 사용하더라도 안전하게 만들기 위해 규율있는 접근법이 필요하다. 

### 언제 값타입을 사용하고 언제 참조타입을 사용하지?
가장 기본적으로 Model data를 구성할 때 struct를 사용하고 행동을 캡슐화할 때 class를 사용한다.

예를들어 e-commerce 앱에서 아이템을 struct로, 아이템을 소유하고 있고 total을 만들어주는 기능을 담는 곳은 class로 정의한다. 

```swift
struct ShoppintItem {
    let itemName: String
    let price: Double
    let aisle: Int
}

class Order {
    var shoppingItems: [ShoppintItem] = []
    
    func totalOrder() -> Double {
        let prices = shoppingItems.map { $0.price }
        let price = prices.reduce(0.0, +)
        return price
    }
}
```

위 코드에서 `ShoppingItem`은 내부 프로퍼티들이 값타입에서 let으로 설정되어있고 값타입만 받고있기 때문에 변경가능성이 존재하지 않는다. 그리고 `Order`는 전역에서 값이 복사된 채로 동작한다면 하나의 앱에서 서로다른 Item들이 shoppingItem이 담겨있을 수 있기 때문에 하나의 쇼핑목록을 공유하는 차원에서 Class로 정의하는 것이 좋다. 

## UICalendarView
날짜 모양의 캘린더를 보여주는 뷰. 사용자가 단일, 다중 날짜를 선택할 수 있도록 한다.

```swift
@MainActor class UICalendasrView: UIView
```

### Overview
캘린더 뷰를 사용하여 모양을 커스텀하여 사용자에게 정보를 갖고있는 날짜를 제공한다. 
사용자는 하나의 날짜, 다중날짜를 선택할 수 있고 선택하지 않을 수도 있다.

캘린더 뷰를 interface에 적용하기 위해 다음을 한다.
- 보여질 캘린더 뷰를 위해 Calendar와 Locale을 설정한다
- 초기에 보여지게 하기 위해 캘린더 뷰에 날짜를 설정한다.
- 원한다면 구체적인 날짜에 모양을 제공하기 위해 delegate를 사용한다
- 날짜 선택을 관리하기 위해 selection메서드와 델리게이트를 설정한다.
- calendar view를 오토레이아웃 설정한다

캘린더는 날짜 선택에만 사용이된다. 만약 날짜와 시간 선택을 다루고싶다면 `UIDatePicker`를 이용한다.

### Configure a calendar view
사용자의 위치, 문화, 언어에 맞게 캘린더를 구성한다. 
기본적으로 캘린더 뷰는 사용자의 현재 `Calendar`와 `Locale`을 선택한다. 만약 사용자가 앱에서 다른 캘린더나 locale을 선택할 수 있다면 그 선택한 것에 맞게 캘린더뷰를 구성해야 한다.

```swift
let calendarView = UICalendarView()
let gregorianCalendar = Calendar(identifier: .gregorian)
calendarView.calendar = gregorianCalendar
calendarView.locale = Locale(identifier: "zh_TW")
calendarView.fontDesign = .rounded
```

그 다음 캘린더 뷰에 보여질 초기값을 설정한다.
```swift
calendarView.visibleDataComponents = DateComponents(calendar: Calendar(identifier: .gregorian), year: 2022, month: 6, day: 6)
```
만약 사용자가 볼 수 있고 선택할 수 있는 날짜를 최신의 날짜로 제한하려면 `availableDateRange`를 사용한다.

```swift
let fromDataComponents = DateComponents(calendar: Calendar(identifier: .gregorian), year: 2022, month: 1, day: 1)
let toDateComponents = DataComponents(calendar: Calendar(identifier: .gregorian), year: 2022, month: 12, day: 31)

guard let fromDate = fromDateComponents.date, let toDate = toDateComponents.date else {
    return
}

let calendarViewDateRange = DateInterval(start: fromDate, end: toDate)
calendarView.availableDateRange = calendarViewDateRange
```

### Display decorations for specific dates
사용자에게 특정 날짜에 decoration을 통해 구체적인 날짜정보를 보여줄 수 있다. color, size, image, custom view를 특정 날짜에 적용함으로써 강조할 수 있다. 캘린더 뷰에 decoration을 보여주기 위해 custom `UICalendarViewDelegate` 객체를 만들고 `calendarView(_:decorationFor:)`를 구현한다. 그리고 커스텀 객체를 calendar view의 delegate로 놓는다. 

```swift
class MyEventDatabase: NSObject, UICalendarViewDelegate {
    // Your database implementation goes here.
    func calendarView(_ calendarView: UICalendarView, decorationFor: DateComponents) -> UICalendarView.Decoration? {
        // Create and return calendar decorations here.
    }
}

let database = MyEventDatabase()
calendarView.delegate = database
```

`UICalendarView.Decoration`의 어떤 타입이 가장 잘맞는지 결정한다
- 기본 decoration은 색과 상대적인 사이즈를 커스텀할 수 있는 색이채워진 원을 보여준다.
- 이미지 decoration은 system symbol, color, 상대적인 사이즈와 함께 커스터마이즈 할 수 있다.
- custom decoration은 커스텀 뷰를 반환하는 클로저와함께 커스터마이즈 할 수 있다.

데이터가 변경되었을 때 캘린더 뷰에게 decoration view를 reload하라고 말해준다.

```swift
database.observeEventChanges { changedDates in
    calendarView.reloadDecorations(for: changedDates, animated: true)
}
```

### Handle date selection

원하는 날짜 유형선택을 결정한다. 그런다음 그 유형에 맞는 `selection` 객체와 `delegate`를 만든다. 그리고 캘린더 뷰의 `selectionBehavior`에 할당한다.

```swift
let dateSelection = UICalendarSelectionSingleDate(delegate: self)
calendarView.selectionBehavior = dateSelection
```
사용자는 캘린더뷰로부터 날짜를 고를 수 있다. 다음과 같이 코드로 선택된날자를 설정할 수도 있다.

`dateSelection.selectedDate(DataComponents(calendar: Calendar(identifier: .gregorian), year: 2022, month: 6, day: 6))`

다음으로 date selection을 처리하기 위해 델리게이트 메서드를 구현한다.
```swift
func dateSelection(_ selection: UICalendarSectionSingleDate, canSelectDate dateComponents: DateComponents?) -> Bool {
    return dateComponents != nil
}
```

### 참고문서
- [Using Structs over Classes in Swift Isn’t as Safe as You Think It is](https://medium.com/devgauge/using-structs-over-classes-in-swift-isnt-as-safe-as-you-think-it-is-a59794d10c31)
- [클로저 캡쳐](https://velog.io/@kimdo2297/%ED%81%B4%EB%A1%9C%EC%A0%B8-%EC%BA%A1%EC%B3%90%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-about-closure-capture)
- [Apple Docs - UICalendarView](https://developer.apple.com/documentation/uikit/uicalendarview)