평범한 컴공 대학생의 공부일지

iOS Swift 옵셔널(Optional), 연산자 본문

iOS Swift

iOS Swift 옵셔널(Optional), 연산자

Taram 2023. 9. 24. 23:22

※이 iOS 카테고리의 글 들은 학교 강의와 과제를 기반으로 작성한 것입니다.※
※Window환경에서 작업하기 때문에 Online Compiler를 사용하는 점 참고 부탁드립니다.※



이번 글에서는 중요한 옵셔널(Optional), 연산자에 대해서 알아보겠습니다.

  • 옵셔널(Optional, (=Nullable Types))
    먼저 정의부터 알아야 내용을 파악할 수 있겠죠?
    -> 변수가 값을 가질 수도 있고, 아무런 값도 가지지 않을 수도 있다는 것을 나타내는 타입
    -> 값을 반환할 때 오류가 발생할 가능성이 있는 값은 옵셔널 타입이라는 객체로 감싸서 반환(안전 처리)
    -> 연산에 사용할 수 없는 타입
    이것이 Optional의 기본적인 정의입니다. 이 Optional을 통해 Swift는 값이 없음을 안전하게 표현할 수 있습니다.

    그리고 Optional에는 2가지 상태가 있는데 nilSome(wrapped)이 있습니다.
    - nil : 변수가 아무런 값도 가지고 있지 않음을 나타냄
             기본 자료형(Int, Double, String 등)은 nil 값 저장 불가능
    - Some(wrapped) : 변수가 특정한 값을 가지고 있으며 Wrapped라는 용어로 값이 래핑 되어 있다고 표현

    Optional의 기본적인 내용을 알아보았으니 선언하고 사용하는 방법을 말씀드릴게요.
    Optional은 다른 변수와 다르게 !(값 풀어헤치기)?(선언)를 사용합니다. 아래 코드는 Optional 타입을 선언하는 코드입니다.
    * 1번째, 2번째 코드 각각 같은 이름의 변수를 선언하기 때문에 이름을 다르게 하거나 한 줄씩 주석처리 해주세요.
var y : Int? = 15 //실행 결과 : optional(15)
var y : Int! = 15 //실행 결과 : optional(15)

print(y)

     위 코드를 실행하면 실행 결과가 optional(15)가 나오는 것을 확인할 수 있습니다.
     이 optional(15)라는 결과가 의아하실 수 있습니다. 15라고 예상을 했는데 갑자기 값 앞에 optional이 붙으니까요.
     이 결과의 뜻은 y라는 변수의 값이 15일 수도 있고 nil일 수도 있는 optional 타입 Int형 변수라는 말입니다.
     이렇게 값을 가질 수도 nil일 수도 있어 값을 안전하게 다루기 위해 사용되는 기능입니다.
     ?는 보통 선언할 때 사용을 하며 !는 값을 풀어헤칠 때 사용하게 됩니다. 값을 풀어헤친다 라는 말이 이해가 안 가시는 분이 계실 겁니다. optional(15)라는 값은 15라는 값이 optional로 wrapped되어 있다는 의미입니다. wrapped되어 있는 것을 풀어헤쳐 15라는 값을 가져온다는 의미를 담고 있기 때문에 !를 사용합니다. 예시 코드로 확인해 볼까요?
아래 코드를 실행해보면 처음 코드에서 optional(15)라고 나온 값이 15로 바뀌어 출력되는 것을 확인할 수 있습니다.

var y : Int? = 15 //실행 결과 : 15
print(y!)

여기서 주의할 점이 있습니다. 위 코드의 y는 optional(15)라는 값을 가지고 있지만 nil(아무 값도 없음)을 !을 사용하게 되면 어떻게 될까요? 예시 코드를 통해 확인해 봅시다.

var y : Int? 
print(y!)

optional은 값을 지정하지 않으면 자동으로 nil 상태를 의미하게 됩니다. nil을 !로 풀어내면 아래와 같은 오류가 발생합니다.

오류 화면

위와 같이 뜨는 오류는 없는 값을 !로 풀어내기 때문에 일어나는 것입니다. 이는 Crash(충돌)가 일어나는 것인데 그로 인해 App이 다운될 수 있습니다. 이 Crash를 방지하기 위해 Forced Unwrapping(강제 언래핑)을 사용합니다. 그럼 이제 Forced Unwrapping에 대해 알아볼까요?
- Forced Unwrapping
  1) Forced Unwrapping
      - Optional 변수에 값이 있으면 wrapped(래핑)되었다고 합니다. 이를 Forced Unwrapping으로 풀어주어 값을 사용합니다. 예시 코드를 먼저 살펴볼까요?

var x : Int? //옵셔널 정수형 변수 x 선언
var y : Int = 0
x = 10 // 주석 처리시 print(x!)에서 Crash 발생 -> App 다운 -> print(y) 실행X

print(x) // 실행 결과 : Optional(10)
print(x!) // 실행 결과 : forced unwrapping해서 10이 나옴
print(y) // 실행 결과 : 0

     위 코드를 살펴보면서 차례대로 설명드리겠습니다.
     1번째 줄 : optional 타입 변수 x를 선언
     2번째 줄 : Int형 타입 변수 y 선언 후 0 값 대입
     3번째 줄 : x에 값 10을 대입
     4번째 줄 : x의 값을 출력 -> 실행 결과 : Optional(10)
     5번째 줄 : x의 값을 풀어 출력 -> 실행 결과 : 10 -> Forced Unwrapping을 통해 optional(10) 값을 풀어주어 10이 출력됨.
     6번째 줄 : y의 값을 출력 -> 실행 결과 : 0
이처럼 optional형 변수의 값을 Forced Unwrapping을 통해 일반 값으로 풀어낼 수 있습니다. 또 다른 코드를 살펴볼까요?

var x : Int? //옵셔널 정수형 변수 x 선언
var y : Int = 0
x = 10 // 주석 처리시 print(x!)에서 Crash 발생 -> App 다운 -> print(y) 실행X

//x = x+2  Int와 Optional 연산 불가 -> 오류
//y = x  Int와 Optional 연산 불가 -> 오류
x = x! + 2 // 연산 후 Optional로 다시 랩핑
y = x! // optional(12)를 Forced Wrapping을 통해 12라는 값 저장
print(x) // Optional(12)
print(y) // 12

    위 코드도 차례대로 1줄씩 설명드리겠습니다.
    1번째 줄 : optional 타입 변수 x를 선언
    2번째 줄 : Int형 타입 변수 y 선언 후 0 값 대입
    3번째 줄 : x에 값 10을 대입
    4번째 줄 : optional형 변수 x에 2를 더하는 연산 수행 -> Optional은 특정 값 존재 or nil이라는 특성 때문에 일반적인 값으로 사용될 수 없음.
    5번째 줄 : Int형 변수 y에 optional형 변수 x의 값을 대입 -> Optional형과 Int형 연산 불가
    6번째 줄 : x에 x를 Forced Unwrapping 한 값인 10과 2를 + 연산 -> 값은 12가 되며 연산 후 Optional로 다시 wrapped 되기 때문에 optional(12)가 저장
    7번째 줄 : x에 저장된 optional(12)라는 값을 Forced Unwrapping을 통해 12라는 값으로 y에 저장
이제 어느정도 이해가 되시나요? 이처럼 Optional형의 값을 강제로 wrapped를 해주는 것이 Forced Unwrapping입니다.
하지만 이 경우 주의 사항이 있습니다. 띄어쓰기와 if문인데요. 다음 코드를 통해 살펴보겠습니다.

var x : Int? //optional형 변수 x 선언
x = 10 //optional(10)
if x != nil { //x가 nil이 아니라면
print(x!) //Forced Unwrapping을 통해 x의 값을 출력
} 
else { //nil이라면
print("nil") //nil을 출력
}

위 코드의 설명은 주석으로 붙여 놓았습니다. 1번째로 if문부터 설명해 드릴게요.
Optional형은 특정한 값이 있거나 없을 수 있다고 했습니다. 그럼 만약에 nil일 경우 x! 명령어가 들어가 있으면 바로 Crash가 일어나 App이 다운될 것입니다. 그로 인해 if 조건문을 이용하여 nil일 때와 아닐 때를 구분하여 사용해야 합니다.

2번째는 띄어쓰기입니다. 특히 3번째 줄을 보셔야 합니다. if x!=nil 이렇게 사용할 경우 아래와 같은 오류가 발생합니다.

오류 코드

이 문제는 !(강제 추출 연산자)의 우선순위로 인해 발생합니다. !는 피연산자(변수)와 묶이는 높은 우선순위를 가집니다.
그러므로 x와 묶여 Optional(10)으로 인식하고 10이라는 값이 나옵니다. 그리고 nil이 x에 대입되는 순서를 가지기 때문에 2번째와 같은 nil이 Int타입과 맞지 않다는 오류가 출력됩니다. 
이러한 경우 때문에 Forced Unwrapping의 경우 이러한 주의 사항을 고려해야 합니다. 추가적으로 옵셔널 값이 nil인 경우 런타임 오류가 발생할 수 있으므로, 가능한 강제 추출 대신 옵셔널 바인딩 또는 옵셔널 체인을 사용하는 것이 좋습니다.
이렇게 하면 코드를 보다 견고하게 만들어 crash를 방지하는 기법이 됩니다.

   2) Optional Binding
      Optional Binding은 옵셔널 변수의 값을 안전하게 추출하고, 해당 값이 존재하는지 여부를 확인하는 방법 중 하나입니다. 
이를 통해 값이 존재할 때만 해당 값을 사용할 수 있으므로 런타임 오류를 방지하고 안전한 코드를 작성할 수 있습니다.
먼저 아래 코드를 살펴보죠.
   - 예제 1 : 값이 있는 경우

var x: Int? // nil
x = 10
if let xx = x {
    print(x, xx) // 출력 결과: optional(10) 10
} else {
    print("nil")
}

위의 코드에서는 x라는 옵셔널 변수에 값이 10으로 할당되어 있습니다. 그리고 if let을 사용하여 x의 값을 안전하게 추출하고, 이 값을 xx에 할당합니다. if 블록 내에서는 xx 변수를 사용하여 값을 출력하므로 값이 존재할 때만 실행됩니다.

   - 예제 2 : 값이 없는 경우

var x1: Int?
if let xx = x1 {
    print(xx)
} else {
    print("nil") // 출력 결과: nil
}

이번에는 x1이라는 옵셔널 변수에 아무 값도 할당되어 있지 않습니다. 따라서 if let 구문 내의 조건이 거짓이 되어 else 블록이 실행됩니다. 이 경우 "nil"이 출력됩니다.
추가적으로 Swift 5.7부터 더 간편하게 쓸 수 있는 방법도 알려 드리겠습니다.

var x: Int? // nil
x = 10

if let x = x {
    print(x) // 실행 결과: 10
} else {
    print("nil")
}

차이점이 보이시나요? 변수 명이 위 코드들과 다르죠?
그 이유는 Swift 5.7에서는 변수명을 중복하여 사용할 수 있게 되었기 때문입니다. 따라서 if let 구문에서 변수명을 중복으로 선언하여도 정상적으로 동작합니다. 이를 통해 코드를 더 간결하게 작성할 수 있습니다.

var x : Int? // nil
x = 10
if let x{ // Optional Binding의 축약형, Swift 5.7부터 지원
print(x) // 실행 결과 : 10
}
else {
print("nil")
}
var x1 : Int?
if let x1{ // Optional Binding의 축약형, Swift 5.7부터 지원
print(x1) // 실행결과 nil
}
else {
print("nil")
}

 

이렇게 Swift 5.7에서는 변수를 또 다른 변수나 중복 변수에 할당하지 않고도 사용 가능합니다.
새로운 기능을 활용하면 코드를 더 간결하게 작성할 수 있으며, 옵셔널 값의 안전한 추출이 더욱 편리해집니다. 이러한 기능을 통해 코드 작성과 유지보수를 더욱 효율적으로 수행할 수 있습니다.

var pet1: String?
var pet2: String?
pet1 = "cat"
//pet2 = "dog" // pet2를 주석처리 -> pet2 = nil
if let pet1, let pet2 { // 콤마(,)사용 -> 둘 다 nil이 아니여야하기 때문에 else로 넘어감
print(pet1,pet2)
} else {
print("nil") // 실행결과 : nil
}

추가적으로 위 코드처럼 다수의 Optional 변수를 한 번에 Unwrapped 할 수도 있습니다. 하지만 이런 경우에는 각 변수가 모두 nil이 아니거나 nil이어야 합니다.

    3) Implicitly Unwrapped Optional
        Implicitly Unwrapped Optional은 암시적 언래핑 옵셔널이라는 뜻입니다. Optional 변수가 Optional로 사용되지 않으면 자동으로 Unwrap 하는 기능을 가지고 있습니다. 아래 코드를 통해 알아보겠습니다.

let a: Int! = 1 // Implicitly Unwrapped Optional, Optional(1)
let b: Int = a // Optional로 사용되지 않으면 자동으로 언래핑 됨
                // Optional(1)로 사용되지 않고 Int로 사용되기 때문에 Implicitly Unwrapped Optional 적용
                
let c: Int = a! // a의 값(Optional(1))을 풀어서 Int형으로 c에 저장
let d = a // Optional로 사용될 수 있으므로 Optional 형태임
let e = a + 1 // 1의 자료형인 Int와 연산되기 때문에 Implicitly Unwrapped Optional 적용 -> e = 1 + 1 -> e = 2

print(a, b, c, d, e) // Optional(1) 1 1 Optional(1) 2
print(type(of: a), type(of: b), type(of: c), type(of: d), type(of: e)) // Optional<Int> Int Int Optional<Int> Int

Implicitly Unwrapped Optional은 암시적으로 풀리는 옵셔널로, 변수 a는 암시적으로 언래핑되어 값을 추출할 필요가 없습니다. 따라서 b는 일반적인 Int로 취급됩니다.


  • 연산자
    - 연산자를 들어가기 전에 먼저 중요한 단어 2가지를 짚고 가보겠습니다.
      Precedence : 우선순위 -> 3 + 4 * 5에서 * 연산이 + 연산보다 우선순위가 높으므로 곱셈을 먼저 계산
      Associativity : 결합성 -> 3 + 4 + 5에서 +연산자는 왼쪽부터 오른쪽으로 결합하기 때문에 (3 + 4) + 5가 성립
    - 종류
       1) 기본 할당 연산자 (=)

           
    할당 연산자는 기본적으로 2개의 피연산자를 가지며 변수에 값을 할당합니다.
            let x = 5와 같은 경우를 예를 들 수 있겠죠.
       2) 산술 연산자 ( - (단항), +, -, *, /, %)
           - (단항) 변수 또는 표현식의 값을 음수로 만듭니다.
           * 곱셈, / 나눗셈, + 덧셈, - 뺄셈, % 나머지연산
let a = 10
let b = 5
let sum = a + b // 덧셈
let difference = a - b // 뺄셈
let product = a * b // 곱셈
let quotient = a / b // 나눗셈
let remainder = a % b // 나머지

        3) 복합 할당 연산자 (+=, -=, *=, /=, %=)
            변수에 현재 값을 연산하여 다시 할당하는 데 사용합니다.

var x = 10
x += 5 // x = x + 5와 동일

       4) 증가 연산자, 감소 연산자 (++, --)
           변수의 값을 1씩 증가 또는 감소시키는 데 사용합니다.
           기존엔 ++, --를 사용했으나 Swift 3에서 사라져 아래 예시와 같은 방법을 사용합니다.

var count = 0
count+=1 // count를 1 증가
count-=1 // count를 1 감소

      5) 비교 연산자 (==, !=, <, >, <=, >=)
          값들을 비교하여 참(True) 또는 거짓(False)을 반환합니다. (Boolean 반환)

let a = 5
let b = 10
let isEqual = a == b // 등호(==) 연산자는 a와 b가 같은지 비교
let isNotEqual = a != b // 부등호(!=) 연산자는 a와 b가 다른지 비교
let isLessThan = a < b // 작은 부등호(<) 연산자는 a가 b보다 작은지 비교
let isGreaterThan = a > b // 큰 부등호(>) 연산자는 a가 b보다 큰지 비교

    6) Boolean 논리 연산자 (&&, ||, !)

        논리 연산을 수행하는 데 사용하며 참(True) 또는 거짓(False)을 반환합니다.

let isTrue = true
let isFalse = false
let andResult = isTrue && isFalse // 논리곱(AND) 연산자는 두 값이 모두 참이어야 참
let orResult = isTrue || isFalse // 논리합(OR) 연산자는 두 값 중 하나라도 참이면 참
let notResult = !isTrue // 논리부정(NOT) 연산자는 값을 반대로 뒤집음

    7) 범위 연산자 (..<, ...)
        숫자 범위를 나타내는 데 사용됩니다.

print(1,2,3)
print(1...3) //실행 결과 : 1...3 -> 1 2 3과 같음

        나중에 배열과 반복문을 통해 아래 예시 코드와 같은 기능도 가능합니다.

let names = ["A", "B", "C", "D"]
for name in names[...2] { //[...2], [..<2] //실행 결과: A\n B\n C\n
print(name)}

     8) 삼항 연산자 (conditional ? trueValue : falseValue)
         조건에 따라 값을 선택하는 데 사용됩니다.

let score = 85
let result = score >= 70 ? "Pass" : "Fail"

        위 코드에서 score가 70 이상이면 "Pass", 그렇지 않으면 "Fail"을 반환합니다.

     9) Nil-Coalescing Operator(Nil 합병 연산자, ??)
        옵셔널 값이 nil인지 확인하고, nil이라면 우측의 기본값을 반환하는 연산자입니다.
        아래 코드의 주석으로 설명을 달아 놓았습니다.

let defaultColor = "black" // defaultColor에 "black"이라는 문자열 저장
var userDefinedColor: String? // 기본적으로 nil 값 가짐

var myColor = userDefinedColor ?? defaultColor
// myColor은 userDefinedColor가 nil이면 defaultColor를 사용하고, 아니면 userDefinedColor 값을 사용

print(myColor) // 출력 결과: "black" (userDefinedColor가 nil이므로 defaultColor 값인 "black"이 할당됨)

userDefinedColor = "red"
myColor = userDefinedColor ?? defaultColor
// userDefinedColor가 nil이 아니므로 userDefinedColor 값인 "red"가 할당됨

print(myColor) // 출력 결과: "red" (userDefinedColor 값인 "red"이 할당됨, 주의: optional("red")가 아님)

  • 강의 중간 과제 (뤼튼 사용)
    1) swift와 kotlin의 optional을 코드로 각 줄에 주석으로 설명해 줘
//Swift Optional

var optionalString: String? = "Hello" // Optional 변수 선언과 초기화
optionalString = nil // Optional 변수에 nil 할당

if let unwrappedString = optionalString { // Optional Binding으로 값 확인
    print(unwrappedString) // 값이 있다면 출력
} else {
    print("The string is nil") // 값이 없다면 출력
}

       1번째 줄 - String 타입의 옵셔널 변수 optionalString을 선언하고 "Hello"라는 값을 할당했습니다.
       2번째 줄 - optionalString에 nil을 할당하여 값을 비웠습니다.
      이후 if let 구문 (Optional Binding)을 사용하여 optionalString의 값을 안전하게 언래핑했습니다. 만약 optionalString에 값이 있다면 해당 값을 사용하고, 그렇지 않다면 "The string is nil" 메시지를 출력합니다.

//Kotlin Optional

var nullableString: String? = "Hello" // Nullable 변수 선언과 초기화
nullableString = null // Nullable 변수에 null 할당

nullableString?.let { 
    println(it)  // 값이 있다면 출력 
} ?: run { 
    println("The string is null")  // 값이 없다면 출력 
}

       1번째 줄 - String 타입의 nullable 변수 'nullableString'을 선언하고 "Hello"라는 값을 초기화했습니다.
       2번째 줄 - 'nullableString'에 'null'을 할당하여 값을 비웠습니다.
       이후 'let' 함수를 사용해서 nullable 한 객체를 안전하게 참조했습니다. 만약 'nullableString'에 값이 있다면 해당 값을 사용해서 프린트하고 그렇지 않으면 "The string is null" 메시지를 프린트합니다.

 

      2) Implicitly Unwrapped Optional을 예를 들어 설명해 줘
          Implicitly Unwrapped Optional은 Swift에서 제공하는 특성입니다.
         이는 Optional이지만 일반적으로 해당 값이 존재한다고 가정하고 사용하는 경우에 유용합니다.
         예를 들어, 인터페이스 빌더의 아웃렛 연결을 설정할 때 Implicitly Unwrapped Optional이 주로 사용됩니다.
        아웃렛 변수들은 초기화 시점에는 nil값을 가지지만, 뷰 컨트롤러의 생명주기 내에서는 항상 값을 가진다고 보장되므로 Implicitly Unwrapped Optional로 선언합니다.
        Implicitly Unwrapped Optional은 일반적인 Optional과 마찬가지로 nil 값을 할당할 수 있습니다. 하지만 이를 사용할 때는 자동으로 언래핑되므로 별도의 언래핑 과정 없이 직접 접근할 수 있습니다.

var implicitlyUnwrappedOptional: String! = "Hello" // Implicitly Unwrapped Optional 변수 선언 및 초기화
print(implicitlyUnwrappedOptional) // 값 출력 ("Hello")

implicitlyUnwrappedOptional = nil // Implicitly Unrapped Optional 변수에 nil 할당

// 아래 코드는 실행 시 RuntimeError를 발생시킵니다.
// implicitlyUnrappedOptional 값이 nil인 상태에서 접근하려 하기 때문입니다.
print(implicitlyUnrappedOptional)

위 예제에서 첫 번째 줄에서 String! 타입으로 Implicitly Unwrapped Optional 변수를 선언하고 "Hello"라는 값을 초기화했습니다. 두 번째 줄에서 implicitlyUnrappedOptional을 그대로 출력하는데, 이때 별도의 언래핑 과정 없이 바로 접근 가능한 것을 확인할 수 있습니다.

그리고 nil을 할당한 후 다시 접근하려 하면 RuntimeError가 발생합니다. 따라서 Implicitly Unrapped Optionals은 값이 항상 존재한다고 확신할 수 있는 경우에만 사용해야 합니다.