如何在Swift中实现具有相同内容的两个没有代码重复的内容?

时间:2014-06-03 19:29:20

标签: ios 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 - >在调用myImageView
  • 之前,Swift编译器会提供有关未初始化变量super.init的错误
  • func commonInit()之前调用super.init会自动失败并出现编译错误'' self'在super.init call"
  • 之前使用

7 个答案:

答案 0 :(得分:21)

我们需要的是在调用任何超类的初始化器之前放置初始化代码的常见位置,所以我目前使用的是,在下面的代码中显示。 (它还涵盖了默认值之间相互依赖的情况,并使它们保持不变。)

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

答案 1 :(得分:20)

我刚收到same problem

正如GoZoner所说,将变量标记为可选将起作用。它不是一种非常优雅的方式,因为每次你想要访问它时你必须打开它的值。

我将向Apple提交增强请求,也许我们可以获得类似" beforeInit"在每个init之前调用的方法,我们可以在其中分配变量,因此我们不必使用可选的变量。

在此之前,我将把所有赋值放入一个从专用初始化程序调用的commonInit方法中。 E.g:

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

感谢David,我再次看了一下这本书,我找到了一些可能对我们的重复数据删除工作有所帮助,而不必使用可选的变量hack。可以使用闭包来初始化变量。

  

使用闭包或函数设置默认属性值

     

如果存储属性的默认值需要某些自定义或设置,则可以使用闭包或全局函数为该属性提供自定义的默认值。每当初始化属性所属类型的新实例时,将调用闭包或函数,并将其返回值指定为属性的默认值。这些类型的闭包或函数通常会创建与属性相同类型的临时值,定制该值以表示所需的初始状态,然后返回该临时值以用作属性的默认值。

     

这是一个关于如何使用闭包来提供默认属性值的骨架轮廓:

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.“The Swift Programming Language。”iBooks。 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 :(得分: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()
    }
}

答案 3 :(得分:1)

基于单个图像创建功能在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?

使用

答案 4 :(得分:1)

它必须要来吗?我认为这是可以用于隐式解包的选项之一:

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

答案 5 :(得分:1)

使用静态方法的另一种选择(添加'其他视图'突出显示可扩展性)

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

答案 6 :(得分:0)

此外,如果打算只分配一次myImageView,则应该是let而不是var。这排除了一些仅适用于var的解决方案。

另一个复杂之处是多个实例变量之间存在依赖性。这排除了调用静态方法的内联初始化程序。

这些要求可以通过使用便捷初始化程序进行覆盖来解决,这些初始化程序委托给一个指定的初始化程序:

import UIKit

class MyView: UIView {
    let myImageView: UIImageView
    // Just to illustrate dependencies...
    let myContainerView: UIView
    
    override convenience init(frame: CGRect) {
        self.init(frame: frame, coder: nil)!
    }
    
    required convenience init?(coder aDecoder: NSCoder) {
        // Dummy value for `frame`
        self.init(frame: CGRect(), coder: aDecoder)
    }
    
    @objc private init?(frame: CGRect, coder aDecoder: NSCoder?) {
        // All `let`s must be assigned before
        // calling `super.init`...
        myImageView = UIImageView(frame: CGRect.zero)
        myImageView.contentMode = .scaleAspectFill
        
        // Just to illustrate dependencies...
        myContainerView = UIView()
        myContainerView.addSubview(myImageView)
        
        if let aDecoderNonNil = aDecoder {
            super.init(coder: aDecoderNonNil)
        } else {
            super.init(frame: frame)
        }
        
        // After calling `super.init`, can safely reference
        // `self` for more common setup...
        self.someMethod()
    }

    ...    
}

这是基于ylin0x81's answer的,我真的很喜欢,但是现在不起作用(使用Xcode 10.2构建),因为来自笔尖的加载崩溃:

This coder requires that replaced objects be returned from initWithCoder:

此问题由一个单独的问题解决,iuriimoz's answer建议将@objc添加到指定的初始化程序中。这就避免了ylin0x81使用的仅Swift枚举。