【问题标题】:How to properly implement lazy instantiation with an @IBOutlet property in Swift如何在 Swift 中使用 @IBOutlet 属性正确实现延迟实例化
【发布时间】:2014-08-09 11:36:38
【问题描述】:

我正在通过 Big Nerd Ranch 的最新 iOS 书籍学习 iOS 开发。我选择在 Swift 中实现他们的应用程序。在他们的一个应用程序中,他们在 Objective C 中有以下代码:

- (UIView *)headerView
{
    // If you have not loaded the header view yet...
    if (!_headerView) {

        // Load HeaderView.xib
        [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil]
    }

    return _headerView;
}

Apple 关于“@IBOutlet”的 Swift 指南:

当你在 Swift 中声明一个 outlet 时,编译器会自动将类型转换为一个弱隐式展开的可选项,并为其分配一个初始值 nil。实际上,编译器将 @IBOutlet var name: Type 替换为 @IBOutlet weak var name: Type! = 无。

正如Lazy loading Properties in swift 中指出的那样,有几个不同的选项。在那篇文章中没有一个人明确提到使用@IBOutlet 进行延迟初始化,所以我已经尽力实施他们的建议,并且想知道什么是最佳实践。

尝试 #1(失败):遵循类似的模式,如 AppDelegate.swift 中的示例。这带来了问题“'IBOutlet' 属性要求属性是可变的”

@IBOutlet var headerView : UIView  {
    // If the HeaderView has not been loaded yet...
    if !_headerView {
        // Load HeaderView.xib
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
        return _headerView!
}
var _headerView : UIView? = nil

尝试#2(失败):使用任何“@lazy”和“@IBOutlet”的变体都不起作用,因为“@lazy”需要一个初始化器,但如果使用闭包,那么“@IBOutlet”有与尝试 #1 相同的问题

尝试#3(成功?):这是我能够让它工作的唯一方法。我从一个稍微不同的问题Lazy property initialization in Swift 中得到了这个想法。我对正在发生的事情的理解是 headerView 实际上被声明为“@IBOutlet weak var headerView:UIView!= nil”,只会用我拥有的 TableViewController 子类初始化一次,并且初始化将是“懒惰的”,因为它只会发生当需要加载 TableViewController 时。

@IBOutlet var headerView : UIView

func loadHeaderView() {
    // If the HeaderView has not been loaded yet...
    if !headerView {
        // Load HeaderView.xib
        println("loaded HeaderView")
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    loadHeaderView()
    tableView.tableHeaderView = headerView
}

那么,如何改进呢?

viewDidLoad() 是正确的函数吗?

谢谢

【问题讨论】:

  • 当我们计划在 Interface Builder 中连接一个属性时,通常我们使用IBOutlet - 如果这一切都将在代码中完成,那么声明该属性似乎是更好的做法明确的属性。
  • 这是有道理的,我现在有点盲目地追随 Big Nerd Ranch 的书。我认为他们展示了以这种方式做事的另一种选择
  • @SchoonSauce 在运行时将 nib 加载到属性中与从 nib 连接对象不同,这是 IBOutlet 所做的。

标签: ios objective-c cocoa-touch swift


【解决方案1】:

延迟加载的插座没有意义 - 如果它是插座,它会在加载 nib 时填充,而不是从代码中填充。如果你从代码中加载它,它不需要是一个插座,所以你可以使用@lazy。

【讨论】:

  • 定义一个ViewControllerUIView 类可能是一个很好的设计,它允许它的用户配置选项。也许我想设计一个可以在 interface-builder 中引用的类。将 IBOutlet 变量分配给您想要自定义的东西,但如果您不这样做,该类将自行配置。
【解决方案2】:

您实际上并没有使用该代码为headerView 提供闭包,而是将其声明为只读计算属性。 @IBOutlet 属性需要是可变的,这样 XIB/Storyboard 才能发挥它的魔力,因此您需要使用 getter 和 setter 来实现它,如下所示:

@IBOutlet var headerView : UIView {
get {
    if !_headerView {
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
    return _headerView!
}
set {
    _headerView = newValue
}
}
var _headerView : UIView? = nil

【讨论】:

  • 那么这是否比我在尝试#3 中实现它的方式更可取,如果是这样,那为什么?谢谢
  • 好吧,我的回答中的方法似乎有点混乱,因为我们还不能将_headerView 声明为私有属性,但是您不必记住在使用之前初始化视图,我认为这是目标。对于#3,如果使用了不同的路由到headerView(我认为在这种情况下没有,但只是如果),那么您将遇到运行时错误,因为您正在尝试工作nil 可选。
【解决方案3】:

以下作品...

@IBOutlet lazy var headerView : UIView? = {
    return NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)[0] as? UIView
    }()

然后设置headerView

override func viewDidLoad() {
  super.viewDidLoad()
  tableView.tableHeaderView = headerView
}

【讨论】:

    【解决方案4】:

    你也可以使用 didSet:

     @IBOutlet weak var profileImageView: UIImageView! {
            didSet {
                profileImageView.image = profileImage
                profileImageView.layer.masksToBounds = true
                profileImageView.layer.cornerRadius = 16
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多