“내재적으로 래핑되지 않은 선택 사항”을 만드는 이유는 값이 있다는 것을 의미하기 때문입니다.


493

왜 일반 변수 나 상수를 만드는 것보다 "암시 적으로 래핑되지 않은 옵션"을 만들겠습니까? 그것이 성공적으로 풀릴 수 있다는 것을 안다면 왜 처음에 옵션을 작성합니까? 예를 들어, 왜 이럴까요 :

let someString: String! = "this is the string"

다음보다 더 유용 할 것입니다.

let someString: String = "this is the string"

"선택 사항이 상수 또는 변수에 '값이 없음'이 허용됨을 표시하지만"때때로 프로그램 구조에서 선택 사항이 항상 해당 값을 먼저 설정 한 후에 값이 있음을 알 수있는 경우 "는 무엇입니까? 처음에는 선택 사항입니까? 옵션이 항상 값을 가질 것이라는 것을 알고 있다면 선택이 아닌가?

답변:


127

객체가 구성 및 구성되는 동안 nil 속성을 가질 수 있지만 나중에 변경 불가능하고 nil이 아닌 객체의 경우를 고려하십시오 (NSImage는 종종 이런 식으로 처리되지만 경우에 따라 변경하는 것이 유용합니다). 암시 적으로 래핑되지 않은 옵션은 상대적으로 낮은 안전 손실로 코드를 정리할 것입니다 (보증이 보장되는 한 안전 할 것입니다).

(편집) 분명히하기 위해 : 일반 옵션은 거의 항상 선호됩니다.


459

Implicitly Unwrapped Optionals의 사용 사례를 설명하기 전에 Swift에있는 Optionals 및 Implicitly Unwrapped Optionals가 무엇인지 이해해야합니다. 그렇지 않은 경우 먼저 옵션에 대한 내 기사를 읽는 것이 좋습니다.

암시 적으로 래핑되지 않은 옵션을 사용하는 경우

하나는 암시 적으로 래핑되지 않은 옵션을 만드는 데는 두 가지 주요 이유가 있습니다. nilSwift 컴파일러는 항상 선택적으로 옵션을 풀어야하기 때문에 액세스 할 수없는 변수를 정의하는 것과 관련이 있습니다 .

1. 초기화 중에 정의 할 수없는 상수

초기화가 완료 될 때까지 모든 멤버 상수에는 값이 있어야합니다. 때로는 초기화하는 동안 상수를 올바른 값으로 초기화 할 수 없지만 액세스하기 전에 값을 가질 수는 있습니다.

Optional 변수를 사용하면 Optional이 자동으로 초기화되고 nil결국에는 포함 할 값이 변경되지 않기 때문에이 문제를 해결할 수 있습니다. 그러나 확실히 아는 변수가 끊임없이 풀리는 것은 고통이 될 수 있습니다. 암시 적으로 래핑되지 않은 선택 사항은 선택 사항과 동일한 이점을 제공하며 추가 혜택을 통해 어디에서나 명시 적으로 래핑 할 필요가 없습니다.

이에 대한 좋은 예는 뷰가로드 될 때까지 멤버 변수를 UIView 서브 클래스에서 초기화 할 수없는 경우입니다.

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

여기서는 뷰가로드 될 때까지 버튼의 원래 너비를 계산할 수 없지만 뷰의 awakeFromNib다른 메소드 (초기화 제외) 전에 호출 될 것임을 알고 있습니다. 클래스 전체에서 값이 명시 적으로 래핑되지 않도록 강제하는 대신 암시 적으로 래핑되지 않은 옵션으로 선언 할 수 있습니다.

2. 앱이 변수로부터 복구 할 수없는 경우 nil

이는 극히 드물지만 변수에 nil액세스 할 때 앱을 계속 실행할 수 없으면 테스트를 방해하는 데 시간이 낭비됩니다 nil. 일반적으로 앱이 계속 실행 되려면 반드시 참이어야하는 조건이있는 경우을 사용합니다 assert. 암시 적으로 래핑되지 않은 옵션에는 nil이 내장되어 있습니다. 그럼에도 불구하고, 선택 사항을 풀고 더 설명이없는 주장을 사용하는 것이 종종 좋습니다.

암시 적으로 래핑되지 않은 옵션을 사용하지 않는 경우

1. 지연 계산 된 멤버 변수

때로는 nil이 아니어야하는 멤버 변수가 있지만 초기화 중에 올바른 값으로 설정할 수없는 경우가 있습니다. 한 가지 해결책은 암시 적으로 래핑되지 않은 옵션을 사용하는 것이지만 더 좋은 방법은 지연 변수를 사용하는 것입니다.

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

이제 멤버 변수 contents는 처음 액세스 될 때까지 초기화되지 않습니다. 이를 통해 클래스는 초기 값을 계산하기 전에 올바른 상태로 들어갈 수 있습니다.

참고 : 이것은 위에서 # 1과 모순되는 것처럼 보일 수 있습니다. 그러나 중요한 차이점이 있습니다. buttonOriginalWidth위는 속성에 액세스하기 전에 버튼 폭 변화하는 사람을 방지 할 수있는 viewDidLoad시 설정해야합니다.

2. 그 밖의 모든 곳

대부분의 경우, 잘못 사용하는 경우에 액세스 할 때 전체 앱이 중단되므로 암시 적으로 래핑되지 않은 선택 사항을 피해야합니다 nil. 변수가 nil 일 수 있는지 확실하지 않은 경우 항상 기본 옵션을 사용하는 것이 기본값입니다. 절대로 절대 변수가 nil되지 않는 것은 크게 아프지 않습니다.


4
이 답변은 베타 5 용으로 업데이트해야합니다 if someOptional. 더 이상 사용할 수 없습니다 .
산타 클로스

2
@SantaClaus hasValue는 옵션에 바로 정의되어 있습니다. 나는 hasValue그것의 의미론을 선호한다 != nil. nil다른 언어를 사용하지 않은 새로운 프로그래머에게는 훨씬 이해하기 쉽다고 생각합니다 . hasValue보다 논리적 nil입니다.
drewag

2
hasValue베타 6에서 가져온 것처럼 보입니다 . 애쉬가 다시 넣었지만 ... github.com/AshFurrow/hasValue
Chris Wagner

1
@newacct Objc 초기화 프로그램의 반환 유형에 대해서는 암시 적으로 래핑되지 않은 옵션입니다. "옵션이 아닌"을 사용하기 위해 설명한 동작은 암시 적으로 언 래핑 된 옵션이하는 것과 정확히 같습니다 (액세스 할 때까지 실패하지 않음). 강제로 래핑 해제하여 프로그램이 더 일찍 실패하게하는 것과 관련하여, 비 선택적 옵션을 사용하는 것이 바람직하지만 항상 가능한 것은 아닙니다.
drewag

1
@ 파일 번호 어쨌든 Objective-C에서 포인터로 표시 될 것입니다 (선택적이거나 암시 적으로 래핑되지 않았거나 선택 사항이 아닌 경우).
drewag

56

암시 적으로 래핑되지 않은 선택 사항은 실제로 커버 아래에서 선택 사항이 필요할 때 속성을 선택 사항으로 표시하는 데 유용합니다. 이것은 종종 서로에 대한 참조가 필요한 두 개의 관련 객체 사이에 "매듭을 묶는"데 필요합니다. 참조가 실제로 선택 사항 이 아닌 경우에는 의미가 있지만 쌍이 초기화되는 동안 이들 중 하나가 nil이어야합니다.

예를 들면 다음과 같습니다.

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

모든 B인스턴스는 항상 유효한 myBuddyA참조를 가져야 하므로 사용자 가 인스턴스 를 선택 사항으로 취급하고 싶지는 않지만 참조 B하기 전에를 구성 할 수 있도록 선택적이어야 A합니다.

하나! 이러한 종류의 상호 참조 요구 사항은 종종 긴밀한 결합과 열악한 설계를 나타냅니다. 암시 적으로 래핑되지 않은 옵션에 의존하는 경우 상호 의존성을 제거하기 위해 리팩토링을 고려해야합니다.


7
이 언어 기능을 만든 이유 중 하나는@IBOutlet
Jiaaro

11
"하늘"경고에 +1 항상 사실이 아닐 수도 있지만, 반드시 살펴 봐야 할 부분입니다.
JMD

4
여전히 A와 B 사이에 강력한 참조주기가 있습니다. 암시 적으로 래핑되지 않은 옵션 은 약한 참조를 생성 하지 마십시오 . 여전히 myByddyA 또는 myBuddyB를 약한 것으로 선언해야합니다 (아마도 myBuddyA)
drewag

6
이 답변이 왜 잘못되고 위험에 오해를 일으키는 지 더 명확하게하기 위해 : 암시 적으로 래핑되지 않은 옵션은 메모리 관리와 전혀 관련이 없으며 유지주기를 막지 않습니다 . 그러나 암시 적으로 래핑되지 않은 옵션은 양방향 참조 설정에 대해 설명 된 상황에서 여전히 유용합니다. 따라서 weak선언을 추가하고 "강한 유지주기를 만들지 않고"제거
drewag

1
@drewag : 맞습니다. 유지주기를 제거하기 위해 답변을 편집했습니다. 나는 역 참조를 약하게 만들려고했지만 그것이 내 마음을 미끄러 뜨렸다 고 생각한다.
n8gray

37

암시 적으로 래핑되지 않은 옵션은 기존 Cocoa 프레임 워크 및 해당 규칙과 상호 운용해야하는 하이브리드 환경에서보다 쾌적한 작업을 수행하는 동시에 실질적인 타협으로 Swift 컴파일러에 의해 시행되는 널 포인터없이보다 안전한 프로그래밍 패러다임으로 단계적으로 마이그레이션 할 수 있습니다.

스위프트 북 의 기본 장에서 암시 적으로 래핑되지 않은 옵션 섹션 은 다음과 같이 말합니다.

암시 적으로 래핑되지 않은 옵션은 옵션이 처음 정의 된 직후에 옵션의 값이 존재하는 것으로 확인 된 후 그 이후의 모든 시점에 존재한다고 가정 할 수있는 경우에 유용합니다. Swift에서 암시 적으로 래핑되지 않은 선택적 옵션의 기본 사용은 클래스 초기화 중입니다 ( 소유되지 않은 참조 및 암시 적으로 래핑되지 않은 선택적 속성)에 설명되어 있습니다.

암시 적으로 래핑되지 않은 옵션은 옵션을 사용할 때마다 자동으로 래핑 해제 할 수있는 권한을 부여하는 것으로 생각할 수 있습니다. 옵션을 사용할 때마다 옵션 이름 뒤에 느낌표를 표시하지 않고 선언 할 때 옵션 유형 뒤에 느낌표를 표시합니다.

이것은 nil 사용 속성이 사용 규칙을 통해 설정되고 클래스 초기화 중에 컴파일러로 시행 할 수없는 사용 사례로 귀결 됩니다. 예를 들어, UIViewControllerNIB 또는 스토리 보드에서 초기화되는 속성 (초기화는 별도의 단계로 나뉘지만 그 이후에는 viewDidLoad()일반적으로 속성이 존재한다고 가정 할 수 있음). 그렇지 않으면 컴파일러를 만족시키기 위해 코드의 주요 목적을 모호하게하기 위해 강제 언 래핑 , 선택적 바인딩 또는 선택적 체인 을 사용해야했습니다 .

Swift 책의 위 부분은 Automatic Reference Counting 장도 참조하십시오 .

그러나 두 속성 모두 항상 값을 가져야하며 nil초기화가 완료된 후에는 두 속성이 없어야하는 세 번째 시나리오가 있습니다 . 이 시나리오에서는 한 클래스의 소유되지 않은 속성을 다른 클래스의 암시 적으로 래핑되지 않은 선택적 속성과 결합하는 것이 좋습니다.

이를 통해 초기화가 완료되면 참조주기를 피하면서 두 속성 모두에 직접 액세스 할 수 있습니다 (선택적 래핑 해제없이).

이것은 가비지 수집 언어가 아닌 단점으로 귀결됩니다. 따라서 유지주기의 중단은 프로그래머로서 당신에게 있으며 암묵적으로 래핑되지 않은 옵션은이 단점을 숨기는 도구입니다.

"코드에서 암시 적으로 래핑되지 않은 옵션을 언제 사용해야합니까?" 질문. 응용 프로그램 개발자는 대부분 Objective-C로 작성된 라이브러리의 메서드 서명에서 옵션 유형을 표현할 수있는 기능이 없습니다.

에서 코코아와 오브젝티브 C, 섹션 스위프트를 사용 전무 작업 :

Objective-C는 객체가 0이 아닌 것을 보증하지 않으므로 Swift는 가져온 Objective-C API에서 인수 유형 및 반환 유형의 모든 클래스를 선택적으로 만듭니다. Objective-C 오브젝트를 사용하기 전에 해당 오브젝트가 누락되지 않았는지 확인해야합니다.

경우에 따라서는 수도 절대적으로 오브젝티브 C의 메서드 나 속성은 결코 반환 없도록 nil객체 참조. 이 특별한 시나리오에서 객체를보다 편리하게 사용할 수 있도록 Swift는 객체 유형을 암시 적으로 래핑되지 않은 옵션 으로 가져옵니다 . 암시 적으로 랩핑되지 않은 선택적 유형에는 선택적 유형의 모든 안전 기능이 포함됩니다. 또한 확인하지 않고 직접 값에 액세스 할 수 있습니다nil또는 직접 포장을 푸십시오. 안전하게 래핑 해제하지 않고 이러한 종류의 옵션 유형의 값에 액세스하면 암시 적으로 래핑되지 않은 옵션이 값이 없는지 확인합니다. 값이 없으면 런타임 오류가 발생합니다. 결과적으로 값을 찾을 수없는 경우가 아니면 암시 적으로 래핑되지 않은 옵션을 직접 확인하고 래핑 해제해야합니다.

... 그리고 저쪽에 누워 용


이 철저한 답변에 감사드립니다. 암시 적으로 래핑되지 않은 옵션을 사용할 때와 표준 변수로 충분할 때에 대한 빠른 체크리스트를 생각할 수 있습니까?
Hairgami_Master

@Hairgami_Master 나는 목록과 구체적인 예제와 함께 내 자신의 답변을 추가
drewag

18

한 줄 (또는 여러 줄) 간단한 예제는 옵션의 동작을 잘 다루지 않습니다. 예, 변수를 선언하고 바로 값을 제공하면 옵션에 아무런 의미가 없습니다.

지금까지 내가 본 가장 좋은 사례는 객체 초기화 후에 발생하는 설정이며 그 다음에 "보증 된"사용을 통해 해당 설정을 따릅니다 (예 : 뷰 컨트롤러).

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

printSize뷰가로드 된 후 호출 될 것임을 알고 있습니다. 뷰 내부의 컨트롤에 연결된 액션 메서드이므로 다른 방법으로 호출하지 않아야합니다. 그래서 우리는 선택적으로 확인 / 바인딩을 할 수 !있습니다. Swift는 (적어도 Apple이 정지 문제를 해결할 때까지) 그 보증을 인식 할 수 없으므로 컴파일러에 존재한다고 알려주십시오.

그러나 이것은 타입 안전성을 어느 정도 떨어 뜨립니다. 암시 적으로 래핑되지 않은 옵션이있는 곳은 "보증인"이 항상 유지되지 않는 경우 앱이 중단 될 수있는 곳이므로 드물게 사용하는 기능입니다. 게다가, 사용! 항상 사용하면 소리를 지르는 것처럼 들리며 아무도 그것을 좋아하지 않습니다.


왜 screenSize를 CGSize (높이 : 0, 너비 : 0)로 초기화하고 변수에 액세스 할 때마다 변수를 외치지 않아도되는 이유는 무엇입니까?
마틴 고든

CGSizeZero실제 사용에서 좋은 센티넬 값이 될 수 있으므로 크기가 가장 좋은 예 는 아닐 수 있습니다. 그러나 실제로 0이 될 수있는 펜촉에서로드 된 크기가 있다면 어떨까요? 그런 다음 CGSizeZero센티넬로 사용 한다고 설정되지 않은 값과 0으로 설정된 값을 구별하는 데 도움이되지 않습니다. 또한 이것은 nib (또는 이후의 다른 곳)에서로드 된 다른 유형 ( init문자열, 하위 뷰 참조 등) 에도 동일하게 적용됩니다 .
rickster

2
기능적 언어에서 선택 사항의 일부는 센티넬 값을 가지지 않는 것입니다. 당신은 가치가 있거나 그렇지 않습니다. 결 측값을 나타내는 값이있는 경우는 없어야합니다.
웨인 태너

4
OP의 질문을 오해했다고 생각합니다. OP는 선택 사항에 대한 일반적인 경우, 특히 암시 적으로 래핑되지 않은 선택 사항의 필요성 / 사용에 대해 묻지 않습니다 (즉 let foo? = 42, 아닙니다 let foo! = 42). 이것은 그것을 다루지 않습니다. (이것은 선택 사항에 대한 관련 답변 일 수 있지만, 다른 동물 / 관련 동물 인 암시 적으로 래핑되지 않은 선택 사항에 대해서는 아닙니다.)
JMD

15

Apple은 Swift Programming Language- > 에서 훌륭한 예를 보여줍니다. Automatic Reference Counting- > 클래스 인스턴스 간의 강력한 참조 사이클 해결 -> 소유되지 않은 참조 및 암시 적으로 래핑되지 않은 선택적 속성

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

의 초기화 프로그램 City은의 초기화 프로그램 내에서 호출됩니다 Country. 그러나 2 단계 초기화에 설명 된대로 새 인스턴스가 완전히 초기화 될 때까지 의 초기화 프로그램 이 초기화 프로그램에 Country전달 될 수 없습니다 .selfCityCountry

이 요구 사항에 대처하기 위해 capitalCity속성을 Country암시 적으로 래핑되지 않은 선택적 속성으로 선언합니다 .


이 답변에 언급 된 자습서는 여기에 있습니다 .
Franklin Yu

3

강제적 래핑 해제에 대한 이론적 근거를 먼저 살펴보면 암묵적 옵션의 이론적 근거를 쉽게 설명 할 수 있습니다.

!를 사용하여 선택적 (암시 적 또는 비 적합) 강제 래핑 해제 operator는 코드에 버그가 없으며 옵션에 이미 래핑되지 않은 값이 있다는 것을 의미합니다. 없이! 연산자, 당신은 아마도 선택적 바인딩으로 주장 할 것입니다 :

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

그다지 좋지 않은

println("\(value!)")

이제 암시 적 옵션을 사용하면 가능한 모든 흐름에서 항상 줄 바꿈 할 때 항상 값을 가질 것으로 예상되는 옵션을 갖는 것을 표현할 수 있습니다 . 따라서 쓰기 요구 사항을 완화하여 더 많은 도움을 줄 수 있습니다. 매번 포장을 풀고 흐름에 대한 가정이 잘못 된 경우 런타임이 여전히 오류가 발생하는지 확인하십시오.


1
@newacct : 코드를 통한 모든 가능한 흐름 에서 non-nil은 할당 해제를 통한 부모 (class / struct) 초기화의 non-nil과 동일하지 않습니다. Interface Builder는 전형적인 예입니다 (그러나 다른 지연 초기화 패턴이 많이 있습니다). 클래스가 펜촉에서만 사용되는 경우 콘센트 변수가 설정되지는 않지만 init구현되지 않을 수도 있습니다. / 후에 설정되도록 다시 보장하십시오 . awakeFromNibviewDidLoad
rickster

3

확실하지 않은 경우nil , 암시 적 래핑되지 않은 선택 사항 대신 옵션에서 값이 반환되면 선택 사항 에서 해당 값을 직접 포착하는 데 사용됩니다.

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

그래서 이것은 사용의 차이점입니다.

let someString : String! let someString : String


1
이것은 OP의 질문에 대답하지 않습니다. OP 암시 적으로 래핑되지 않은 옵션이 무엇인지 알고 있습니다.
Franklin Yu

0

나는 Optional많은 초보자를 혼란스럽게하는이 구조의 나쁜 이름 이라고 생각 합니다.

다른 언어 (예 : 코 틀린 및 C #)는이 용어를 사용 Nullable하므로이를 이해하기가 훨씬 쉽습니다.

Nullable이 유형의 변수에 널값을 지정할 수 있음을 의미합니다. 따라서 인 경우 Nullable<SomeClassType>null을 할당 할 수 있으며 그냥 인 SomeClassType경우 할 수 없습니다. 바로 Swift가 작동하는 방식입니다.

왜 사용합니까? 글쎄, 때로는 null이 필요할 때가 있습니다. 예를 들어, 클래스에 필드를 갖고 싶지만 해당 클래스의 인스턴스를 생성 할 때 어떤 필드에도 할당 할 수 없지만 나중에는 필드를 할당 할 수 없습니다. 사람들이 이미 여기에 제공했기 때문에 예를 들지 않겠습니다. 나는 단지 내 2 센트를 제공하기 위해 이것을 쓰고 있습니다.

Btw, Kotlin 및 C #과 같은 다른 언어로 이것이 어떻게 작동하는지 살펴 보시기 바랍니다.

Kotlin에서이 기능을 설명하는 링크는 다음과 같습니다. https://kotlinlang.org/docs/reference/null-safety.html

Java 및 Scala와 같은 다른 언어에는 Optionals가 있지만 OptionalSwift에서는 s와 다르게 작동합니다. Java 및 Scala의 유형은 모두 기본적으로 널 입력 가능하기 때문입니다.

Nullable대체로이 기능은 Swift에서 이름이 지정 되어 있어야한다고 생각합니다 Optional.


0

Implicitly Unwrapped OptionalOptional프로그래머가 변수를 풀지 않도록 하는 구문 설탕입니다 . 그것은 동안 초기화 할 수없는 변수에 사용할 수 있습니다 two-phase initialization process의미 비 무기 호. 이 변수는 비-닐 (non-nil) 로 작동 하지만 실제로는 선택적 변수입니다. 좋은 예는 다음과 같습니다. Interface Builder의 콘센트

Optional 보통 바람직하다

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.