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

Как реализовать два входа с одним контентом без дублирования кода в Swift?

Предположим, что класс, полученный из UIView следующим образом:

class MyView: UIView {
    var myImageView: UIImageView

    init(frame: CGRect) {
        super.init(frame: frame)
    }

    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }

    ...

Если бы я хотел иметь один и тот же код в обоих инициализаторах, например

self.myImageView = UIImageView(frame: CGRectZero)
self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill

и НЕ дублировать этот код дважды в реализации класса, как бы я структурировал методы init?

Пробовал подходы:

  • Созданный метод func commonInit(), который вызывается после super.init → Swift-компилятора, выдает ошибку о неинициализированной переменной myImageView перед вызовом super.init
  • Вызов func commonInit() до super.init не выполняется, очевидно, с ошибкой компилятора "self", используемой перед вызовом super.init "
4b9b3361

Ответ 1

У меня просто была та же проблема.

Как сказал GoZoner, пометка ваших переменных как необязательная будет работать. Это не очень элегантный способ, потому что вам нужно разворачивать значение каждый раз, когда вы хотите получить к нему доступ.

Я напишу запрос об улучшении с Apple, возможно, мы могли бы получить что-то вроде метода "beforeInit", который вызывается перед каждым init, где мы можем назначить переменные, поэтому нам не нужно использовать необязательные vars.

До тех пор я просто поставлю все назначения в метод commonInit, который вызывается из выделенных инициализаторов. Например:.

class GradientView: UIView {
    var gradientLayer: CAGradientLayer?  // marked as optional, so it does not have to be assigned before super.init

    func commonInit() {
        gradientLayer = CAGradientLayer()
        gradientLayer!.frame = self.bounds
        // more setup 
    }

    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
        commonInit()
    }

     init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer!.frame = self.bounds  // unwrap explicitly because the var is marked optional
    }
}

Благодаря Дэвиду я снова взглянул на книгу, и я нашел кое-что, что может быть полезно для наших усилий по дедупликации, не используя необязательный переменный хак. Для инициализации переменной можно использовать замыкание.

Установка значения свойства по умолчанию с закрытием или функцией

Если для значения по умолчанию для хранимых свойств требуется некоторая настройка или настройка, вы можете использовать функцию закрытия или глобальную функцию для предоставления настраиваемого значения по умолчанию для этого свойства. Всякий раз, когда инициализируется новый экземпляр типа, к которому принадлежит свойство, замыкается или функция, и его возвращаемое значение присваивается как значение по умолчанию свойства. Эти типы замыканий или функций обычно создают временное значение того же типа, что и свойство, портят это значение для представления желаемого начального состояния, а затем возвращают это временное значение, которое будет использоваться как значение по умолчанию свойства.

Вот схема скелета о том, как можно использовать закрытие для предоставления значения свойства по умолчанию:

class SomeClass {
let someProperty: SomeType = {
    // create a default value for someProperty inside this closure
    // someValue must be of the same type as SomeType
    return someValue
    }() 
}

Обратите внимание, что за закрытием заканчивается фигурная скобка, за которой следует пустая пара скобок. Это означает, что Swift немедленно выполнит закрытие. Если вы опустите эти круглые скобки, вы пытаетесь присвоить это свойство закрытию, а не возвращаемое значение закрытия.

Примечание

Если вы используете закрытие для инициализации свойства, помните, что остальная часть экземпляра еще не была инициализирована в точке, в которой выполняется закрытие. Это означает, что вы не можете получить доступ к каким-либо другим значениям свойств из вашего закрытия, даже если эти свойства имеют значения по умолчанию. Вы также не можете использовать неявное свойство self или вызвать любой из методов экземпляров.

Отрывок из: Apple Inc. "Быстрый язык программирования". интерактивные книги. https://itun.es/de/jEUH0.l

Это то, как я буду использовать с этого момента, потому что он не обходит полезную функцию, не допускающую nil на переменные. Для моего примера это будет выглядеть так:

class GradientView: UIView {
    var gradientLayer: CAGradientLayer = {
        return CAGradientLayer()
    }()

    func commonInit() {
        gradientLayer.frame = self.bounds
        /* more setup */
    }

    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)
        commonInit()
    }

    init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
}

Ответ 2

Нам нужно обычное место, чтобы поместить наш код инициализации перед вызовом инициализаторов суперкласса, поэтому то, что я сейчас использую, показано в коде ниже. (Он также охватывает случай взаимозависимости между значениями по умолчанию и сохраняет их постоянными.)

import UIKit

class MyView: UIView {
        let value1: Int
        let value2: Int

        enum InitMethod {
                case Coder(NSCoder)
                case Frame(CGRect)
        }

        override convenience init(frame: CGRect) {
                self.init(.Frame(frame))!
        }

        required convenience init?(coder aDecoder: NSCoder) {
                self.init(.Coder(aDecoder))
        }

        private init?(_ initMethod: InitMethod) {
                value1 = 1
                value2 = value1 * 2 //interdependence among defaults

                switch initMethod {
                case let .Coder(coder): super.init(coder: coder)
                case let .Frame(frame): super.init(frame: frame)
                }
        }
}

Ответ 3

Как насчет этого?

public class MyView : UIView
{
    var myImageView: UIImageView = UIImageView()

    private func setup()
    {
        myImageView.contentMode = UIViewContentMode.ScaleAspectFill
    }

    override public init(frame: CGRect)
    {
        super.init(frame: frame)
        setup()
    }

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

Ответ 4

Назначьте myImageView в обоих методах init(), основанных на одной функции создания изображения. Таким образом:

self.myImageView = self.createMyImageView ();

Например, например:

class Bar : Foo {
    var x : Int?
    func createX () -> Int { return 1 }
    init () {
        super.init ()
        self.x = self.createX ()
    }   
}

Обратите внимание на необязательное использование в Int?

Ответ 5

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

class MyView: UIView {
    var myImageView: UIImageView!

    init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
        self.commonInit()
    }

    func commonInit() {
        self.myImageView = UIImageView(frame: CGRectZero)
        self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill
    }

    ...
}

Неявно развернутые опции позволяют пропускать присвоение переменной перед вызовом super. Однако вы можете получить к ним доступ, как обычные переменные:

var image: UIImageView = self.myImageView // no error

Ответ 6

Еще одна опция, использующая статический метод (добавлена ​​опция "otherView", чтобы выделить масштабируемость)

class MyView: UIView {

    var myImageView: UIImageView
    var otherView: UIView

    override init(frame: CGRect) {
        (myImageView,otherView) = MyView.commonInit()
        super.init(frame: frame)
    }

    required init(coder aDecoder: NSCoder) {
        (myImageView, otherView) = MyView.commonInit()
        super.init(coder: aDecoder)!
    }

    private static func commonInit() -> (UIImageView, UIView) {
        //do whatever initialization stuff is required here
        let someImageView = UIImageView(frame: CGRectZero)
        someImageView.contentMode = UIViewContentMode.ScaleAspectFill
        let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        return (someImageView, someView)
    }
}