Programing

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

lottogame 2020. 8. 21. 08:23
반응형

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


다음 코드를 사용하여 간단한 모델 클래스를 정의하려고 시도하며 (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()
    }
}

업데이트 : 보내는 사람 스위프트 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
    }
}

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

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

This is an implementation limitation in the swift 1.1 compiler, documented in the release notes. The compiler is currently unable to destroy partially initialized classes in all cases, so it disallows formation of a situation where it would have to. We consider this a bug to be fixed in future releases, not a feature.

Source

EDIT:

So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2

Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.


I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.

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
        }
    }
}

Another way to circumvent the limitation is to work with a class-functions to do the initialisation. You might even want to move that function to an extension:

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
    }
}

Using it would become:

if let user = User.fromDictionary(someDict) {

     // Party hard
}

Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.


I found out this can be done in Swift 1.2

There are some conditions:

  • Required properties should be declared as implicitly unwrapped optionals
  • Assign a value to your required properties exactly once. This value may be nil.
  • Then call super.init() if your class is inheriting from another class.
  • After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.

Example:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

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

A failable initializer for a value type (that is, a structure or enumeration) can trigger an initialization failure at any point within its initializer implementation

For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place.

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


You can use convenience init:

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)
    } 
}

참고URL : https://stackoverflow.com/questions/26495586/best-practice-to-implement-a-failable-initializer-in-swift

반응형