""
如果你是iOS或macOS开发人员,你一定知道awakeFromNib()或awakeFromNib()等价方法的作用。这些方法通常被用来初始化界面元素、数据、绑定到控制器的属性等。然而,它们看起来很简单,但在实践中,它们可能会导致令人困惑的问题。如今,让我们重新审视一个专门用于捕捉awakeFromNib()黑魔法的常见错误。
1.关于awakeFromNib()
将nib文件连接到视图控制器、窗体控制器、自定义视图和xib等对象时,awakeFromNib()函数是被用来初始化这些对象的。当nib文件加载并准备好之后它会自动调用这个方法。awakeFromNib()方法必须实现在NSObject或它的子类中,不然它不会被调用。
以下是awakeFromNib()在NSObject子类中的一个简单的示例:
override func awakeFromNib() {
super.awakeFromNib()
// 初始化代码
}
如果你在UIView或NSView的子类中使用它,可以通过它去初始化所有子视图:
override func awakeFromNib() {
super.awakeFromNib()
// 子视图初始化代码
}
2.循环引用
事实上,awakeFromNib()方法是在Interface Builder(IB)中自动生成的,这就是为什么你会在类中找到它,但它不会出现在代码编辑器中。但是,当我们需要自定义操作时,我们需要手动添加它。
这就是为什么一旦你开始自定义这个方法,你需要非常小心,因为如果你在实现中使用了错误的类型(如var、lazy var、static var等),你就会遭遇循环引用的困扰。这就意味着,一个控制器通过Storyboard等连接到界面元素时,可能会无限制地保留引用。这个问题通常问题发生在自定义View、TableCell等视图控件中。下面是循环引用发生的示例代码:
class CustomView: UIView {
@IBOutlet weak var titleLabel: UILabel!
lazy var tapGestureRecognizer: UITapGestureRecognizer = {
let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
return recognizer
}()
@objc func handleTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
// 操作
}
override func awakeFromNib() {
super.awakeFromNib()
self.addGestureRecognizer(self.tapGestureRecognizer)
self.titleLabel.text = "Hello"
}
}
在这个例子中,tapGestureRecognizer是一个Lazy存储属性,每次调用它时,它将返回新的实例。所以这个过程是无限制的,视图还需要保持对tapGestureRecognizer的引用,因为会在控制器dealloc之前实例中的引用计数变为零,并且释放掉这个控制器拥有的所有内存。解决循环引用的最佳方法是尽量将所有的视图控件的实例作为命名属性,而不是作为计算属性或懒加载属性。这样一来,内存的释放过程就可以由ARC负责了,在弱引用上做更多工作。
3.awakeFromNib()时机
`awakeFromNib()`在nib加载后自动调用,并在interface outlet连结后调用。如果没有这些连结,方法不会被调用。因此,请将你的代码放在方法的末尾,从而保证所有连结都完成了。如果在前面部分的代码中访问视觉清晰度,则可能会发现一件已经nil掉的视图对象,从而导致崩溃。下面是一个基本示例:
override func awakeFromNib() {
super.awakeFromNib()
print("view did load")
// 做一些代码
}
接下来,看下二个例子,当从导航栏返回控制器时,`awakeFromNib()` 方法何时被调用:
- 通过push进入下一个页面,返回时不会调用`awakeFromNib()`,因为视图注册表中没有被销毁。
- 通过modal present进入下一个页面,返回时会调用`awakeFromNib()` 。
如何解决默认值覆盖问题?
这个问题出现在awakeFromNib()中具有UIView层次结构的界面元素。如果没有用于配置样式的自定义 initView() 方法,它们通常也会在这里初始化,因为它们的操作只能在接口输出后执行。
如果在awakeFromNib()中子类化了UILabel、UITextField等,赋值语句将覆盖在 IB 中的默认值。如下所示:
override func awakeFromNib() {
super.awakeFromNib()
self.font = UIFont.systemFont(ofSize: 10)
self.textColor = UIColor.red
}
此时,可以使用以下代码解决这个问题:
override func layoutSubviews() {
super.layoutSubviews()
self.font = UIFont.systemFont(ofSize: 10)
self.textColor = UIColor.red
}
或者使用以下代码来实现:
override func awakeFromNib() {
super.awakeFromNib()
DispatchQueue.main.async {
self.font = UIFont.systemFont(ofSize: 10)
self.textColor = UIColor.red
}
}
最后
awakeFromNib()可以作为对象的初始化代码被用来配置控制器的初始状态。然而,对它的不正确使用,如对循环引用的错误处理,它将变得让你烦恼。因此,了解awakeFromNib()定义的方法和时机非常重要。
回顾一下本篇文章的精华:
- awakeFromNib()方法是在自定义控件(Nib或Storyboard)和自定义View的类中使用的,用于设置属性和为控件提供附加结构和动作。
- 当你继承自NSObject时,必须实现此实例方法。
- 不要信任自己的命名变量。memory在ARC原则下,使用weak来防止循环引用。
- 除了在awakeFromNib()方法中初始化对象,还有其他方法,例如自定义init()等。
愿在这篇文章中所述的awakeFromNib()方法问题的解决方案可以在你的下一个项目中帮助你。