Swift에서 실패 가능한 이니셜 라이저를 구현하는 모범 사례


100

다음 코드를 사용하여 간단한 모델 클래스를 정의하려고 시도하며 (json-) 사전을 매개 변수로 사용하는 실패 가능한 이니셜 라이저입니다. nil사용자 이름이 원래 json에 정의되어 있지 않으면 초기화 프로그램이 반환 해야합니다.

1. 코드가 컴파일되지 않는 이유는 무엇입니까? 오류 메시지는 다음과 같습니다.

클래스 인스턴스의 모든 저장된 속성은 초기화 프로그램에서 nil을 반환하기 전에 초기화되어야합니다.

말이 안 돼. 반환하려고 할 때 이러한 속성을 초기화해야하는 이유는 무엇 nil입니까?

2. 내 접근 방식이 올바른지 아니면 내 목표를 달성하기위한 다른 아이디어 나 일반적인 패턴이 있습니까?

class User: NSObject {

    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        if let value: String = dictionary["user_name"] as? String {
            userName = value
        }
        else {
           return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()
    }
}

비슷한 문제가 있었는데, 각 사전 값이 예상되어야한다고 결론을 내렸으므로 값을 강제로 풀었습니다. 속성이 없으면 버그를 잡을 수 있습니다. 또한 canSetCalculableProperties이니셜 라이저가 즉석에서 생성 할 수 있거나 생성 할 수없는 속성을 계산할 수 있도록 부울 매개 변수를 추가했습니다 . 예를 들어 dateCreated키가없고 canSetCalculableProperties매개 변수가 true 이기 때문에 즉시 속성을 설정할 수있는 경우 현재 날짜로 설정합니다.
Adam Carter

답변:


71

업데이트 : 보내는 사람 스위프트 2.2 변경 기록 (2016 년 3월 21일 출시) :

실패하거나 throwing으로 선언 된 지정된 클래스 이니셜 라이저는 이제 개체가 완전히 초기화되기 전에 각각 nil을 반환하거나 오류를 throw 할 수 있습니다.


Swift 2.1 및 이전 버전 :

Apple의 설명서 (및 컴파일러 오류)에 따르면 클래스는 nil실패 가능한 초기화 프로그램에서 반환하기 전에 저장된 모든 속성을 초기화해야합니다 .

그러나 클래스의 경우 실패 가능한 이니셜 라이저는 해당 클래스에 의해 도입 된 모든 저장된 속성이 초기 값으로 설정되고 모든 이니셜 라이저 위임이 발생한 후에 만 ​​초기화 실패를 트리거 할 수 있습니다.

노트 : 실제로 클래스가 아닌 구조 및 열거 형에 대해 잘 작동합니다.

이니셜 라이저가 실패하기 전에 초기화 할 수없는 저장된 속성을 처리하는 제안 된 방법은 암시 적으로 언 래핑 된 옵션으로 선언하는 것입니다.

문서의 예 :

class Product {
    let name: String!
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

위의 예에서 Product 클래스의 이름 속성은 암시 적으로 래핑되지 않은 선택적 문자열 유형 (String!)을 갖는 것으로 정의됩니다. 이는 선택적 유형이기 때문에 초기화 중에 특정 값이 할당되기 전에 name 속성의 기본값이 nil이라는 것을 의미합니다. 이 기본값 nil은 Product 클래스에 의해 도입 된 모든 속성에 유효한 초기 값이 있음을 의미합니다. 결과적으로 제품에 대한 실패 가능한 이니셜 라이저는 이니셜 라이저 내의 name 속성에 특정 값을 할당하기 전에 빈 문자열이 전달되는 경우 이니셜 라이저 시작시 초기화 실패를 트리거 할 수 있습니다.

그러나 귀하의 경우에는 기본 클래스의 속성 초기화에 대해 걱정할 필요가 있기 때문에 단순히 userNamea로 정의 해도 String!컴파일 오류가 수정되지 않습니다 NSObject. 운 좋게도으로 userName정의되어 String!있으면 실제로 super.init()먼저 호출 return nil하여 NSObject기본 클래스를 초기화 하고 컴파일 오류를 수정할 수 있습니다.

class User: NSObject {

    let userName: String!
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        super.init()

        if let value = dictionary["user_name"] as? String {
            self.userName = value
        }
        else {
            return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            self.isSuperUser = value
        }

        self.someDetails = dictionary["some_details"] as? Array
    }
}

1
당신은 매우뿐만 아니라 권리뿐만 아니라 잘 설명 감사합니다
카이 Huppmann에게

9
swift1.2에서 문서의 예제에서 "이니셜 라이저에서 nil을 반환하기 전에 클래스 인스턴스의 모든 저장된 속성을 초기화해야합니다."라는 오류가 발생합니다.
jeffrey

2
@jeffrey 맞습니다. 문서 ( Product클래스) 의 예제 는 문서에서 가능하다고 말하더라도 특정 값을 할당하기 전에 초기화 실패를 트리거 할 수 없습니다. 문서가 최신 Swift 버전과 동기화되지 않았습니다. var대신 지금 은 그것을 만드는 것이 좋습니다 let. 출처 : Chris Lattner .
Arjan

1
문서에는이 코드가 약간 다릅니다. 먼저 속성을 설정 한 다음 존재하는지 확인합니다. “Failable Initializers for Classes”,“The Swift Programming Language”를 참조하십시오. ```class Product {let name : String! ? 초기화 (이름 : 문자열) {self.name = name.isEmpty {반환 전무} 경우 이름}}```
미샤 Karpenko

나는 애플 문서에서도 이것을 읽었지만 이것이 왜 필요한지 알지 못한다. 실패는 어쨌든 nil을 반환한다는 것을 의미합니다. 그러면 속성이 초기화되었는지 여부는 무엇이 중요합니까?
Alper

132

말이 안 돼. nil을 반환 할 계획인데 왜 이러한 속성을 초기화해야합니까?

Chris Lattner에 따르면 이것은 버그입니다. 그가 말하는 것은 다음과 같습니다.

이는 릴리스 노트에 문서화 된 신속한 1.1 컴파일러의 구현 제한 사항입니다. 컴파일러는 현재 모든 경우에 부분적으로 초기화 된 클래스를 삭제할 수 없으므로 필요한 상황이 형성되는 것을 허용하지 않습니다. 이 버그는 기능이 아닌 향후 릴리스에서 수정 될 것으로 간주됩니다.

출처

편집하다:

따라서 swift는 이제 오픈 소스 이며이 변경 로그 에 따르면 swift 2.2의 스냅 샷에서 수정되었습니다.

실패하거나 throwing으로 선언 된 지정된 클래스 이니셜 라이저는 이제 개체가 완전히 초기화되기 전에 각각 nil을 반환하거나 오류를 throw 할 수 있습니다.


2
더 이상 필요하지 않은 속성을 초기화하는 아이디어가 그다지 합리적이지 않다는 점을 지적 해 주셔서 감사합니다. 그리고 출처를 공유하는 +1은 Chris Lattner가 나처럼 느껴진다는 것을 증명합니다;).
Kai Huppmann 2014 년

22
FYI : "사실. 이것은 우리가 개선하고 싶은 부분이지만 Swift 1.2를위한 컷을 만들지 않았습니다." -Chris Lattner 10. 2015 년 2 월
dreamlab

14
참고 : Swift 2.0 베타 2에서 이것은 여전히 ​​문제이며 던지는 초기화 프로그램의 문제이기도합니다.
aranasaurus 2015-06-24

7

Mike S의 답변이 Apple의 권장 사항이라는 것을 인정하지만 모범 사례라고 생각하지 않습니다. 강력한 유형 시스템의 요점은 런타임 오류를 컴파일 시간으로 이동하는 것입니다. 이 "솔루션"은 그 목적을 무효화합니다. IMHO, 계속해서 사용자 이름을 초기화 ""한 다음 super.init () 후에 확인하는 것이 좋습니다. 비어있는 userNames가 허용되면 플래그를 설정하십시오.

class User: NSObject {
    let userName: String = ""
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: [String: AnyObject]) {
        if let user_name = dictionary["user_name"] as? String {
            userName = user_name
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()

        if userName.isEmpty {
            return nil
        }
    }
}

감사합니다. 그러나 강력한 유형 시스템의 아이디어가 Mike의 대답에 의해 어떻게 손상되었는지는 알 수 없습니다. 대체로 초기 값이 nil 대신 ""로 설정되어 있다는 차이점이있는 동일한 솔루션을 제시합니다. 또한, 당신이 코드는 사용자 이름으로 ""를 사용 빼앗아 (아주 학구적으로 보일 수도 있지만, 적어도 그것은 JSON / 사전에 설정되어 있지 않은 것을 다르다)
카이 Huppmann

2
검토 결과 귀하가 옳다는 것을 알지만 userName이 상수이기 때문입니다. 변수라면 userName이 나중에 nil로 설정 될 수 있기 때문에 허용되는 대답은 내 대답보다 나쁠 것입니다.
Daniel T.

나는이 대답을 좋아한다. @KaiHuppmann, 빈 사용자 이름을 허용하려면 간단한 Bool 요구 사항 ReturnNil을 가질 수도 있습니다. 사전에 값이 없으면 needsReturnNil을 true로 설정하고 userName을 무엇이든 설정하십시오. super.init () 후 needsReturnNil을 확인하고 필요한 경우 nil을 반환합니다.
Richard Venable

6

제한을 피하는 또 다른 방법은 초기화를 수행하기 위해 클래스 함수로 작업하는 것입니다. 해당 기능을 확장으로 옮기고 싶을 수도 있습니다.

class User: NSObject {

    let username: String
    let isSuperUser: Bool
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {

         self.userName = userName
         self.isSuperUser = isSuperUser
         self.someDetails = someDetails

         super.init()
    }
}

extension User {

    class func fromDictionary(dictionary: NSDictionary) -> User? {

        if let username: String = dictionary["user_name"] as? String {

            let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
            let someDetails = dictionary["some_details"] as? [String]

            return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
        }

        return nil
    }
}

그것을 사용하면 다음과 같습니다.

if let user = User.fromDictionary(someDict) {

     // Party hard
}

1
나는 이것을 좋아한다; 나는 생성자가 원하는 것에 대해 투명하고 사전에 전달하는 것이 매우 불투명 한 것을 선호합니다.
Ben Leggiero 2015


1

나는 이것이 Swift 1.2에서 할 수 있다는 것을 알았습니다.

몇 가지 조건이 있습니다.

  • 필수 속성은 암시 적으로 언 래핑 된 옵션으로 선언되어야합니다.
  • 필수 속성에 정확히 한 번 값을 할당합니다. 이 값은 nil 일 수 있습니다.
  • 그런 다음 클래스가 다른 클래스에서 상속되는 경우 super.init ()를 호출하십시오.
  • 모든 필수 속성에 값이 할당 된 해당 값이 예상과 같은지 확인합니다. 그렇지 않은 경우 nil을 반환합니다.

예:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}

0

값 유형 (즉, 구조 또는 열거 형)에 대한 실패 가능한 이니셜 라이저는 이니셜 라이저 구현 내의 어느 지점에서나 초기화 실패를 트리거 할 수 있습니다.

그러나 클래스의 경우 실패 가능한 이니셜 라이저는 해당 클래스에 의해 도입 된 모든 저장된 속성이 초기 값으로 설정되고 모든 이니셜 라이저 위임이 발생한 후에 만 ​​초기화 실패를 트리거 할 수 있습니다.

발췌 : Apple Inc.“ The Swift Programming Language. ”iBooks. https://itun.es/sg/jEUH0.l


0

편리한 초기화 를 사용할 수 있습니다 .

class User: NSObject {
    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
        self.userName = userName
        self.isSuperUser = isSuperUser
        self.someDetails = someDetails
    }     

    convenience init? (dict: NSDictionary) {            
       guard let userName = dictionary["user_name"] as? String else { return nil }
       guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
       guard let someDetails = dictionary["some_details"] as? [String] else { return nil }

       self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
    } 
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.