次のコードを使用して、単純なモデルクラスと、(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()
}
}
更新: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
}
}
_
それは意味がありません。なぜnilを返すのにこれらのプロパティを初期化する必要があるのですか?
Chris Lattnerによると、これはバグです。彼の言うことは次のとおりです。
これは、リリースノートに記載されているSwift 1.1コンパイラの実装上の制限です。現在、コンパイラはすべてのケースで部分的に初期化されたクラスを破棄することができません。これはバグであり、機能ではなく将来のリリースで修正される予定です。
編集:
Swiftは現在オープンソースであり、 this changelog に従ってスナップショットで修正されていますSwift 2.2
オブジェクトが完全に初期化される前に、フェイル可能またはスローとして宣言された指定クラス初期化子は、それぞれnilを返すか、エラーをスローする可能性があります。
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
}
}
}
制限を回避する別の方法は、クラス関数を使用して初期化を行うことです。その機能を拡張機能に移動することもできます。
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
}
Swift 2.2がリリースされ、イニシャライザに失敗する前にオブジェクトを完全に初期化する必要はなくなりましたが、 https://bugs.Swift.org/browse/SR-704 が修正されました。
私はこれを見つけましたcan Swift 1.2
いくつかの条件があります:
例:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
値型(つまり、構造体または列挙)の失敗可能な初期化子は、その初期化子実装内の任意の時点で初期化の失敗をトリガーできます。
ただし、クラスの場合、フェイル可能イニシャライザは、そのクラスによって導入されたすべての格納済みプロパティが初期値に設定され、初期化子の委任が行われた後にのみ、初期化エラーをトリガーできます。
抜粋:Apple Inc.“The Swiftプログラミング言語。” iBooks 。 https://itun.es/sg/jEUH0.l
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)
}
}