---
# System prepended metadata

title: unsafe swift (wwdc2020)

---

# unsafe swift (wwdc2020)

![](https://hackmd.io/_uploads/r1kyRykwn.png)

표준 라이브러리에 있는 대부분의 작업들은 실행하기 전에 코드를 완벽히 검증하기때문에 위와같이 오류가 
있을 경우 fatal error를 통해 오류를 알려준다. 

위 예시도 런타임 오류가 일어난 예시중 하나이다.

> 강제 언래핑 연산자는 가능한 모든 입력에 대한 동작을 완전히 설명할 수 있기 때문에 "안전"하다고 말한다.

> 그에 반해, unsafe 연산자는 **특정 input**에 대해 **정의되지 않은 행동**을 가지는 것을 의미한다.

<br>
<br>

unsafe의 예시중 하나인 **unsafelyUnwrapped**를 보자. 

![](https://hackmd.io/_uploads/HyLAAkkv3.png)

일반적인 force-unwrapped 연산자와 같이 기본 값이 nil이 아니어야 한다.
그러나 다른 점은, 컴파일 하면 기본값이 nil인지 아닌지 확인하지 않는다. 
**즉 nil이 아닌 값을 호출하고 직접 읽어낸다는 것을 신뢰한다.**

![](https://hackmd.io/_uploads/BJMSggJP3.png)

실수로 nil을 호출하면 임의의 상황에 따라 즉시 충돌을 일으키거나 일부 쓰레기 값을 반환할 수 있다.


> 요점은 이 속성을 사용하면 해당 요구 사항을 충족할 전적인 책임을 진다는 것이다.
실수로 이를 위반하면 결과를 예측할 수 없으며 문제를 디버깅하는 것이 매우 어려울 수 있다.

>❗️따라서 사용할 때 각별한 주의가 필요하며 사용 조건을 완전히 이해해야 한다.

### unsafe API를 사용하는 사례

- C 또는 Objective-C와의 상호 호환을 위해
- 런타임 성능 향상을 위해
    - unsafelyUnwrapped 프로퍼티가 여기에 속함 (nil에 대한 불필요한 검사를 줄일 수 있기 때문)
- 작은 성능 향상조차 중요한 작업을 할 때는 unsafe를 사용하면 좋다는거 같다.

![](https://hackmd.io/_uploads/HknQfx1P3.png)

- 안전한 API의 목표는 충돌을 방지하는 것이 아니다. 
- 실제로는 정반대이다. 이상한 인풋이 입력되면 안전한 API는 치명적인 런타임 오류를 발생시켜 실행을 중지한다.
- 충돌을 명확하게 발생시키고 개발자가 쉽게 상황을 이해하고 수정할 수 있게 하는 것이다.
- 이 기능을 하지 못하는 모든 Construct들이 Unsafe로 표시되어 있다.

#### unsafePointer

- 일반적으로 Swift에서는 수동으로 메모리를 관리할 필요가 없다.
    - 그렇게 해야 할 때 unsafePointer는 메모리를 효과적으로 관리하는 데 필요한 모든 하위 수준 작업을 제공한다. (임의의 메모리 주소에 접근할 수 있는 기능 등등)
- 포인터는 단순히 메모리 어딘가에 있는 위치의 주소를 나타낸다.
    - 그들은 강력한 작업을 제공하지만 사용자가 올바르게 사용할 것이라고 믿어야 하므로 근본적으로 안전하지 않다.
    - 잘못 사용하면 큰일난다. (어쩌구저쩌구.. 생략)
    - xcode는 이런 메모리 사용 문제를 파악하는데 도움을 주는 Address Sanitizer라는 디버깅 도구를 제공한다.

> ❓ 포인터가 그렇게 위험하다면 왜 포인터를 사용하는거냐? 
> 
> 가장 큰 이유는 C나 Objective-C와 같은 안전하지 않은 언어와의 호환 때문.
이러한 언어에서 함수는 종종 포인터 인수를 사용하므로 Swift에서 호출할 수 있으려면 Swift 값에 대한 포인터를 생성하는 방법을 알아야 함

Swift와 C API 사이의 매핑방법은 

![](https://hackmd.io/_uploads/SkDwKeywn.png)

![](https://hackmd.io/_uploads/B125YgyP3.png)

- UnsafeMutablePointer에서 allocate 메서드를 사용하여 동적 버퍼를 만들 수 있다. 
- 그런 다음 initialize를 통해 버퍼의 요소를 특정 값으로 설정할 수 있다.
- 이제 모든 준비를 마치면 C 함수를 호출하여 초기화된 버퍼에 대한 포인터를 전달할 수 있게 된다.

![](https://hackmd.io/_uploads/HkLmcxkvh.png)

- 함수가 반환되면 버퍼를 초기화하고 deinitialize해줘야 한다.
    - 나중에 다른 작업을 위해 메모리 위치를 재사용할 수 있도록 하는 것이다.

>이 모든 과정이 **unsafe**하게 이루어 지고 있다.
포인터의 Lifetime은 따로 관리되지 않기 때문에 적절한 순간에 deinitailize하고 deallocate해주어야 한다. 또한 initialize하는 과정에서 사용하는 메모리주소가 우리가 선언한 버퍼 범위 이내인지 확인하지 않는다.

=> 버퍼를 생성하는 과정을 개선할 수 있도록 Swift에서 제공하는 타입이 있다.

```swift
UnsafeBufferPointer<Element>
UnsafeMutableBufferPointer<Element>

UnsafeRawBufferPointer
UnsafeMutableRawBufferPointer
```

Swift의 연속적인 콜렉션들은 임시로 버퍼에 직접 접근할 수 있는 메소드를 제공한다.

```swift
Sequence.withContiguousStorageIfAvailable(_:)
MutableCollection.withContiguousMutableStorageIfAvailable(_:)
 
String.withCString(_:)
String.withUTF8(_:)
 
Array.withUnsafeBytes(_:)
Array.withUnsafeBufferPointer(_:)
Array.withUnsafeMutableBytes(_:)
Array.withUnsafeMutableBufferPointer(_:)
```



---


```swift
// C:
void process_integers(const int *start, size_t count);
 
// Swift:
let values: [CInt] = [0, 2, 4, 6]
 
values.withUnsafeBufferPointer { buffer in
    print_integers(buffer.baseAddress!, buffer.count)
}
```

- 입력 데이터를 배열 값에 저장한 다음 withUnsafeBufferPointer 메서드를 사용하여 일시적으로 array의 기본 저장소에 직접 액세스할 수 있다.
- 이 함수에 전달하는 클로저 내에서 시작 주소와 카운트 값을 추출하고 호출하려는 C 함수에 직접 전달할 수 있다.

- 근데 이렇게 C 함수에 대한 포인터를 전달해야 하는 경우는 너무 자주 발생하여 Swift는 이에 대한 특수 구문을 제공한다.


```swift
let values: [CInt] = [0, 2, 4, 6]

print_integers(values, values.count)
```

- 이렇게만 써도, unsafe를 사용한 것과 같은 코드를 만들어낸다.


## 요약 

![](https://hackmd.io/_uploads/BJfa0gkv3.png)

- unsafe API을 사용할 땐 API의 요구사항을 정확히 파악하고 사용하자.
- unsafe API 사용을 가급적 하지마라.
- 메모리에 접근할 때 단순한 포인터 값을 사용하지 말고 UnsafeBufferPointer를 사용하자.
- Xcode의 Address Sanitizer를 사용하면 Unsafe API를 디버깅하기 좋다.