# 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를 디버깅하기 좋다.