Как определить инициализаторы в расширении протокола?

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

Как определить инициализатор в расширении протокола?


person bogen    schedule 25.06.2015    source источник
comment
Если вы используете простые структуры, вы можете опустить инициализатор в протоколе, так как он будет синтезирован компилятором!   -  person heyfrank    schedule 28.02.2018


Ответы (4)


Как видите, в данных обстоятельствах это не работает, потому что при компиляции нужно убедиться, что все свойства инициализированы перед использованием 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, и его можно переопределить, поэтому его нельзя добавить через расширение. Обходной путь (как предлагает компилятор): сделайте файл class final.

Другой обходной путь (глубоко; без 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).

Если вы добавите его, у вас будет прежняя проблема с объявлением class как final, но теперь он печатает два раза «HoverCar». В любом случае вторая проблема, вероятно, никогда не возникнет, поэтому это может быть лучшим решением.

Примечание: если я допустил какие-то ошибки (код, язык, грамматика и т. д.), вы можете меня исправить :)

person Qbyte    schedule 25.06.2015
comment
Можете ли вы добавить объявление класса, чтобы проиллюстрировать ваше решение? - person Craig Grummitt; 15.01.2016
comment
@CraigGrummitt Какой класс вы имеете в виду? Car — это протокол, и его объявление завершено. - person Qbyte; 15.01.2016
comment
Я имею в виду класс, который бы реализовывал протокол Car. - person Craig Grummitt; 15.01.2016
comment
@CraigGrummitt Спасибо за комментарий к этому ответу. При добавлении примера я нашел второе решение, которое, вероятно, лучше первого :) См. отредактированный ответ выше. - person Qbyte; 16.01.2016
comment
спасибо за показ класса. Основная проблема, которую я вижу в этом решении, заключается в том, что вам нужно либо объявить значение по умолчанию для колес (в объявлении var или требуемом init()), либо вам нужно объявить его неявно развернутым необязательным. Это такое же хорошее решение проблемы, как и у нас, мне просто интересно, не пахнет ли это само по себе, возможно, нам просто не следует реализовывать инициализаторы в расширениях протокола! :) - person Craig Grummitt; 18.01.2016
comment
@CraigGrummitt Я полностью с тобой согласен. Требование инициализации по умолчанию, которое заставляет вас устанавливать значение по умолчанию для колес, довольно плохо. Однако для Swift 3 ведется обсуждение частичных инициализаторов, которые могут решить эту проблему. - person Qbyte; 18.01.2016
comment
@Qbyte drivableHoverCar.wheels печатает 4, а не 6. - person slider; 03.11.2017
comment
но теперь он печатает два раза «HoverCar», упомянутый для второго подхода. Почему это произошло??? - person pravir; 09.07.2020
comment
@pravir Такое поведение связано со статической и динамической отправкой, см. «Примеры» в medium.com/flawless-app-stories/. Если мы укажем в протоколе «init(wheels:Int)», то он использует динамическую отправку. Если метод определен только в extension, то он использует статическую диспетчеризацию. - person Qbyte; 10.07.2020

Насколько я понимаю, это невозможно, потому что расширение протокола не может знать, какие свойства имеет соответствующий класс или структура, и поэтому не может гарантировать, что они правильно инициализированы.

Если есть способы обойти это, мне очень интересно узнать! :)

person Eivind Rannem Bøhler    schedule 25.06.2015

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

У меня есть 2 способа: использовать final или реализовать init

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

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

    required init() {

    }
}
person onmyway133    schedule 30.12.2015
comment
отлично. Наконец-то моя ссылка, в которой показаны основные части - person Drew; 30.12.2015

Может быть не то же самое, но в моем случае вместо использования init я использовал статическую функцию для возврата объекта класса.

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

Я знаю, что это не лучшая вещь, но это лучшее решение, которое я смог найти, чтобы не связывать бизнес-модель с уровнем данных.

ПРИМЕЧАНИЕ. Я ленив и написал все прямо в комментарии, поэтому, если что-то не работает, дайте мне знать.

person Mijail    schedule 20.05.2016