web-dev-qa-db-ja.com

Swiftで失敗可能な初期化子を実装するためのベストプラクティス

次のコードを使用して、単純なモデルクラスと、(json-)ディクショナリをパラメーターとして受け取る失敗可能な初期化子を定義しようとします。ユーザー名が元のjsonで定義されていない場合、初期化子はnilを返す必要があります。

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()
    }
}
97
Kai Huppmann

更新:Swift 2.2変更ログ (2016年3月21日リリース)から:

オブジェクトが完全に初期化される前に、フェイル可能またはスローとして宣言された指定クラス初期化子は、それぞれnilを返すか、エラーをスローするようになりました。


Swift 2.1以前の場合:

Appleのドキュメント(およびコンパイラエラー)によると、クラスは失敗したイニシャライザーからnilを返す前に、格納されているすべてのプロパティを初期化する必要があります。

ただし、クラスの場合、フェイル可能イニシャライザは、そのクラスによって導入されたすべての格納済みプロパティが初期値に設定され、初期化子の委任が行われた後にのみ、初期化エラーをトリガーできます。

注:実際には、クラスではなく、構造体と列挙型で正常に動作します。

初期化子が失敗する前に初期化できない保存されたプロパティを処理するための推奨される方法は、暗黙的にラップされていないオプションとしてそれらを宣言することです。

ドキュメントの例:

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

上記の例では、Productクラスのnameプロパティは、暗黙的にラップされていないオプションの文字列型(String!)を持つものとして定義されています。これはオプションのタイプであるため、これは、初期化中に特定の値が割り当てられる前に、nameプロパティのデフォルト値がnilであることを意味します。このデフォルト値のnilは、Productクラスによって導入されたすべてのプロパティに有効な初期値があることを意味します。その結果、初期化子内の名前プロパティに特定の値を割り当てる前に、空の文字列が渡された場合、Productの失敗可能な初期化子は初期化子の開始時に初期化失敗をトリガーできます。

ただし、あなたの場合、userNameを_String!_として定義するだけでは、基本クラスNSObjectのプロパティの初期化について心配する必要があるため、コンパイルエラーは修正されません。幸いなことに、userNameを_String!_として定義すると、NSObject基本クラスを初期化し、コンパイルを修正する_return nil_の前に、実際にsuper.init()を呼び出すことができます。エラー。

_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
    }
}
_
69
Mike S

それは意味がありません。なぜnilを返すのにこれらのプロパティを初期化する必要があるのですか?

Chris Lattnerによると、これはバグです。彼の言うことは次のとおりです。

これは、リリースノートに記載されているSwift 1.1コンパイラの実装上の制限です。現在、コンパイラはすべてのケースで部分的に初期化されたクラスを破棄することができません。これはバグであり、機能ではなく将来のリリースで修正される予定です。

ソース

編集:

Swiftは現在オープンソースであり、 this changelog に従ってスナップショットで修正されていますSwift 2.2

オブジェクトが完全に初期化される前に、フェイル可能またはスローとして宣言された指定クラス初期化子は、それぞれnilを返すか、エラーをスローする可能性があります。

131
mustafa

Mike Sの答えはAppleの推奨だと認めますが、ベストプラクティスだとは思いません。強力な型システムのポイントは、ランタイムエラーをコンパイル時に移動することです。この「解決策」はその目的に反します。私見では、先に進み、ユーザー名を""に初期化してから、super.init()の後にチェックする方が良いでしょう。空白のuserNameが許可されている場合は、フラグを設定します。

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
        }
    }
}
7
Daniel T.

制限を回避する別の方法は、クラス関数を使用して初期化を行うことです。その機能を拡張機能に移動することもできます。

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
}
6
Kevin R

Swift 2.2がリリースされ、イニシャライザに失敗する前にオブジェクトを完全に初期化する必要はなくなりましたが、 https://bugs.Swift.org/browse/SR-704 が修正されました。

3
sssilver

私はこれを見つけましたcan Swift 1.2

いくつかの条件があります:

  • 必須プロパティは、暗黙的にラップされていないオプションとして宣言する必要があります
  • 必要なプロパティに値を1回だけ割り当てます。この値はnilになる場合があります。
  • クラスが別のクラスを継承している場合は、super.init()を呼び出します。
  • Afterすべての必要なプロパティに値が割り当てられています。それらの値が期待どおりかどうかを確認します。そうでない場合は、nilを返します。

例:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

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

値型(つまり、構造体または列挙)の失敗可能な初期化子は、その初期化子実装内の任意の時点で初期化の失敗をトリガーできます。

ただし、クラスの場合、フェイル可能イニシャライザは、そのクラスによって導入されたすべての格納済みプロパティが初期値に設定され、初期化子の委任が行われた後にのみ、初期化エラーをトリガーできます。

抜粋:Apple Inc.“The Swiftプログラミング言語。” iBooks 。 https://itun.es/sg/jEUH0.l

0
user1046037

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