You are currently viewing [Swift] Optional binding – Part 2
swift

[Swift] Optional binding – Part 2

자, 그럼 이제 옵셔널 바인딩(optional binding)을 사용해서 여기 있는 값을 언래핑(unwrapping)해 보겠습니다. 이것이 바로 옵셔널을 안전하게 다루는 방법입니다.

기본 개념은 optionalAge에 값이 있는지 확인하는 것입니다. 만약 값이 있다는 것을 발견하면, 그 값을 옵셔널이 아닌 변수나 상수에 **바인딩(binding)**하게 됩니다. 이것이 바로 바인딩 과정입니다. 즉, 값이 존재한다면 값을 언래핑해서 바인딩하는 두 가지 작업을 동시에 수행합니다. 이렇게 진행해 보겠습니다.

Swift
var optionalAge: Int?

if let unwrappedAge = optionalAge {
    print("언래핑된 값: \\(unwrappedAge)")
}

자, 이제 값을 언래핑하고 바인딩했습니다. 이 조건문은 ‘만약 값을 찾으면, 이 상수(unwrappedAge)에 바인딩해 주세요’라는 의미로 생각하시면 됩니다. 지금 이 경우에는 optionalAge에 값이 없으므로, 이 옵셔널 바인딩은 실패하게 됩니다. 하지만 만약 제가 여기에 값을 할당하면,

Swift
optionalAge = 5

if let 바인딩은 optionalAge에서 값 5를 발견하고, 그 값을 추출해서 unwrappedAge라는 상수에 바인딩하게 됩니다.

그리고 이 상수는 해당 스코프(scope) 안에서만 사용할 수 있습니다. 스코프에 대해서는 이전 영상에서 잠시 다뤘었죠. 즉, 이 중괄호 {} 사이에서만 사용할 수 있고, 밖에서는 사용할 수 없다는 의미입니다. 그래서 이렇게 unwrappedAge를 출력할 수 있습니다. 만약 값을 얻지 못하면, 그냥 다음 코드로 계속 진행됩니다.

자, 이번에는 출력문이 확실히 실행될 텐데요, 조금 흥미로운 것을 보여드리겠습니다. 큰따옴표를 넣고, “The value found is”라고 쓴 뒤에 이렇게 작성해 보겠습니다.

Swift
if let unwrappedAge = optionalAge {
    print("The value found is \\(unwrappedAge)")
}

아직 **문자열 보간법(string interpolation)**에 대해 배우지는 않았지만, 지금 보시는 것이 바로 그것입니다. 문자열 보간법은 기본적으로 문자열과 문자열이 아닌 값을 섞어서 사용할 수 있게 해줍니다. 여기서는 ‘The value found is 5’와 같이 출력하고 싶습니다. 숫자 5는 문자열로 해석될 수 있는데, 이렇게 감싸주는 것을 ‘보간한다(interpolating)‘고 합니다. 즉, 문자열 보간법을 사용해서 숫자 5를 그에 상응하는 문자열로 표현하는 것입니다. 이 부분은 나중에 더 자세히 다룰 것이니, 지금은 바인딩된 값을 확인하는 방법 정도로만 이해하시면 됩니다. 물론 이 경우에는 어떤 값이 출력되는 것을 보게 될 겁니다.

프로그램을 실행하면, ‘The value found is five’ 라는 결과가 나오는 것을 볼 수 있습니다. 완벽하네요. 이제 이 할당 부분을 주석 처리하면, 이번에는 아무것도 출력되지 않을 겁니다.

대조를 위해, else라는 것을 추가해 보겠습니다. 기본적으로 ‘만약 optionalAge에 값이 존재하면, 그 값을 바인딩하고 이 스코프로 진입해라. 하지만 nil이라서 아무 값도 찾지 못하면, else 구문으로 가라’는 의미입니다. else는 값이 발견되지 않았을 때의 대안 또는 기본 동작이라고 생각하시면 됩니다. 이 부분은 나중에 강좌에서 if-else 구문을 배울 때 더 자세히 다룰 겁니다. 여기에 no value was found 라는 출력문을 넣을 수 있고, 이번에는 바로 이 문장이 출력될 겁니다.

Swift
var optionalAge: Int?

if let unwrappedAge = optionalAge {
    print("The value found is \\(unwrappedAge)")
} else {
    print("no value was found")
}

프로그램을 실행해 보면, ‘no value was found’가 출력되고, 값을 할당하기 전까지는 계속 그럴 겁니다. 물론 값을 할당하면, ‘The value found is five’가 출력되죠.

모든 프로그램은 위에서 아래로 실행된다는 점도 중요합니다. 물론 우리가 이 시점에 optionalAge에 5를 할당할 수는 있지만, 이 if let 구문이 해석되는 시점에는 optionalAge에 값이 없습니다. 따라서 이 코드의 결과가 어떻게 될지 잘 생각해 보세요.

Swift
var optionalAge: Int?

if let unwrappedAge = optionalAge {
    print("The value found is \\(unwrappedAge)")
} else {
    print("no value was found")
}

optionalAge = 5

만약 ‘no value was found’가 출력될 것이라고 예상하셨다면, 정답입니다. 제가 직접 실행해 보면 실제로 그렇게 나오죠. 왜냐하면 우리가 optionalAge에 값 5를 할당하는 시점은 값을 언래핑하려는 시도보다 나중이기 때문입니다. 모든 프로그램은 위에서 아래로 실행되니까요.


이것이 if let 바인딩의 작동 방식입니다. 하지만 if let 바인딩의 한 가지 문제점은, 여기서처럼 값을 특정 스코프 내에서만 사용해야 한다는 점입니다. 이 값은 특정 스코프에 종속되어 있기 때문에, 이 스코프 밖에서는 사용할 수 없다는 점을 아는 것이 중요합니다. 그래서 이 스코프 밖에서는 unwrappedAge를 나중에 사용할 수 없습니다. 보시다시피 자동 완성 목록에도 나타나지 않죠. 이것은 오류입니다. 물론 ‘상수라서 오류가 나는 거 아닌가? 변경 가능한 var로 만들면 되지’라고 생각하실 수도 있겠지만, var로 바꿔도 여전히 이 스코프 밖에서는 사용할 수 없습니다. 이 변수는 이 중괄호 {} 사이의 스코프에만 유효하기 때문입니다. 그래서 이 안에서는 원한다면 값을 변경할 수 있습니다. 이번에는 optionalAge에 값이 있다면, 옵셔널이 아닌 변수에 값을 바인딩하는 거죠. 이러한 스코프 제한에는 한계가 있습니다. 아마도 저는 unwrappedAge를 코드의 다른 부분에서 나중에 사용하고 싶었을 수 있습니다. 그렇죠? 이게 문제가 될 수 있습니다.

그래서 guard let (guard var) 바인딩이라는 개념이 있습니다. 이 방법은 값이 발견된 바로 그 스코프 내에서 바인딩된 값을 사용할 수 있게 해줍니다. 이것을 사용하려면 함수 내에서 var 바인딩을 사용해야 합니다. 아직 함수에 대해 배우지 않았으니 이 문법에 대해 너무 걱정하지 마세요. guard let (guard var) 바인딩은 특정 스코프 내에서만 동작해야 하기 때문입니다. if let처럼 전역 스코프에서는 작동하지 않아요. 저는 지금 특정 스코프와 코드 블록을 정의하는 함수 내부에 있고, 여기에 optionalAge가 있습니다.

방금 위에서 했던 모든 것과 동일한 작업을 guard let 바인딩으로 구현하면 다음과 같을 겁니다.

Swift
func processAge() {
    var optionalAge: Int?
    
    guard let unwrappedAge = optionalAge else {
        print("값이 없습니다.")
        return
    }
    
    print("언래핑된 값: \\(unwrappedAge)") // 함수 내 다른 곳에서 사용 가능
}

이제 optionalAge에 접근할 수 있어야 합니다.

몇 가지 차이점이 있습니다. if let과 마찬가지로 값을 언래핑하고, 값이 있으면 바인딩합니다. 만약 optionalAge에 값이 있다면, 그 값은 unwrappedAge라는 상수에 바인딩됩니다. 하지만 값이 없는 경우, return이라는 것이 실행되는데, 이것을 **’탈출 조건(exit condition)’**이라고 부릅니다. 탈출 조건이란 기본적으로 값을 찾지 못했기 때문에 현재 스코프를 빠져나간다는 의미입니다. 즉, 이 함수를 빠져나가 프로그램의 나머지 부분을 계속 실행하게 됩니다. 생각해 보면, guard 문은 값이 발견되지 않으면 더 이상 진행할 수 없는 안전장치를 만드는 셈입니다. 그리고 만약 값이 발견되지 않으면, 스코프를 빠져나가게 되죠.

따라서 여러분은 guard let이나 guard var를 (이것은 var가 될 수도 있습니다) 그 값이 존재하지 않으면 더 이상 진행할 수 없다는 것을 알 때 사용하게 될 겁니다. 예를 들어, 옵셔널인 username이 있다고 가정해 봅시다. 이 사용자의 프로필 페이지를 렌더링하려고 하는데, 사용자 이름 없이는 페이지를 렌더링할 방법이 없습니다. 사용자 이름은 100% 존재해야 하죠. 그래서 username을 언래핑하여 페이지를 렌더링하려고 시도할 수 있습니다. unwrappedUsername을 얻으려고 시도하겠죠. 만약 username이 존재하지 않는다면, 페이지 렌더링을 계속하지 않을 겁니다. 아마 경고나 에러 메시지를 표시하고, 페이지 렌더링 시도를 완전히 중단할 겁니다. 이런 경우에 guard let이나 guard var 바인딩이 매우 유용하게 사용됩니다.

따라서 다루고 있는 값이 옵셔널이고, 그 값이 없더라도 큰 문제가 발생하지 않는다면 if varif let 바인딩을 사용하면 됩니다. 반면에, 이 옵셔널 변수나 상수가 존재하지 않으면 더 이상 진행할 수 없는 상황이라면, guard varguard let을 고려해야 합니다.

여기서 한 가지 더 추가할 점은, if var unwrappedAge와 관련하여, 여기서 optionalAge를 그대로 사용해도 전혀 문제가 없다는 것입니다.

Swift
var optionalAge: Int?

if let optionalAge = optionalAge {
    print("언래핑된 값: \\(optionalAge)")
}

optionalAge를 두 번 생성하는 것처럼 보여서 약간 혼란스러울 수 있지만, 실제로는 그렇지 않습니다. 왜냐하면 여기서 생성되는 optionalAge는 위에서 생성된 optionalAge와는 다른 것이기 때문입니다. 블록과 스코프에 대해 배울 때 우리가 보지 못했던 한 가지는, 다른 스코프에 존재한다면 같은 이름으로 무언가를 재선언할 수 있다는 것입니다. 스코프 개념으로 다시 돌아가 보겠습니다. 예를 들어 if true가 있고, 위쪽에 optionalAge가 선언되어 있다면,

Swift
var optionalAge: Int?

if true {
    var optionalAge: Int? // 다른 스코프이므로 문제 없음
}

optionalAge가 여기에 있더라도, 아래에서 다시 선언할 수 있습니다. 여기 있는 optionalAge와 똑같은 이름으로요. 그리고 이것은 재선언으로 간주되지 않습니다. 그 이유는 이 optionalAge가 저것과는 다른 스코프에 존재하기 때문입니다. 그래서 둘 사이에 충돌이 없습니다. 문제는 같은 스코프에 두 개의 optionalAge가 있을 때 발생합니다. 만약 같은 스코프에 optionalAge를 두 번 생성하면, 오류로 간주될 겁니다. 같은 스코프에 두 개가 있기 때문이죠. 전역 스코프에 optionalAge를 선언해도 마찬가지일 겁니다. 같은 스코프에 두 개가 있으므로 오류로 간주됩니다.

따라서 여기서 보는 var optionalAge는 전역 스코프와는 구별되는 이 스코프 안에 있으므로 전혀 충돌이 없습니다. 그래서 이 둘은 조화롭게 공존할 수 있습니다. 그리고 이것은 사실 코드를 읽기 쉽게 만들어줍니다. optionalAge가 존재하면, 같은 이름의 변수에 바인딩해서 스코프 내에서 사용한다는 것을 명확하게 볼 수 있으니까요. 여기서도 마찬가지입니다. 저는 다른 스코프에 있으므로 여기서 username을 그대로 사용할 수 있고, 이 username은 여기에 있는 username입니다. 이것은 어떤 종류의 오류로도 해석되지 않습니다.


옵셔널 바인딩은 값을 추출하기 위해 강좌 전반에 걸쳐 계속 사용하게 될 겁니다. 하지만 가끔, 매우 드문 경우지만, 변수를 선언할 때는 nil을 가지고 있지만, 실제로 그 변수를 사용할 시점에는 값이 있을 것이라고 100% 확신하는 상황이 있습니다. 이것은 확실히 드문 상황이며, 다음과 같이 보일 수 있습니다.

예를 들어, bio라는 변수를 String 타입으로 선언하고 뒤에 느낌표 !를 붙입니다.

Swift
var bio: String!

느낌표는 옵셔널의 값을 강제로 추출할 때 사용한다는 것을 알고 있죠. 제가 지금 하는 것은 bio라는 변수를 String 타입으로 선언하는 것인데, 값이 없는 것을 주목하세요. 실제로는 nil이라는 값, 즉 값이 없다는 의미를 가지고 있습니다. 하지만 제가 여기서 기본적으로 말하는 것은, ‘내가 bio를 사용할 때쯤에는 반드시 값이 있을 거야’라는 의미입니다. 즉, 지금은 잠시 nil을 가지고 있지만, 실제로 사용되기 전에 미리 강제로 언래핑하는 것과 같습니다.

예를 들어, 여기에 optionalAge가 있습니다. **암시적 추출 옵셔널(implicitly unwrapped optional)**인 totalApples라는 Int 변수를 만들어 보겠습니다. 그리고 옵셔널이 아닌 값을 가진 totalOranges가 있습니다. 이 둘은 직접 더할 수 있습니다.

Swift
var totalApples: Int!
var totalOranges = 10

// 이 시점에서 totalApples는 nil이므로 아래 코드는 오류 발생
// let total = totalApples + totalOranges

왜냐하면 totalApplesnil을 가지고 있더라도, 제가 ‘사용할 때는 nil인지 아닌지 걱정하지 말고 그냥 써. 값이 있을 거라고 100% 확신해’라고 선언했기 때문입니다. 하지만 이 경우, 치명적인 오류가 발생할 겁니다. 왜냐하면 제가 totalApples를 사용할 때 여전히 값이 없기 때문이죠. 이 표현식에서 사용하기 전에 반드시 totalApples에 3과 같은 값을 할당해야 합니다. 그렇지 않으면 totalApplesnil이 되고, niltotalOranges에 더하려고 하면 오류가 발생합니다.

Swift
totalApples = 3
let total = totalApples + totalOranges // 이제 정상 작동

이것이 암시적 추출 옵셔널에 대한 간단한 설명이었습니다. 기본적으로 옵셔널을 생성하지만, 실제로 사용하기 전에 미리 강제 언래핑하는 것과 같다고 볼 수 있습니다. 암시적 추출 옵셔널이나 강제 언래핑 같은 방법들은 여러분이 직접 애플리케이션을 만들 때 흔하게 사용되지는 않을 겁니다. 옵셔널에 값이 있는지 안전하게 확인하고, 값이 있다면 그 값을 사용하는 if let 바인딩이나 guard let 바인딩에 의존하는 것이 훨씬 더 안전한 방법입니다. 방금 우리가 여기서 본 두 가지 방법 중 하나를 사용하는 것이죠.

답글 남기기