Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<4주차> Optional 이란 무엇인지 설명하시오. #22

Closed
namsoo5 opened this issue Nov 20, 2020 · 8 comments
Closed

<4주차> Optional 이란 무엇인지 설명하시오. #22

namsoo5 opened this issue Nov 20, 2020 · 8 comments

Comments

@namsoo5
Copy link
Collaborator

namsoo5 commented Nov 20, 2020

No description provided.

@iJoom
Copy link
Collaborator

iJoom commented Nov 24, 2020

Optional

래핑된 값 또는 값이 없는 nil 상태를 표현하는 형식이며, enum 구조를 뛰고 있다.
값이 있고 래핑된 some case 제네릭 형태 그리고 값이 없는 nil상태 none 케이스

Optional.none은 nil과 동일하다


공식문서 예시 코드
Optional.some (Wrapped)은 래핑된 값을 저장

let number: Int? = Optional.some(42)
let noNumber: Int? = Optional.none

여러 컨텍스트에서 Optional 값을 사용하려면 Optional 인스턴스의 값을 래핑을 해제한 후 사용해야 합니다. 래핑된 값을 새 변수에서 바인딩 하려면 if let과 guard let 그리고 switch문을 이용해 옵셔널 바인딩을 할 수 있습니다.

if let starPath = imagePaths["star"] {
    print("The star image is at '\(starPath)'")
} else {
    print("Couldn't find the star image")
}

강제 옵셔널로 값을 사용할 수 있지만, 프로젝트 크기가 커지고 협업을 하다보면 예상치 못하게 앱이
죽는 결과로 이어질 수 있습니다. 따라서 강제 옵셔널을 사용하지 않는 것이 바람직합니다.

let number = Int("42")!
print(number)
// Prints "42"

@iJoom iJoom added the 인준 label Nov 24, 2020
@namsoo5
Copy link
Collaborator Author

namsoo5 commented Nov 25, 2020

Optional이란?
값이 있을 수도 없을 수도 있는 열거형타입
none, some케이스가 존재하고 some은 제네릭타입으로 구현되어있음

참고링크

@namsoo5 namsoo5 added the 남수 label Nov 25, 2020
@khyunjiee
Copy link
Member

Optional

nil을 사용할 수 있는 타입과 사용할 수 없는 타입을 구분하고, 사용할 수 있는 타입을 가리켜 옵셔널 타입이라고 부른다.

  • 자료형 뒤에 ?를 붙여 옵셔널 타입으로 만듦
  • nil은 값이 없음을 의미하는 값

옵셔널 타입으로 선언된 자료형은 nil 값을 저장할 수 있다.
옵셔널 타입의 처리 과정에 문제가 발생하면 nil이 반환되고, 문제가 없이 처리가 성공했다면 옵셔널 객체로 감싸진 결과값(Optional(객체))이 반환된다.
이와 같이 옵셔널 타입으로 반환된 값을 옵셔널 래핑 (Optional Wrapping) 이라고 한다.
래핑된 값은 옵셔널 언래핑 (Optional Unwrapping) 을 통해 옵셔널 타입을 해제하고 실제 값을 추출하여 사용해야 한다.

옵셔널 언래핑 (Optional Unwrapping)

  1. 명시적 해제
    1-1. 강제 해제
    ! 연산자를 사용해서 옵셔널 객체를 해제한다.
    강제 해제 연산자인 !를 사용하면 먼저 옵셔널 값이 nil인지 확인해야 한다.
    옵셔널 값이 nil이 아닐 때만 강제 해제 연산자를 붙여서 값을 추출해야 한다.
    1-2. 옵셔널 바인딩 (Optional Binding, 비강제 해제)
    조건문 내에서 일반 상수에 옵셔널 값을 대입하는 방식으로 이루어진다.
    if let a = optionalVar {
        // ...
    } else {
       // ...
    }
    if let 구문을 사용한 옵셔널 바인딩은 단순히 옵셔널 값의 처리 결과에 따라 서로 다른 피드백을 주고 싶을 때 사용한다.
    guard let a = optionalVar else {
       // ...
       return
    }
    guard let 구문은 조건에 맞지 않으면 무조건 함수의 실행을 종료시키는 특성이 있다.
    따라서 실행 흐름상 옵셔널 값이 해제되지 않으면 더 이상 진행이 불가능할 정도로 큰일이 생길 때에만 사용하는 것이 좋다.
    옵셔널 타입이긴 하지만 절대 nil 값이 들어가지 않을 것이라는 보장이 있을 때에는 강제 해제 연산자를 사용하여 옵셔널 타입을 처리하는 것이 효율적이다.
  2. 묵시적 해제
    2-1. 컴파일러에 의한 자동 해제
    명시적으로 강제 해제를 하지 않아도 컴파일러에서 자동으로 옵셔널을 해제해주는 경우가 있다.
    옵셔널 객체의 값을 비교 연산자를 사용해서 비교하는 경우이다.
    명시적으로 옵셔널 객체를 강제 해제하지 않아도 한쪽이 옵셔널, 다른 한쪽이 일반 타입이라면 자동으로 옵셔널 타입을 해제하여 비교 연산을 수행한다.
    2-2. ! 연산자를 사용한 자동 해제
    ! 연산자를 붙여 변수를 정의하면 옵셔널 타입이 된다. var str: String! = "Swift"
    묵시적 해제 선언이 이루어졌기 때문에 일반적으로 옵셔널 타입을 사용할 때 필요한 강제 해제 연산자나 옵셔널 바인딩 과정 없이, 일반 변수처럼 다루어도 된다.
    변수의 값이 nil이 될 가능성이 있다면 묵시적 옵셔널 해제를 사용하지 않는 것이 좋다.
    즉, 형식상 옵셔널로 정의해야 하지만, 실제로 사용할 때에는 절대 nil 값이 대입될 가능성이 없는 변수일 때 사용한다.

@dongminyoon
Copy link
Collaborator

dongminyoon commented Nov 26, 2020

Optioanal

Swift의 Fast, Safe, Expression 의 특징 중 Safe를 가장 잘 보장해줄 수 있는 기능.
Optional은 '?'을 통해 표현되는데 이 타입을 사용하면 nil 값을 할당할 수 있다.
이 값을 사용하면 변수안에 값이 있을지 없을 지 모르게 된다.

그렇기 때문에 Optional 값을 사용하기 위해선 값이 있는지 없는지 확인하는 작업이 필요하다.

첫번째로 '!'을 사용해서 강제 Unwrapping을 실행할 수 있다. 그러나 이 방법은 런타임 중에 에러를 발생시킬 수 있기 때문에 정말 값이 없다고 보장하는 경우에만 사용하는 것이 권장!

두번째는 Optional Binding을 이용하는 방법이 있다. if var, if let, guard let 구문과 함께 사용해서 더욱 안전하게 Unwrapping을 할 수 있다.

Optional에서 추가로 공부하면 좋을 것은 Optional Chaining이 있다.
Optional Chaining을 사용하면 nil일 수 있는 프로퍼티나, 메소드에 접근하는 과정을 말한다. 만약 옵셔널이 값을 갖고 있지 않다면 연산 과정에서 nil 값을 반환하고 그렇지 않으면 원하는 동작을 수행합니다. 만약 여기서 연결된 값중 어느 하나라도 nil일 경우 nil을 반환한다.

@choidam
Copy link
Member

choidam commented Nov 27, 2020

Optional

swift 에서는 변수를 선언할 때 non-optional 한 값을 변수에 주어야 합니다. 만일 변수 안에 값이 확실히 있다는 것을 보장할 수 없으면 Optional 을 사용해야 합니다. 타입 어노테이션 값에 Optional 기호 ? 를 붙여 사용합니다.

var test: Int?

optional 변수는 초기화 하지 않으면 nil 로 자동으로 초기화 됩니다.


Optional 변수의 값 가져오는 방법

  1. Forced Unwrapping (강제 언래핑)

옵셔널에 값이 있다고 가정하고 값에 바로 접근할 수 있도록 강제하는 방법입니다. 느낌표(!) 키워드를 붙여 사용합니다. 그러나 값이 존재하지 않는 옵셔널 값에 접근하려 시도하면 런타임 에러가 발생합니다. 느낌표를 사용하여 강제 언래핑 하기 전에 항상 옵셔널 값이 nil 이 아니라는 것을 확실히 해야 합니다.

var optionalEmail: String?
print(optionalEmail!) 

fatal error: unexpectedly found nil while unwrapping an Optional value

가급적이면 일반적인 옵셔널을 사용해서 정의하고, 옵셔널 바인딩 또는 옵셔널 체이닝을 통해 값에 접근하는 것이 더 바람직합니다.


  1. Optional Binding

옵셔널의 값이 존재하는 지를 검사한 뒤, 존재한다면 그 값을 다른 변수에 대입시켜줍니다. if let if var 구문을 사용합니다. 옵셔널을 바인딩할 때 , 를 사용해서 조건도 함께 지정할 수 있습니다.

if let email = optionalEmail {
  print(email)
}
var optionalAge: Int? = 22

if let age = optionalAge, age >= 20 {
  print(age)
}

  1. Optional Chaining

옵셔널 체이닝은 옵셔널의 속성에 접근할 때, 옵셔널 바인딩 과정을 ? 키워드로 줄여주는 역할을 합니다.

let isEmptyArray = array?.isEmpty == true

@choidam choidam added the label Nov 27, 2020
@elesahich
Copy link
Collaborator

Optional

ref : https://www.youtube.com/watch?v=25dvsEiwn4s&feature=youtu.be&t=824
야곰 스위프트 책에 있는 내용 - Monad

  1. Optional은 Enumeration 타입이다.
  2. nil을 wrapping하거나, 를 Wrapping한 타입이다. 형식은 Optional

위에 사용법들은 너무 많이 잘 적어줘서 조금 줄이고( IUO, 강제 언래핑 등),,,, 모나드 이야기를 하겟읍니다

Monad

설명을 위해 Content와 Context 맥락을 도입합니다. (Content == Container)
컨테이너는 내용물이 있어도 되고, 없어도 되는 객체 그 자체입니다.
모나드도 비슷한 맥락으로, 그 자체로 함수객체이며 값이 있을 수도 없을 수도 있는 상태를 포함하는 개념입니다.
값이 있을 수도 있고 없을 수도 있다? 굉장히 많이 들어본 말이며,, 옵셔널과 굉장히 비슷한데요,
Swift에서는 Optional이 Monad를 구현한 형태라고 할 수 있습니다.

Optional을 다루고 난 데이터는 Optional을 리턴합니다. 다음을 봅시다.

image

딕셔너리는 대표적으로 Optional을 리턴하는 객체입니다.
var data = dic[2] 에서 data는 Optional 타입이고,

그 다음줄에서 data에 확실한 인티저 3을 대입해주었을 때
다음 data의 타입은 Optional을 벗어날 수 있을까요?

정답은 X입니다. data의 Optional 타입은 바뀌지 않습니다.
Optional의 연산값은 Optional을 리턴해주게 됩니다.

이유는 다음 코드로 설명을 해보겠습니다, Monad를 코드로 써 볼게요.

class Functer<T> {
    let value: T
    init(_ v: T) {
        value = v
    }
    
    func map<U>(_ f: (T) -> U) -> Functer<U> {
        let u = f(value)
        return Functer<U>(u)
    }
}

다음과 같은 Functer class가 있고, 내부함수 map함수는 Functer를 리턴하고 있습니다.
map이 Functer를 리턴하면 다음과 같이 계속 사용할 수 있습니다.

  let name = "Original"

  let changeNameLength = Functer(name) // Funter<String>
            .map { "\($0) Changed" } // Funter<String>
            .map { $0.count }     // Funter<Int>
            .map { $0 * $0 }       // Funter<Int>
            .value
        print(changeNameLength)

Functer 클래스 내부함수에서 map은 Functer를 리턴하니 계속 map을 활용할 수 있습니다.
내부 컨텐츠의 타입은 바뀌지만, 컨테이너는 변하지 않습니다.

옵셔널도 위와 같은 컨테이너 역할을 하게 됩니다. 그럼, [Int]?나 [Int?]` 이런 애들은 이중 컨테이너에 갇힌거에유.

그런데 얘내들은 문제가 생기죠...어떤 문제냐면, 제가 이제 함수를 만들어 볼게요.
아직 완벽한 모나드의 구현은 아직인거에여

그리고 다른 함수, Functer를 리턴해주는 함수를 만들어 볼거에요.

   func stringLength(_ s: String) -> Functer<Int> {
        return Functer(s).map { $0.count }
    }

스트링 길이를 구하는 함수를 만들었습니다.
리턴형에서 Functer를 리턴하고 있는것을 잘 보세요. 그럼 이제 이렇게 만들 수 있습니다.

 let changeNameLength = Functer(name)    // Funter<String>
            .map { "\($0) Changed" }   // Funter<String>
            .map (stringLength)         // Functer<Funter<String>> ㅏㅏ Error
            .map { $0 * $0)    // 
            .value
        print(changeNameLength)

이렇게 사용하면 다음과 같은 문제가 생깁니다. Functer<Funter<String>> 은 value를 추출할수 없어서 에러.
이 Functer로 두번 래핑된 친구를 하나로 만들어줘야겠죠?
(Mono하게 만들다(하나로 만들다) > Monoid -> Monad...이런 유래라는데요)
모나드 개념이 여기에서 도입되었대요.

Functer Class에 flatMap을 만들어 봅시다.

class Functer<T> {
    let value: T
    init(_ v: T) {
        value = v
    }
    
    func map<U>(_ f: (T) -> U) -> Functer<U> {
        let u = f(value)
        return Functer<U>(u)
    }
    
    func flatMap<U>(_ f: (T) -> Functer<U>) -> Functer<U> {
        let fu = f(value)
        return Functer<U>(fu.value)
    }
}

flatMap은 Functer를 리턴하는 인자 f를 받아서 value를 추출한다음, 다시 Functer를 리턴해주니까

        let changeNameLength = Functer(name) // Funter<String>
            .map { "\($0) Changed" } // Funter<String>
            .flatMap(stringLength(_:)) // Funter<Int>
            .map { $0 * $0 } // Funter<Int>
            .value
        print(changeNameLength)

이 코드도 문제없이 동작하게 됩니다.
결론을 내려볼게요

Immutable Data를 다루는 관점

Optional 안에 있는 값의 관점에서 보면, 위에서 보았듯 Optional이라는 것으로 Wrapping되어 있으므로 이를 바로 수정할 수 없습니다.
이는 변하지 않음을 보장하므로

  1. 변화 추적/예측이 가능하다(predictable)
  2. 테스트하기 좋다(testable)
  3. 동시성이 좋다 (thread-safe)

위와 같은 세가지의 장점을 가질 수 있습니다.

스크린샷 2020-11-27 오후 2 30 17

그림으로 마무리 해보겠습니다.
data타입에 접근하는 방법은 내부적으로는 getter, setter가 있고,
외부에서 접근하는 방법은 flatMap, map을 사용할 수 있는 것이다.

변경된 값이 필요하다? 새로 만들어라! 가 핵심이 되겠습니다.
모나드 정리는 여기서 끝이고,
그래서 옵셔널은 모다? 모나드이다. 끝~~

@elesahich elesahich added the 승호 승호 label Nov 27, 2020
@Juhyeoklee
Copy link
Collaborator

Optional

Optional 이란 값이 있을 수도 있고 없을 수도 있다는 뜻을 나타내는 개념으로 일반적인 자료형 옆에 '?' 표시를 해줌으로 표현할 수 있다.
따라서 옵셔널이 붙은 자료형의 경우에는 nil(=null)값을 할당 할 수 있게 되며, 이에따라 옵셔널 이 아닌 타입에 nil을 할당하게 된다면 컴파일 단계에서 부터 에러가 발생한다.

그리고 '?'로 표시된 옵셔널 타입의 변수는 'Optional<>'이라는 객체로 쌓여져 있게 된다. 따라서 이러한 변수를 사용하기 위해서는 unwrapping 과정이 필요하다. Unwrapping을 하는 방법에는 강제추출, 옵셔널 바인딩, 옵셔널 체이닝 이 있다.

강제 추출의 경우 nilsafe하지 않고 Optional로 Wrapping된 객체를 강제로 뜯어서 사용하는 방법이다. 따라서 컴파일 단계에서는 문제가 안나타날 수 있으나 만약 런타임에서 강제추출로 접근한 변수에 nil이 할당되어 있다면 런타임 에러가 발생할 것이다.

옵셔널 바인딩의 경우 if let 구문, guard let 구문 으로 옵셔널 타입 변수에 대한 에러 핸들링이 가능하도록 해주는 문법 이다. Unwrapping 할 옵셔널 변수에 실제 nil 이 할당되어 있다면 ,else 문을 통해 에러를 처리해 줄 수 있도록 해준다. 이 둘의 차이는 옵셔널 Unwrapping단계를 통해 할당 된 변수의 Scope의 차이가 있다. if let 의 경우 { } 내부에 할당 된 지역변수로만 사용되고 나머지에선 사용할 수 없다. guard let의 경우에는 자신이 위치 한 블록 내에서 전부 사용할 수 있도록 해주는 차이가 있다.

@5anniversary
Copy link
Collaborator

5anniversary commented Nov 27, 2020

옵셔널이란 갑이 있을수도 있고 없을수도있다는 것을 표현해주는 개념!!

*** 보다가 배운것 Objc에서는 옵셔널이 필수였는데 swift에서는 선택적인 개념이 되었다는 것??

var number: Int? = 20 이라고 하면 20으로 선언된 변수이지만 나중에 언제든지 nil로 변경이 될수있다는 점!!

그리고 해당 변수를 사용하기 위해서는 옵셔널 바인딩 처리를 해 사용을 해야한다는 점!!

guard let nonOptionalNumber = number else { return } // number를 nonOptionalNumber로 사용 가능

if let nonOptionalNumber = number {
    // 이 스코프에서 number를 nonOptionalNumber로 사용 가능
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment