[Swift 문법] 옵셔널 (Optionals)

Yellowtoast·2023년 12월 24일
0

Swift

목록 보기
9/11
post-thumbnail

해당 글은 iOS 스터디를 위한 기본 문법을 정리한 글 입니다.
Advanced Swift (by Chris Eidhof) 책의 내용을 참고하여 작성하였습니다.

센티널 값(Sentinel Values)

일반적으로 센티널 값은 "존재가 종료 조건으로 사용되는" 특별한 값입니다.

프로그래밍에서는 일반적으로 값을 반환하거나 반환하지 않는 연산을 사용합니다.
아래 코드는, 특정 파일의 끝에 도달할 때 까지 읽는 코드입니다. 이와 같은 코드에서는 파일의 끝에 도달했을 때 값을 반환하지 않는 것이 필요할 수 있습니다.

int ch;
while ((ch = getchar()) != EOF) {
printf("Read character %c\n", ch); 
}
printf("Reached end-of-file\n");

파일에 아직 읽어야 할 문자가 있는 경우에는, getChat()함수는 남은 문자를 반환합니다. 하지만 파일의 끝에 도달하면 getchar는 -1을 반환합니다. 이 -1은 센티널 값이 "파일에 끝에 도달했다"는 의미라고 볼 수 있겠죠.

반면에, 아래의 C++ 코드에서처럼 값을 반환하지 않으면 "찾을 수 없음"을 의미할 수도 있습니다:

auto vec = {1, 2, 3};
auto iterator = std::find(vec.begin(), vec.end(), someValue); if (iterator != vec.end()) {
std::cout << "vec contains " << *iterator << std::endl; }

위의 vec.end()는 컨테이너의 "끝에서 하나 지나간" 이터레이터로, 컨테이너의 끝을 확인할 수는 있지만 실제로 값에 액세스하는 데 사용해서는 안 되는 특수 이터레이터입니다(Swift의 컬렉션의 endIndex와 유사). end 함수는 이를 사용하여 컨테이너에 해당 값이 없음을 나타냅니다.

또는 함수를 처리하는 동안 문제가 발생하여 값을 반환할 수 없을 수도 있습니다. 보통은 널포인트가 이와 같은 예인데요, 아래의 코드는 NullPointerException를 발생시킬 수도 있습니다.

int i = Integer.getInteger("123")

Integer.getInteger가 문자열을 정수로 파싱하지 않고 "123"이라는 시스템 속성의 정수 값을 가져오는 경우가 있습니다. 이 속성은 존재하지 않을 수 있으며, 이 경우 getInteger는 null을 반환합니다. 그런 다음 null이 정수로 변환되면 Java는 예외를 던집니다.

위의 모든 예제에서 함수는 실제 값을 반환하지 않았음을 나타내기 위해 특별한 "Magic Number"를 반환합니다. 이와 같은 마법 값을 "센티널 값"이라고 합니다.

하지만 "Magic Number"를 반환하는데에는 문제가 있습니다. 이는 반환된 결과가 실제 값처럼 보이고 느껴진다는 것입니다.

또한 이로 인해다른 프로그래머가 오인하여 코드를 만들거나, 잘못된 값으로 프로그램이 진행되도록 만드는 사이드 이팩트를 초래할 수 있다는 것입니다.

Java와 달리 Objective-C는 nil로 원하는 결과값이 아님을 표현할 수 있습니다. nil로 반환되는 값이 항상 0과 동등한 값이라면, Exception을 발생시키지는 않기에 "안전"합니다. 하지만, 동시에 더 큰 문제를 야기할 수 있습니다.


NSString *someString = ...
if ([someString rangeOfString:@"Swift"].location != NSNotFound) {
NSLog(@"Someone mentioned Swift!"); }

위의 코드에서, 일부 String이 nil이면 rangeOfString: 함수에서는 0이 된 NSRange를 반환합니다.

따라서 그 .location은 0이 되고, 결국 이는 SNotFound(NSIntegerMax로 정의됨)과 같지 않기 때문에 if 내부는 실행되어서는 안되는 상황에서 실행되게 됩니다.

센티널 값의 또다른 문제점

센티널 값의 또 다른 문제점은 올바르게 사용하려면 사전 지식이 필요하다는 것입니다. C++ 종료 반복자나 Objective-C의 오류 처리 규칙처럼 커뮤니티에서 널리 통용되는 관용구가 있는 경우도 있습니다. 이러한 규칙이 존재하지 않거나 잘 모르는 경우에는 문서를 참조해야 합니다. 또한 함수가 실패할 수 없음을 나타낼 수 있는 방법이 없습니다.

Enum으로 센티널 값을 대체하기

유능한 프로그래머는 "Magic Number"이 나쁘다는 것을 알고 있습니다. 대부분의 언어는 열거형 유형을 지원하는데, 이는 유형에 대해 가능한 집합만을 표시하기에 안전합니다.

Swift는 "연관 값"이라는 개념을 사용하여 열거형을 한 단계 더 발전시켰습니다. 이는 열거형 값과 연관된 다른 값을 가질 수 있는 열거형 값입니다. 열거형 챕터에서 열거형에 대해 자세히 살펴보겠습니다. 지금은 Optional이 열거형으로 정의되어 있다는 것만 알아두면 충분합니다:

enum Optional<Wrapped> { 
	case none
	case some(Wrapped)
}

스위프트에서 열거형의 연관 값을 찾기 위해서는 "패턴 매칭"을 사용해야 합니다. (예: 스위치 또는 if 대소문자 let 문).

이 값을 찾기 위해서는 반드시 누락되었을 경우의 상태에 대한 코드를 작성해야 하므로, 코드의 표현력이 향상됩니다.

또한 센티널 값과 달리 명시적으로 확인하거나 압축을 풀지 않고서는 값을 실수로 사용할 수 없습니다.

아래의 코드를 보면, Swift가 어떻게 이를 잘 활용하고 있는지 알 수 있습니다. Swift는 firstIndex(of:)함수의 결과값으로 센티널 값을 반환하는 대신 Optional<Index>를 반환합니다:

extension Collection where Element: Equatable {
	func firstIndex(of element: Element) -> Optional<Index> {
		var idx = startIndex 
  		while idx != endIndex {
			if self[idx] == element { 
  				return .some(idx)
			}
			formIndex(after: &idx) 
  		}
 		// Not found, return .none.
		return .none 
  	}
}

이를 통해 값이 유효한지 확인하기 전에 실수로 사용하는 문제를 해결할 수 있습니다.

var array = ["one", "two", "three"]
let idx = array.firstIndex(of: "four") // returns Optional<Int>.
// Compile-time error: remove(at:) takes an Int, not an Optional<Int>. 
array.remove(at: idx)

위의 코드에서는 firstIndex 함수를 사용하며, 이는 Optional<Int>를 반환합니다. 이는 Optional 형태이기 때문에, 바로 Int와 같이 사용할 수 없으며 Int를 얻기 위해서는'언래핑'해야 합니다. 또한 이는 런타임이 아닌 컴파일 타입에 오류를 알려주기 때문에 더더욱 안전합니다.

var array = ["one", "two", "three"] 
switch array.firstIndex(of: "four") { 
case .some(let idx):
	array.remove(at: idx)
case .none:
	break // Do nothing.
}

위의 switch 문은 값을 언패킹 하는 코드 + 각 옵션에 대한 열거 구문을 전부 작성해야 합니다. 하지만 이런 코드를 아래와 같이 간결히 작성할 수도 있습니다.


switch array.firstIndex(of: "four") {
case let idx?: // Equivalent to .some(let idx)
	array.remove(at: idx) 
case nil:
	break // Do nothing. }

?를 사용하여 옵션 값과 일치시킴과 동시에 선언하고, nil 사용하여 일치하지 않는 값에 대한 처리까지 할 수 있습니다.

profile
Flutter App Developer

0개의 댓글