web-dev-qa-db-fra.com

Comment définir des initialiseurs dans une extension de protocole?

protocol Car {
     var wheels : Int { get set}

     init(wheels: Int)

}

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

sur self.wheels = roues, je reçois l'erreur

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

Comment puis-je définir l'initialiseur dans l'extension de protocole?

41
bogen

Comme vous pouvez le voir, cela ne fonctionne pas dans ces circonstances car lors de la compilation, vous devez vous assurer que toutes les propriétés sont initialisées avant d'utiliser la structure/enum/class.

Vous pouvez rendre un autre initialiseur obligatoire afin que le compilateur sache que toutes les propriétés sont initialisées:

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

Depuis Xcode 7.3 beta 1, il fonctionne avec structs comme prévu mais pas avec les classes car si elles ne sont pas final la init(wheels: Int) dans le protocole est un required init et il peut être remplacé, il ne peut donc pas être ajouté via une extension. Solution de contournement (comme le suggère le complice): créez le classfinal.

Une autre solution de contournement (en profondeur; sans final class)

Pour travailler avec des classes sans les rendre définitives, vous pouvez également supprimer l'exigence init(wheels: Int) dans le protocole. Il semble qu'il ne se comporte pas différemment qu'auparavant, mais considérez ce code:

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)

Donc, si vous créez un Car à partir d'un contexte générique où le type sur lequel vous appelez init ne doit être connu que comme Car l'initialiseur d'extension est appelé même si un initialiseur est défini dans HoverCar. Cela se produit uniquement car il n'y a pas d'exigence init(wheels: Int) dans le protocole.

Si vous l'ajoutez, vous avez l'ancien problème avec la déclaration de class comme final mais maintenant il imprime deux fois "HoverCar". Quoi qu'il en soit, le deuxième problème ne se produit probablement jamais, il pourrait donc être une meilleure solution.

Sidenote: Si j'ai fait quelques erreurs (code, langue, grammaire, ...) vous êtes les bienvenus pour me corriger :)

60
Qbyte

@Qbyte est correct.

De plus, vous pouvez jeter un oeil à mon Configurable

En ce que j'ai le protocole 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)
    }
}

Ensuite, pour s'y conformer

extension Robot: Initable { }

J'ai 2 façons, en utilisant final ou implémenter init

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

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

    required init() {

    }
}
5
onmyway133

Ma compréhension est que cela n'est pas possible, car l'extension de protocole ne peut pas savoir quelles propriétés possède la classe ou la structure conforme - et ne peut donc pas garantir qu'elles sont correctement initialisées.

S'il existe des moyens de contourner cela, je suis très intéressé de savoir! :)

5

Peut ne pas être le même mais dans mon cas, au lieu d'utiliser init, j'ai utilisé un func statique pour renvoyer l'objet de la classe.

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

}

Ensuite, pour créer l'objet, je fais quelque chose comme:

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

Je sais que ce n'est pas la meilleure chose qui soit, mais c'est la meilleure solution que j'ai pu trouver pour ne pas coupler le modèle commercial avec la couche de données.

REMARQUE: je suis paresseux et j'ai tout codé directement dans le commentaire, donc si quelque chose ne fonctionne pas, faites le moi savoir.

2
Mijail