Подтвердить что ты не робот

Добавление инициализаторов удобства в Swift Subclass

В качестве учебного упражнения я пытаюсь реализовать подкласс SKShapeNode, который предоставляет новый инициализатор удобства, который принимает число и строит ShapeNode, который является квадратом ширины и высоты числа.

В соответствии с Swift Book:

Правило 1

Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.

Правило 2

Если ваш подкласс обеспечивает реализацию всех его инициализаторов, назначенных суперклассам, либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части определения, то он автоматически наследует все инициализаторы удобства суперкласса ".

Однако следующий класс не работает:

class MyShapeNode : SKShapeNode {
    convenience init(squareOfSize value: CGFloat) {
        self.init(rectOfSize: CGSizeMake(value, value))
    }
}

Вместо этого я получаю:

Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
        self.init(rectOfSize: CGSizeMake(value, value))
    ^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
    self.init(rectOfSize: CGSizeMake(value, value))
         ^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}

Я понимаю, что MyShapeNode должен наследовать все инициализаторы удобства SKShapeNode, потому что я не выполняю никаких собственных инициализаторов, и потому что мой инициализатор удобства вызывает init(rectOfSize), другой удобный инициализатор, это должно работать. Что я делаю неправильно?

4b9b3361

Ответ 1

Мое понимание Инициализатора Наследование такое же, как у вас, и я думаю, что мы оба хорошо согласны с тем, что говорится в книге. Я не думаю, что это проблема интерпретации или непонимание указанных правил. Тем не менее, я не думаю, что вы делаете что-то неправильно.

Я тестировал следующее на игровой площадке и работает как ожидалось:

class RectShape: NSObject {
    var size = CGSize(width: 0, height: 0)
    convenience init(rectOfSize size: CGSize) {
        self.init()
        self.size = size
    }
}

class SquareShape: RectShape {
    convenience init(squareOfSize size: CGFloat) {
        self.init(rectOfSize: CGSize(width: size, height: size))
    }
}

RectShape наследует от NSObject и не определяет никаких назначенных инициализаторов. Таким образом, согласно правилу 1 он наследует все NSObject назначенные инициализаторы. Инициализатор удобства, который я представил в реализации, правильно делегирует назначенному инициализатору, прежде чем выполнять настройку для intance.

SquareShape наследует от RectShape, не предоставляет назначенный инициализатор и, опять же, согласно правилу 1, наследует все SquareShape назначенные инициализаторы. В соответствии с правилом 2 он также наследует инициализатор удобства, определенный в RectShape. Наконец, инициализатор удобства, определенный в SquareShape, должным образом делегирует его в унаследованный инициализатор удобства, который, в свою очередь, делегирует унаследованный назначенный инициализатор.

Итак, учитывая тот факт, что вы ничего не делаете неправильно, и мой пример работает так, как ожидалось, я экстраполирую следующую гипотезу:

Так как SKShapeNode записано в Objective-C, правило, которое гласит, что "каждый инициализатор удобства должен вызывать другой инициализатор из того же класса" не применяется языком. Итак, возможно, инициализатор удобства для SKShapeNode фактически не вызывает назначенный инициализатор. Следовательно, хотя подкласс MyShapeNode наследует инициализаторы удобства, как и ожидалось, они должным образом не делегируют унаследованный назначенный инициализатор.

Но, опять же, это только гипотеза. Все, что я могу подтвердить, это то, что механика работает так, как ожидалось, на двух классах, которые я создал сам.

Ответ 2

Здесь есть две проблемы:

  • SKShapeNode имеет только один назначенный инициализатор: init(). Это означает, что мы не можем выйти из нашего инициализатора без вызова init().

  • SKShapeNode имеет свойство path, объявленное как CGPath!. Это означает, что мы не хотим выбраться из нашего инициализатора без какой-либо инициализации path.

Сочетание этих двух вещей является источником проблемы. Вкратце, SKShapeNode неправильно написан. Он имеет свойство path, которое должно быть инициализировано; поэтому он должен иметь назначенный инициализатор, который устанавливает path. Но это не так (все его инициализаторы path - инициализаторы удобства). Это ошибка. Иначе говоря, источником проблемы является то, что удобство или нет, методы shapeNodeWith... вообще не являются инициализаторами.

Тем не менее вы можете делать то, что хотите - написать инициализатор удобства, не будучи вынужденным писать какие-либо другие инициализаторы, - выполнив оба требования в этом порядке, то есть написав это следующим образом:

class MyShapeNode : SKShapeNode {
    convenience init(squareOfSize value: CGFloat) {
        self.init()
        self.init(rectOfSize: CGSizeMake(value, value))
    }
}

Это выглядит незаконно, но это не так. Как только мы вызвали self.init(), мы выполнили первое требование, и теперь мы можем обращаться к self (мы больше не получаем "использование" я "при делегировании инициализатора до того, как self.init называется" ошибка) и удовлетворяют второму требованию.

Ответ 3

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

Вот что сработало для подкласса SKShapeNode:

class CircleNode : SKShapeNode {

    override init() {
        super.init()
    }

    convenience init(width: CGFloat, point: CGPoint) {
        self.init()
        self.init(circleOfRadius: width/2)
        // Do stuff
     }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Ответ 4

Хорошие новости с 2019 года! Я могу сообщить, что у меня теперь есть подкласс SKShape, который имеет следующие три инициализатора:

    override init() {
    super.init()
}

convenience init(width: CGFloat, point: CGPoint) {
    self.init(circleOfRadius: width/2)
    self.fillColor = .green
    self.position = point
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

и это ведет себя точно так, как и ожидалось: когда вы вызываете удобный инициализатор, вы получаете зеленые точки в нужной позиции. (С другой стороны, двойной вызов init(), описанный @matt и @Crashalot, теперь приводит к ошибке).

Я бы предпочел иметь возможность изменять SKShapeNodes в редакторе сцен .sks, но вы не можете иметь все. ВСЕ ЖЕ.