2024. 7. 13. 16:28ㆍSwift, iOS Foundation
WWDC 영상을 보고 요약글을 블로그에 써보는 것은 처음인 것 같다.
이번 글은 "What's new in Swift"라는 주제로 Swift 6의 업데이트를 기다리고 있는 입장에서, 새로운 기능으로 어떤 점이 있는지를 중점적으로 살펴보고자 정리를 해본다. (Swift 6 기능 소개는 위 영상 17분 정도부터 시작된다.)
영상 앞 부분을 간단히 요약하자면, Swift 언어의 역사부터 시작해서 조직 체계의 변화, 새로운 커뮤니티, 그리고 새로운 테스팅 라이브러리로 소개된 Swift Testing (이 부분도 나중에 다뤄보고자 한다!), Foundation 프레임워크 변화, Building 시스템의 개선 등의 내용이 담겨 있었다.
흥미로웠던 것은 Swift가 Apple 생태계 안에서만 갇혀서 사용되고 있다는 기존 나의 인식과 달리,
생각보다 다른 언어와의 호환이나 크로스 플랫폼 언어로의 성장 등 다양한 플랫폼에서 활용되는 것을 목표로 하고 있는 "Swift Everywhere"의 가치 아래, 발전을 해나가고 있다는 점이었다.
*이 부분에서 Swift가 어떤 플랫폼에서 어떤 방식의 지원을 새롭게 추가하게 되었는지가 궁금하다면, 영상 초반부를 집중해서 보면 되겠다.
1️⃣ Noncopyable types (~Copyable) : 복사할 수 없는 타입
🤔 복사할 수 없는 타입 (~Copyable)이 왜 필요한데?
값 타입(Value Type)이든, 참조 타입(Reference Type)이든 그동안 Swift에서는 인스턴스 복사를 하는 데 있어 아무런 제약이 없었다.
그러나 고유한 시스템의 자원(ex. 파일)이나 데이터의 무결성(데이터의 전송, 저장 등 모든 과정에서 변경되지 않고 완전성, 정확성, 일관성이 보장되어야 한다는 특성)이 보장되어야 하는 경우에는,
Swift의 자유로운 복사 제약이, 동일한 파일을 여러 곳에서 접근할 때 발생할 수 있는 문제나 리소스 누수와 같은 문제의 원인이 되었다.
Noncopyable 타입은 특정 타입의 인스턴스를 복사하지 못하도록 만드는 기능이다.
사용은 ~Copyable 키워드를 사용하고자 하는 타입 이름 옆에 붙이면 된다.
아래를 살펴보게 되면, NonCopyableStruct 구조체는 Noncopyable 타입으로 선언되어 있기 때문에 nonCopyable 객체를 복사해서 copyStruct에 사용하고자 하는 경우 에러를 발생시키는 화면에 해당한다.
단, 세션에서는 Noncopyable 타입의 사용 시 주의점에 대해서도 설명하고 있다.
아래 코드에서는 파일을 초기화(init)할 때 descriptor라는 파일의 구분자/실명자를 받게 되는데, 이 부분이 직관적이지 않고 안전하지도 않다고 표현한다. (Unintuitive and Unsafe.)
그 이유로 만약 파일을 열고 초기화 하는 코드 사이에 프로그램을 종료하는 코드(exit 또는 throw와 같이)를 삽입하게 된다면, 해제자(deinit)이 실행될 수가 없어 메모리 누수가 발생할 수 있다는 것이다.
이에 대한 해결책으로 생성자에서 파일을 열고 값을 받는 모든 과정을 담당하도록 수정하는 것을 보여준다.
Swift 5.10까지는 복사할 수 없는 타입을 구체적인 타입 (Concrete Type)에 대한 지원으로 제한했었지만,
Swift 6부터는 모든 Generic Context와 Standard Library (Optional, Result, Unsafe Pointers)에 대한 지원을 도입한다는 것이 핵심 변경사항이다.
더 자세한 사항을 WWDC24의 다른 세션인 Comsume noncopyable types in Swift에서 살펴볼 수 있다는 것과 함께 마무리된다.
2️⃣ Embedded Swift : 저수준 환경에서도 돌아가는 Swift
🤔 Embeded Swift가 왜 필요한데?
"적은 메모리, 적은 저장공간, 런타임 기능" 등의 제약이 있는 저수준 시스템(Low-Level System)에서는 그동안 C와 C++가 Low footprint(작은 저장공간) 아래 적합한 선택으로 여겨지던 언어였다.
하지만, 이제는 Swift 언어를 사용해서도 저수준 시스템(Low-Level)에서 사용할 수 있게 하겠다는 것이 핵심 목적이다.
Embeded Swift는 Javascript-Typescript 관계처럼 Swift의 서브셋에 해당한다.
Embeded Swift에서는 아주 작은 독립 실행형 바이너리를 생성 (produce extremely small standardlone binaries) 할 수 있게 되었다고 한다. (이게 무슨 말이지..?)
지금 당장 Embeded Swift라는 것을 사용할 일이 없어서 조금 와닿지 않는 감이 있지만, 결국 핵심은 아래와 같다.
- Mirror (Swift reflection)이나 any 타입과 같이 런타임 지원이 필요한 기능을 끈다.
- Full Generic Specialization과 Static Linking을 통해 적합한 바이너리를 생성한다. (Special compiler techniques)
- Embeded Swift는 완전한 Swift와 매우 가깝다. (Embeded Swift is close to full Swift)
역시 마지막에는 더 자세한 내용을 담은 Go small with Embeded Swift 세션을 소개하며 마무리된다.
3️⃣ C++ interoperability : Swift와 C++ 간의 상호 운용성
작년인 2023년에는 Swift와 C++ 간의 양방향 운용성을 발표했었다. (WWDC23-Mix Swift and C++ 세션 참조)
올해 Swift 6에서는 여기서 더욱 발전되어 가상 메서드 (Virtual methods), 기본 인수 (Default arguments), C++ 표준 라이브러리 유형 등을 Swift로 직접 가져올 수 있게 되었다고 한다.
여기서 말하는 interoperability(상호 운용성)는 단순히 Swift에서 C++를 사용할 수 있다 정도의 수준이 아니라, 유사한 언어 문법으로 자동 매핑되거나 같은 의미를 갖는 것으로 변환되는 것을 의미한다고 말하고 있다.
또한 애플 왈, C++에서 안전성 보장이 부족해 보안 공격에 노출될 수 있다는 점을
C++ 프로젝트 내에 보안이나 생산성이 뛰어난 Swift를 점진적으로 도입하며 보안과 생산성을 개선할 수 있게 된다는 어쩌구 저쩌구..이야기였다.
자세하게 C++를 Swift와 함께 어떻게 사용할 수 있는지는 아래 Swift 문서를 참조하는게 더 좋겠다. 아무튼 신기하네!
4️⃣ Typed throws : 어떤 에러 타입을 던지는지 명확하게 표현하자!
🤔 Typed throws가 왜 필요한데?
기존 Swift에서 Error를 처리할 때 throws라는 키워드를 사용해 해당 함수가 에러를 던질 수 있는(throw) 함수라는 것을 표현했다.
그동안 throw된 error를 catch하는 핸들러에서는 특정 타입의 에러가 아닌 any Error 타입으로 넘어오기 때문에,
특정 타입 에러를 처리하기 위해서는 "as 특정타입 에러" 구문과 같이 타입을 변환하거나, 동적으로 타입을 검사해주는 로직이 필요했다.
이번에는 위의 내용이 어떤 문제인지부터 코드로 이해해보도록 하겠다.
NetworkError라는 Error 타입을 지정하고 fetchData에서 NetworkError.notFound라는 에러 케이스를 던지고 있는데,
핵심은 에러를 잡는(catch) 부분에서 에러 처리를 해주기 위해서 networkError as NetworkError라는 변환을 해주고 있는 부분이다.
이렇게 사용해도 큰 이상은 없겠지만,
세션에서는 이렇게 런타임 중에 타입을 할당할 수 없는 매우 제한된 시스템(highly constrained systems without runtime allocation capabilities)에서는 어찌할 방도가 없다는 것을 문제로 들고 있다.
enum NetworkError: Error {
case badRequest
case unAuthorized
case notFound
}
func fetchData() throws -> Data {
// 어쩌구 저쩌구 네트워크 요청 코드
throw NetworkError.notFound
}
do {
let data = try fetchData()
}
catch let error as NetworkError { // NetworkError로의 형변환 필요!
switch error {
case .badRequest:
print("400: BAD REQUEST EXCEPTION")
case .unAuthorized:
print("401: UNAUTHORIZED EXCEPTION")
case .notFound:
print("404: NOT FOUND EXCEPTION")
}
}
새로 나온 Type throws는 함수가 던지는 에러 타입을 명확하게 지정하여 에러 처리를 명확하고 안전하게 만드는 방법이다.
사용은 에러를 던지는 함수의 throws 키워드 뒤에 "(던지고자 하는 에러 타입)"을 지정하면 된다.
위의 코드를 수정하면 throws(NetworkError)라고 명시적으로 던지고자 하는 에러 타입을 지정하고, catch 부분에서는 타입 체크하는 부분 없이 바로 NetworkError 타입의 에러를 처리해주는 방식으로 만들 수 있겠다.
func fetchData() throws(NetworkError) -> Data {
// 어쩌구 저쩌구 네트워크 요청 코드
throw NetworkError.notFound
}
do {
let data = try fetchData()
}
catch {
// NetworkError 타입의 error가 바로 넘어온다.
}
그러니까 기존에 throws 방식의 에러 전파(Error propagate)는 Typed throws 방식에서 (any Error) 타입을 지정해서 던지는 것과 같은 의미였으며,
아무런 에러를 보내지 않는 함수는 Never를 throws하는 Typed throws 방식과 같은 의미로 볼 수 있다는 것.
5️⃣ Data-race safety : Swift Concurrency의 핵심 목표
🤔 Data-race safety가 왜 필요한데?
데이터 레이스(Data-race)란 멀티 스레드 환경에서 동시 프로그래밍(writing concurrent programs)을 할 때 발생할 수 있는 흔한 오류이다. -> Swift actor가 도입되는 배경에서 나오는 개념이라 나중에 블로그에서 자세하게 다룰 것이다!
여러 스레드가 데이터를 공유(multiple threads are sharing data)하는 환경에서 그 중 한 곳이 데이터를 변형하고자 할 때, 예상치 못한 런타임 동작(unexpexted runtime behavior)이나 프로그램 충돌(program crashes)과 같은 문제를 일으킬 수 있다.
Swift 5.5에서 Async/await, Task, Actor 등의 등장으로 시작되었던 Swift Concurrency의 개념은
동시성 프로그래밍에서 발생하는 Data-race 문제를 해결하기 위한 방법의 고민으로 계속해서 이어져왔다고 볼 수 있다.
*가변 상태를 보호하기 위한 Actor, 안전한 데이터 공유를 위한 Sendable 프로토콜 등이 이에 해당하는 셈!
Swift 6부터는 default로 Data-race safety 환경을 보장한다.
세션에서는 앱에서 발생하는 모든 data-race 문제를 컴파일 타임 에러로 잡으면서 동시에 앱에 대한 보안 성능을 개선한다고 설명한다. (Turning all the data-race issues in your app into compile time errors.)
또한 이 외에도 Swift 6로의 마이그레이션(incremental migration)을 위한 업데이트나 Low-level 수준의 동기화 모듈(Atomics, Mutex) 등을 함께 지원해 Data-race 문제로부터 안전한 도구들을 모두 구축했다고 표현한다.
더 자세한 내용은 아래 문서를 참조할 수 있다.
'Swift, iOS Foundation' 카테고리의 다른 글
[Swift] KVO (Key-Value Observing) 완전 정복하기 (feat. WKWebView progressBar) (0) | 2024.10.01 |
---|---|
[Swift] 자네 열거형(enum)을 CaseIterable로 사용해본 적이 있는가? (0) | 2024.08.05 |
[Swift] [weak self] 이젠 제대로 알고 사용하자! (feat. ARC 2탄) (0) | 2024.07.08 |
[Swift] ARC (Automatic Reference Counting) 완전 정복하기 (0) | 2024.07.06 |
[Swift] 제네릭 (Generic) 완전 정복하기 (0) | 2024.07.03 |