关于xcode:如何初始化相互依赖的属性

How to initialize properties that depend on each other

我希望图片移到底部。 如果按按钮,图片应向下移动1。

我添加了图片和一个按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var corX = 0
var corY = 0

var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton

var image = UIImage(named:"panzerBlau.jpg");
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40));  //

override func viewDidLoad() {
    super.viewDidLoad()

    panzer.image = image;    //
    self.view.addSubview(panzer);    //

    runter.frame = CGRectMake(100, 30, 10 , 10)
    runter.backgroundColor = UIColor.redColor()
    view.addSubview(runter)
    runter.addTarget(self, action:"fahren", forControlEvents:UIControlEvents.TouchUpInside)
}

至少我在函数" fahren"中说过将图片向下移动1。

1
2
3
4
5
func fahren(){
    corY += 1
    panzer.frame = CGRectMake(corX, corY, 30, 40) //
    self.view.addSubview(panzer);
}

所以我的问题是:这些corX和corY东西出现几个错误。 没有它们,它会完美地工作,但比起一次性按钮更是如此。 错误是:ViewController.Type没有名为corX的成员,而ViewController.Type没有名为panzer的成员。

PS:我使用Xcode Beta5

这是没有其他内容的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UIKit

class ViewController: UIViewController {

    var corX = 0
    var corY = 0
    var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
    var image = UIImage(named:"panzerBlau.jpg");
    var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40));

    override func viewDidLoad() {
        super.viewDidLoad()
            panzer.image = image;
            self.view.addSubview(panzer);

        runter.frame = CGRectMake(100, 30, 10 , 10)
        runter.backgroundColor = UIColor.redColor()
        view.addSubview(runter)
        runter.addTarget(self, action:"fahren", forControlEvents:UIControlEvents.TouchUpInside)
    }

    func fahren(){
        corY += 100
        panzer.frame = CGRectMake(corX, corY, 30, 40)
        self.view.addSubview(panzer);
    }
}


@MartinR在这里指出了主要问题:

1
2
3
var corX = 0
var corY = 0
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40))

问题在于,Swift默认初始化程序无法引用另一个属性的值,因为在初始化时,该属性尚不存在(因为实例本身尚不存在)。基本上,在panzer的默认初始化程序中,您隐式地引用self.corXself.corY-但没有self,因为self正是我们在创建过程中所需要的。

一种解决方法是使初始化程序变得懒惰:

1
2
3
4
5
6
class ViewController: UIViewController {
    var corX : CGFloat = 0
    var corY : CGFloat = 0
    lazy var panzer : UIImageView = UIImageView(frame: CGRectMake(self.corX, self.corY, 30, 40))
    // ...
}

这是合法的,因为panzer直到稍后被实际代码首次引用时才进行初始化。到那时,self及其属性已经存在。


您的依附财产必须是:

  • lazy
  • 有一个明确的: Type
  • 使用self.访问其他属性
  • 例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let original ="foo"

    // Good:
    lazy var depend: String = self.original

    // Error:
         var noLazy: String = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
    lazy var noType         = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
    lazy var noSelf: String = original      // Error: Instance member 'original' cannot be used on type 'YourClass'


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    //
    //  ViewController.swift
    //
    //  Created by Shivank Agarwal on 19/05/18.
    //  Copyright ? 2018 Shivank Agarwal. All rights reserved.
    //

    import UIKit

    class ViewController: UIViewController {

        var corX = 0
        var corY = 0
        var runter: UIButton = UIButton()
        var image = UIImage(named:"panzerBlau.jpg")
        var panzer = UIImageView()

        override func viewDidLoad() {
            super.viewDidLoad()
            panzer.image = image;
            self.view.addSubview(panzer);
            panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
            runter.backgroundColor = UIColor.red
            view.addSubview(runter)
            view.addSubview(panzer)
            runter.addTarget(self, action: Selector(("fahren")), for:UIControlEvents.touchUpInside)
        }

        private func fahren(){
            corY += 100
        }

        private func updatePanzerFrame(){
            panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
        }
    }

    Note: Do not add panzer imageView every time when user tap only add it on viewDidLoad()

    Xcode sample


    我正在解决问题的标题:

    直到对象初始化之后才知道属性的初始值时,惰性属性和计算属性都可以帮助您处理。但是有一些区别。我用粗体突出显示了差异。

    如果您只需要在初始化一些其他变量后初始化一个变量,则应该使用lazy,即如果要添加一个延迟(因此所有必需的属性都在初始化之前),那么使用lazy是正确的方法去做。

    但是,如果您需要不断地基于另一个变量来更改变量,则需要一个可以同时起作用的计算属性:

    • 如果计算出的属性集,则将变量设置为其相关的存储属性
    • 如果已设置(或再次重置)存储的属性,则它将触发随后计算的属性的更改。

    如果更改惰性属性的值,则不会影响其基于的存储属性。看到这里

    使用惰性属性的一个很好的例子是,一旦拥有firstNamelastName,您就会懒惰地实例化fullName,并且很可能永远都不会更改对象的firstName lastName,而fullName仅是一次。 。
    或者也许只能由lazy属性完成的事情是直到您不访问该属性之前,它都不会被初始化,因此这将减少类的初始化负担。正在加载大量计算。

    另外,使用lazy会向其他开发人员发出信号:"嘿,首先阅读其他属性并了解它们是什么...然后进入这个懒惰属性...因为此属性的值基于它们+这是可能不应该过早访问繁重的计算..."

    至于计算属性,一个很好的例子是,如果您将温度设置为华氏温度,那么您还希望您的摄氏温度改变它的值...并且如果您设置摄氏温度,那么您又想改变您的华氏温度值。

    结果,计算属性将增加额外的计算...如果您的计算非常简单且调用不频繁,则无需担心,但是如果调用过于频繁或占用大量CPU,则可能会更好考虑其他选择...