web-dev-qa-db-ja.com

プロトコル拡張で初期化子を定義する方法は?

protocol Car {
     var wheels : Int { get set}

     init(wheels: Int)

}

extension Car {
    init(wheels: Int) {
        self.wheels = wheels
    }
}

self.wheels =ホイールでエラーが発生します

Error: variable 'self' passed by reference before being initialized

プロトコル拡張で初期化子を定義するにはどうすればよいですか?

41
bogen

ご覧のとおり、これらの状況ではこれは機能しません。コンパイル時に、struct/enum/classを使用する前にすべてのプロパティを初期化する必要があるためです。

すべてのプロパティが初期化されていることをコンパイラが認識するように、別の初期化子を要件にすることができます。

_protocol Car {
    var wheels : Int { get set }
    // make another initializer
    // (which you probably don't want to provide a default implementation)
    // a protocol requirement. Care about recursive initializer calls :)
    init()
    init(wheels: Int)

}

extension Car {
    // now you can provide a default implementation
    init(wheels: Int) {
        self.init()
        self.wheels = wheels
    }
}

// example usage

// mark as final
final class HoverCar: Car {
    var wheels = 0
    init() {}
}

let drivableHoverCar = HoverCar(wheels: 4)
drivableHoverCar.wheels // 4
_

Xcode 7.3 beta 1の時点では、期待どおりstructsで動作しますが、finalでない場合、プロトコルのinit(wheels: Int)は_required init_であるため、クラスでは動作しません。また、オーバーライドできるため、拡張機能を介して追加することはできません。回避策(コンパイラーが示唆するとおり):classfinalを作成します。

別の回避策(詳細、_final class_なし)

クラスを最終的にせずに作業するには、プロトコルでinit(wheels: Int)要件を削除することもできます。以前と同じように動作するようですが、このコードを検討してください:

_protocol Car {
    var wheels : Int { get set }
    init()
    // there is no   init(wheels: Int)
}

extension Car {
    init(wheels: Int) {
        self.init()
        print("Extension")
        self.wheels = wheels
    }
}

class HoverCar: Car {
    var wheels = 0
    required init() {}
    init(wheels: Int) {
        print("HoverCar")
        self.wheels = wheels
    }
}

// prints "HoverCar"
let drivableHoverCar = HoverCar(wheels: 4)

func makeNewCarFromCar<T: Car>(car: T) -> T {
    return T(wheels: car.wheels)
}

// prints "Extension"
makeNewCarFromCar(drivableHoverCar)
_

したがって、Carを呼び出す型がinitとしてのみ知られる汎用コンテキストからCarを作成すると、初期化子が呼び出されても、拡張初期化子が呼び出されます。 HoverCarで定義されています。これは、プロトコルにinit(wheels: Int)要件がないためにのみ発生します。

それを追加すると、classfinalとして宣言するという前の問題がありますが、今では2回「HoverCar」と表示されます。いずれにしても、2番目の問題はおそらく発生しないので、より良い解決策になる可能性があります。

サイドノート:間違い(コード、言語、文法など)を犯した場合は、私を修正してください:)

60
Qbyte

@Qbyteは正しいです。

さらに、あなたは私の Configurable を見ることができます

その中で私はInitableプロトコルを持っています

public protocol Initable {
    // To make init in protocol extension work
    init()
}

public extension Initable {
    public init(@noescape block: Self -> Void) {
        self.init()
        block(self)
    }
}

その後、それに適合するために

extension Robot: Initable { }

finalを使用するか、initを実装する2つの方法があります

final class Robot {
    var name: String?
    var cute = false
}

class Robot {
    var name: String?
    var cute = false

    required init() {

    }
}
5
onmyway133

プロトコル拡張は適合クラスまたは構造体がどのプロパティを持っているかを知ることができないため、これらが正しく初期化されることを保証できないため、私の理解ではこれは不可能です。

これを回避する方法がある場合、私は知ることに非常に興味があります! :)

同じではないかもしれませんが、私の場合は、initを使用する代わりに、静的funcを使用してクラスのオブジェクトを返します。

protocol Serializable {
   static func object(fromJSON json:JSON) -> AnyObject?
}

class User {
    let name:String

    init(name:String) {
        self.name = name
    }
}

extension User:Serializable {

    static func object(fromJSON json:JSON) -> AnyObject? {
        guard let name = json["name"] else {
            return nil
        }

        return User(name:name)
     }

}

次に、オブジェクトを作成するには、次のようにします。

let user = User.object(fromJSON:json) as? User

私はそれが今までで最高のものではないことを知っていますが、ビジネスモデルをデータレイヤーと結び付けないために見つけることができる最高のソリューションです。

注:私は怠け者であり、コメント内のすべてを直接コーディングしたので、うまくいかない場合はお知らせください。

2
Mijail