【问题标题】:Lazy loading Properties in swift快速延迟加载属性
【发布时间】:2014-07-26 22:57:16
【问题描述】:

我正在尝试围绕 Swift 语言展开思考。使用 Objective-C 在代码中构建视图的常见模式是覆盖 UI 属性并延迟加载它们,如下所示:

@property(nonatomic, strong) UILabel *myLabel;

- (UILabel *)myLabel
{
     if (!_myLabel) {
         _myLabel = [[UILabel alloc] initWithFrame:CGRectMake(20.0f, 75.0f, 320.0f, 20.0f)];
        [_myLabel setFont:[UIFont subHeadlineFont]];
        [_myLabel setTextColor:[UIColor subHeadlineColor]];
        [_myLabel setText:@"Hello World"];
     }
     return _myLabel;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.view addSubview:self.myLabel];
}

这允许 UIElements 的配置在其设置中自包含,但不会导致每次都重新配置它们。

似乎我们无法访问 Swift 中的后备存储,并且 @lazy 关键字实际上并没有相同的语义。

我很好奇是否有人在 Swift 中发现了一种类似的模式,它允许人们以一种不会导致每次都重新配置的简洁语法方式将变量和常量的配置与它们的声明保持在一起?

【问题讨论】:

    标签: swift


    【解决方案1】:

    斯威夫特 3.0

    我更喜欢这种内联样式。

    lazy var myLabel: UILabel = self.createMyLabel()
    
    private func createMyLabel() -> UILabel {
        let mylabel = UILabel()
        // ...
        return mylabel
    }
    

    【讨论】:

    • 这对我不起作用,它给出了一个错误:使用未解析的标识符'self'。在惰性 var 行上。
    【解决方案2】:

    我认为使用闭包初始化的 lazy 属性会起作用:

    lazy var myLabel: UILabel = {
        var temporaryLabel: UILabel = UILabel()
        ...
        return temporaryLabel
    }()
    

    当我阅读“Swift 编程语言”时。 (棋盘格示例)闭包只评估一次)。

    【讨论】:

    • 惰性属性必须是var,不能是let
    • 内部的temporaryLabel 可以被忽略,显然没有任何不良后果。
    • 棒极了,这是否意味着一旦myLabel被初始化,就不会再重新初始化,或者生成一个新的UILabel实例而是返回第一次启动的UILabel实例?
    • 闭包只会执行一次。
    • 评估关闭。闭包的类型为“(​​) -> ​UILabel​”。但是属性“myLabel”的类型是“UILabel”。因此,当第一次需要 myLabel 时,需要评估闭包并将返回值分配给“myLabel”。
    【解决方案3】:

    作为 Christian Otkjær 答案的变体,也可以将类方法分配给 @lazy var:

    class MyClass {
    
      @lazy var myLabel : UILabel = MyClass.newLabel()
    
      class func newLabel() -> UILabel {
        var temporaryLabel : UILabel = UILabel()
        ...
        return temporaryLabel
      }
    }
    

    它实际上与使用闭包相同,但如果闭包中的代码行数过多,则可以选择在声明所有属性和初始化方法之后将该代码放在其他地方的类方法中。

    【讨论】:

      【解决方案4】:

      Apple 的做法似乎有所不同...如果我在 Xcode 中创建一个新项目并添加 Core Data,AppDelegate.swift 中有一个示例:

      // Returns the managed object model for the application.
      // If the model doesn't already exist, it is created from the application's model.
      var managedObjectModel: NSManagedObjectModel {
          if !_managedObjectModel {
              let modelURL = NSBundle.mainBundle().URLForResource("MyApp", withExtension: "momd")
              _managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)
          }
          return _managedObjectModel!
      }
      var _managedObjectModel: NSManagedObjectModel? = nil
      

      虽然,这对我来说是因为变量是在初始化时创建的,但是随后读取,@lazy 似乎是一个更好的实现。有什么想法吗?

      所以我尝试了这个:

      class TestClass {
          @lazy var myLabel : UILabel = {
              var temporaryLabel : UILabel = UILabel()
              return temporaryLabel 
          }()
      
          var testLabel: UILabel {
          if !_testLabel {
              _testLabel = UILabel()
              }
          return _testLabel!
          }
          var _testLabel: UILabel? = nil
      
          func test () {
              println(myLabel)
              println(self.testLabel)
          }
      }
      

      两者确实只是懒惰地创建的。作为@bradlarson points out on Twitter:

      @runmad 您的方法保留的一件事是只读的 财产的状态。 @lazy 不能与 let 一起使用,这是一个 问题。

      【讨论】:

      • 用@lazy 写这个的方法不是很直观。我宁愿使用Apple的方法。 ` @lazy var managedObjectModel: NSManagedObjectModel = {() -> NSManagedObjectModel in var _managedObjectModel: NSManagedObjectModel? = nil let modelURL = NSBundle.mainBundle().URLForResource("SwiftCD", withExtension: "momd") _managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL) return _managedObjectModel!; }() `
      • 有一点。如果你使用@lazy,你几乎需要确保它是安全的。你不会再有机会了。使用苹果的模板,如果创建者返回nil,下次会再次尝试创建对象。
      • 请注意,Apple 的模板确实现在使用lazy
      【解决方案5】:
      class Thingy {
          init(){
              println("making a new Thingy")
          }
      }
      
      var thingy = {
          Thingy();
      }()
      
      println("\(self.thingy)")
      println("\(self.thingy)")
      

      日志信息“making a new Thingy”只出现一次,证明只创建了一个Thingy——闭包只被调用了一次,即初始化这个属性。这实际上就是您所描述的。您所要做的就是向闭包添加更多内容,以便将其配置为返回的对象。

      如果你创建 var @lazy 并注释掉 println 语句,no Thingy 被创建,证明懒惰做了它想要做的事情;但是,您可以忽略这一点,因为您知道实际上在早期总是需要该标签。 @lazy 的目的是防止闭包永远被调用,除非调用 getter,但你总是要调用 getter,所以这在你的情况下毫无意义。

      【讨论】:

      • 从解释的角度来看,这是迄今为止最好的答案,尽管 Christian 还指出,闭包在“快速编程语言”中只评估一次。虽然让我们想象一个我想调用的按钮 _myButton.addTarget(self, action: "buttonPressed", forControlEvents:.TouchUpInside) 在闭包的上下文中不会捕获自我吗?
      • 在 Swift 中没有危险,因为有闭包语法可以防止强捕获你想要阻止捕获的任何内容。比我们过去不得不做的“弱强舞”要简单得多。
      • 没问题,这是您的复选标记!实际上,真正的第一个正确响应者是 Cezar,然后他删除了他的答案。问题在于他尝试使用函数 self.configureMyLabel() 但是,正如我们刚刚看到的,您还不能引用 self 来获取函数。它需要保存在其他地方,或者您可以使用闭包。
      • @PhamHoan {} 定义了一个匿名函数。 {}() 定义了一个匿名函数并调用它。
      • @PhamHoan 不,没有错。我们正在定义一个变量,其默认值是调用函数的结果。 Apple 正在定义一个变量,其默认值一个函数。
      【解决方案6】:

      您可以在 @lazy 变量上提供一个闭包来声明它应该如何创建:

      class Blah {
          @lazy var label: () -> UILabel = {
              var view:UILabel = UILabel();
              //Do stuff here
              return view;
          }
      }
      

      【讨论】:

      • 在这种情况下您的labelfunc,我认为您需要删除() ->
      • () -> UILabel 应该在大括号内,然后是 in,并且 () 应该在右大括号之后...
      【解决方案7】:

      这几乎是作为您的 ObjectiveC 示例的 Swift 版本。 (简化为使用Int 而不是视图)

      class Foo {
          var _value: Int?
          var value: Int {
              get {
                  if !_value {
                      _value = 123
                  }
                  return _value!
              }
          }
      }
      
      Foo().value //-> 123
      

      虽然不是很漂亮。

      【讨论】:

      • 是的,我可以声明两个变量来懒惰地加载我想放在屏幕上的每个 UIElement,但模式并不完全一样。正如你提到的,它不是很漂亮。
      • 如果您需要通过将变量设置为 nil 来重置变量,这很有价值。
      • 否则你不能覆盖lazy
      猜你喜欢
      • 1970-01-01
      • 2017-12-28
      • 1970-01-01
      • 2013-07-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多