这些天,我们很多人在教程中都使用这样的模式来初始化变量:
lazy var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}()
我已经看到它用于设置UILabels
等控件。 这样做的好处是将初始化代码保存在一个方便的地方。
我想我会开始讨论,看看这是否是我们想要在风格指南中鼓励的模式?
顺便说一句,自我循环保留在这里吗? 我认为应该添加[unowned self]
或[weak self]
还有一个问题:
在这种情况下,strongSelf 的推荐名称是什么,在您的指南中没有找到:
if let strongSelf = self {
}
我认为是否保留 self 并不重要,因为每当使用locationManager
变量然后被销毁时都会执行闭包。
虽然我不完全确定当你从不打电话时会发生什么locationManager
。 在这种情况下,关闭可能确实会保持自我。 有趣的。 :-)
编辑:刚刚测试了这一点,在这个闭包中使用 self 似乎没问题。 我想在您第一次调用locationManager
之前,不会真正创建闭包。 然后它立即结束并再次被销毁。
顺便说一句, !
是不需要的; 这很好用:
lazy var locationManager: CLLocationManager = {
谢谢,我更新了原始代码块以删除!
。 但是我们仍然需要讨论主要问题——我们是否应该在我们的风格指南中鼓励这种模式?
我喜欢。 它使事物的初始化接近事物的声明。 因此, viewDidLoad()
往往会变得更短且更易于阅读。
我也喜欢这个。 👍
如果强引用周期存在问题,那么我认为[unowned self]
应该解决它:
lazy var locationManager: CLLocationManager = {
[unowned self] in
let manager = CLLocationManager()
manager.delegate = self
...
return manager
}()
我喜欢这种方法,但我很难看到反对意见。 是否只是这种方法有点不明显,并且可能有细微差别(例如强大的保留周期的潜力)?
(参考:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID57)
IMO 这只是一种模式。 也许它属于 swift-patterns 存储库。 至于保留周期,我想念它。 这只是一个典型的委托分配。 为什么这与您刚刚在初始化方法中执行 CLLocationManager 初始化内容有什么不同? 委托模式通常鼓励委托属性(在这种情况下,在 CLLocationManager 中)被定义为弱,不是吗?
我的错误是强保留周期。 这不是问题,因为执行了闭包。 如果它返回一个闭包,那么可能有必要使用[unowned self]
,尽管在这种情况下不是,因为它被分配为一个弱持有的委托。
还值得记住的是,为生成惰性 var 而指定的闭包不应依赖于类的任何可变属性。 在生成惰性变量时(通过对闭包的评估),它将捕获这些依赖项的当前值。 如果 var 不改变,那会很混乱。
请参阅http://mikebuss.com/2014/06/22/lazy-initialization-swift/中的此示例
class Person {
var name: String
lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
我认为这很令人困惑:
let p = Person(name: "sam")
p.personalizedGreeting
// => "Hello, sam!"
p.name = "definitely somebody else"
p.personalizedGreeting
// => "Hello, sam!"
说了这么多,我还是觉得这个模式很好,适合在教程中使用,但是作者理解这些问题很重要,以免犯这些相对复杂的错误。
/我现在就嘘
在@sammyd链接的苹果文档中,苹果实际上举了一个例子:
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
在@sammyd给出的第二个链接中(他今天的链接很开心:]),作者举了一个例子:
lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
我认为在这些情况下使用unowned self
永远不会成为问题......也就是说,在创建惰性属性时......我想在某些情况下,虽然(正如@sammyd再次提到的那样,返回一个块,也许是其他人?)_not_ 使用unowned
可能会有问题。
为简单起见,为什么不总是将unowned
用于惰性属性?
无论如何,我完全赞成使用这种模式。 :+1:
注意asHTML
变量_stores_ 的闭包供以后使用。 我们在这里谈论的是使用立即执行的闭包来初始化常规变量。
我喜欢它,对于lazy
和非lazy
属性。
不过,我不确定本指南中的指导是什么。 _总是_那样做? 如果您有一些计算的常量或颜色,我还可以看到“旧方式”在viewDidLoad()
中初始化多个事物以重用对象的好处。 对我来说,这感觉像是一个个案。
多年来,我一直使用 initWithFrame 调用来实例化 ui 视图,其中包含 uilabels / textviews / 的子视图,它简化了代码并避免了视图生命周期调用——尤其是在调用超类时。
它会受到很小的性能影响吗? 或许。 这是否违背了 uikit 的流程和使用 nib 文件? 是的。 我会花费数小时对 nib 文件进行故障排除吗? 不会。我的应用程序会按时发货吗? 是的。 我担心吗? 不,其他开发人员感到震惊吗? 大概。 我在乎吗 - 不。
简单代码 = 简单逻辑 - 易于维护。
有些人会争辩说,在呈现视图控制器(它被推送到导航控制器上)之前,为了性能或内存 - 然后不要实例化控件(等待 viewDidLoad / loadView )。 那是在我们可以访问视图控制器本身的惰性变量之前。
所以这解决了抢先实例化的问题。
例如。
懒惰的 var loginVC = { return LoginVC()}()
注意 - 我在这里覆盖了 init 方法 - (没有 xib 文件)
required public init() {
super.init(nibName: nil, bundle: nil)
}
来自stackoverflow
http://stackoverflow.com/questions/27585409/using-a-programmatically-created-uitableviewcell-subclass-in-swift/31801507#31801507
这
类事件单元:UITableViewCell {
var eventName: UILabel = UILabel()
var eventCity: UILabel = UILabel()
var eventTime: UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(eventName)
self.contentView.addSubview(eventCity)
self.contentView.addSubview(eventTime)
}
override func layoutSubviews() {
super.layoutSubviews()
eventName = UILabel(frame: CGRectMake(20, 10, self.bounds.size.width - 40, 25))
eventCity = UILabel(frame: CGRectMake(0, 0, 0, 0))
eventTime = UILabel(frame: CGRectMake(0, 0, 0, 0))
}
}
VS
class EventCell: UITableViewCell {
lazy public var lblName = {
return UILabel (frame: CGRectMake(10, 0, self.bounds.size.width , 40))
}()
lazy public var lblCity = {
return UILabel (frame: CGRectMake(10, 40, self.bounds.size.width , 20))
}()
lazy public var lblTime = {
return UILabel (frame: CGRectMake(self.bounds.size.width - 80, 10, , 25))
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(lblName)
self.contentView.addSubview(lblCity)
self.contentView.addSubview(lblTime)
}
}
关于[unowned self]
用于防止保留循环:现在不再如此。
Apple 给出并由@sammyd链接的示例代码不再使用[unowned self]
:
lazy var asHTML: Void -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
事实上,我永远不会错过一个 deinit,方法是使用一个用引用裸self
的闭包初始化的惰性 var。
现在我不知道在这些闭包中self
是隐含的unowned
还是unowned(unsafe)
。 然而,我们应该停止让它显式,因为它是隐式的。
asHTML 示例不是用闭包初始化值,而是将闭包存储在变量中。 在 asHTML 的情况下,仍然需要 [unowned self] 来防止强引用循环。 这在链接的 Swift 文档中都提到了。
Eric Chamberlain,首席架构师 - iOS
ArcTouch - 应用开发工作室
埃里克。 [email protected] 邮寄地址: [email protected]
+1-415-944-2000
为世界级品牌和财富 500 强定制应用程序
arctouch.com/work | arctouch.com/blog
2015 年 12 月 8 日上午 8:18,Gwendal Roué [email protected]写道:
关于 [unowned self] 用于防止保留循环:不再是这种情况。
示例代码https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097 -CH20-ID57 由 Apple 提供并在上面链接@sammyd https://github.com/sammyd不再使用 [unowned self]:
懒惰的 var asHTML: Void -> String = {
如果让文本 = self.text {
返回“<(self.name)>(文本)(self.name)>”
} 别的 {
返回“<(self.name)/>”
}
}
事实上,我永远不会错过一个 deinit,方法是使用一个惰性 var 初始化一个引用裸自我的闭包。现在我不知道 self 在这些闭包中是隐式无主还是无主(不安全)。 然而,我们应该停止让它显式,因为它是隐式的。
—
直接回复此邮件或在 GitHub 上查看https://github.com/raywenderlich/swift-style-guide/issues/88#issuecomment -162933300。
@arctouch-ericchamberlain,你是对的,示例代码现在在属性中存储了一个闭包。
然而,我重复自己一遍,“我永远不会错过一个 deinit,方法是使用一个惰性 var 初始化一个引用裸自我的闭包。”
举个例子:
class C {
lazy var d: String = { "\(self)" }()
deinit { print("deinit") }
}
C() // deinit
C().d // deinit
你可以自己试试。 如果您找到保留周期,我很乐意再次使用[unowned self]
。
那是因为
懒惰的 var something = {..}()
不保留任何东西。 变量初始化时构造并执行闭包。 然而,
懒惰的东西= {...}
变量初始化时创建一个强引用循环。
示例代码中的 asHTML 变量属于后一种情况。
从初始化程序中删除 (),然后看看会发生什么。
Eric Chamberlain,首席架构师 - iOS
ArcTouch - 应用开发工作室
埃里克。 [email protected] 邮寄地址: [email protected]
+1-415-944-2000
为世界级品牌和财富 500 强定制应用程序
arctouch.com/work | arctouch.com/blog
2015 年 12 月 9 日下午 2:10,Gwendal Roué [email protected]写道:
@arctouch-ericchamberlain https://github.com/arctouch-ericchamberlain ,你是对的,示例代码现在在属性中存储了一个闭包。
然而,我重复自己一遍,“我永远不会错过一个 deinit,方法是使用一个惰性 var 初始化一个引用裸自我的闭包。”
举个例子:
C类{
懒惰的 var d: String = { "(self)" }()
deinit {打印(“deinit”)}
}C() // 取消初始化
C().d // 取消初始化
你可以自己试试。 如果您找到保留周期,我很乐意再次使用 [unowned self]。—
直接回复此邮件或在 GitHub 上查看https://github.com/raywenderlich/swift-style-guide/issues/88#issuecomment -163413290。
我不跟着你。
这些惰性变量都不需要[unowned self]
:
lazy var d: String = { "\(self)" }()
lazy var asHTML: Void -> String = { "\(self)" }
哪种惰性 var 可以?
糟糕,调用 asHTML 确实会创建保留循环。
谢谢@arctouch-ericchamberlain。 我误会了。
@groue
您发现 Apple 文档不使用[unowned self]
的原因是因为他们正在设置存在问题的情况:
不幸的是,如上所述,HTMLElement 类在 HTMLElement 实例和用于其默认 asHTML 值的闭包之间创建了一个强引用循环。
然后他们继续通过添加[unowned self]
来解决这个问题:
无主引用是用于解决之前 HTMLElement 示例中的强引用循环的适当捕获方法。
这导致了这个实现:
lazy var asHTML: Void -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
该文档页面很好地描述了何时可以使用保留周期以及何时应该使用unowned
或weak
来摆脱它。
@johndpope
尝试与此实现进行比较,不确定我错过了什么,但我从 Apple 看到的示例倾向于避免使用封闭括号来设置内容。
class EventCell: UITableViewCell {
lazy public var lblName = UILabel(frame: CGRectZero)
lazy public var lblCity = UILabel(frame: CGRect())
lazy public var lblTime = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
}
不同意lazy
应该用于可读性的概念。 它的唯一目的是真正的性能,因为它可能会引入状态问题,例如在闭包中使用可变属性。 您已经在一个地方进行了初始化,它称为指定的初始化。
您正在使用惰性,因为您有一些可能无法执行因此不需要的重物,或者您想延迟其执行,这恰好允许用户中止。
所以很有可能,在您拥有所述类之后,闭包将保留自我,从而造成泄漏。
即使您 100% 确定会调用闭包,也请始终使用弱/无主。
最终,我怀疑编译器会处理这个问题。 一个带有闭包的惰性属性只能执行一次,想想dispatch_once(..)
。 无论如何,这可能就是我将如何滚动。
首先,让我们把内存引用的东西排除在外。 (即使是关于lazy 的热门博客文章也会出现这个错误。我们需要更改Maxime!)由于上述原因,这不会泄漏。 即:闭包被懒惰地执行一次。 如果它被执行,self 被保留然后释放。 如果不执行,则永远不会保留。
下面的示例代码明确显示了这一点:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class Person {
init(name: String) {
self.name = name
print("\(name) is born.")
}
deinit {
print("\(name) dies.")
}
var name: String
lazy var greeting: String = {
return "Hello \(self.name)!"
}()
}
do {
let t = Person(name: "Ray")
print(t.greeting)
}
print ("Next")
// Prints:
// Ray is born.
// Hello Ray!
// Ray dies.
// Next
do {
let t = Person(name: "Ray")
}
print ("End of the world")
// Prints:
// Ray is born.
// Ray dies.
// End of the world
接下来,让我们看看这整个懒惰的事情。 这是控制与视图控制器有关的初始化的好方法,因为您只需在使用时支付费用。 这对于CLLocationManager
尤其重要,因为您可能希望控制何时发生访问,因此它不会在中间请求权限。
另一种看起来不那么 javascript-y 的模式是创建一个私有方法并调用它:
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
几点注意事项:
self
未解决的神秘错误消息。对于未在情节提要中分配或在viewDidLoad()
中无条件分配的 UI 组件,我认为惰性是一个很好的模式。 但我不认为我已经准备好说“所有事情都懒惰”,因为它可能需要一些技巧,比如 LazyBoxes 来解决不变性错误。
我应该提一下,虽然它还没有被接受,但惰性的工作方式可能会发生相当大的变化:
https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md
你怎么看待这件事:
懒惰的
考虑使用延迟初始化来更好地控制对象生命周期。
// Mark: - Location services
extension MyViewController {
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
// :
}
我不介意变量的块样式初始化。 事实上,通过将此类初始化放在变量声明中,可以轻松实现单元/UI 测试覆盖。
顺便说一句,由于分配 manager.delegate = self,将 locationManager 定义为类属性不起作用
你有什么建议?
只需让您的类(视图控制器?)符合 CLLocationManagerDelegate。
@rayfix我已将此片段移至超类。 我错过了将 CLLocationManagerDelegate 协议包含在基础中。
谢谢😀
作为一个 FYI,Swift 的 Joe Groff 已经确认惰性变量是自动的@noescape
所以他们不会捕获自我。
https://twitter.com/jckarter/status/704100315587477504
请记住,您仍然可以通过其他方式捕获自我(例如,在惰性变量中捕获自我的块变量)
只是想让你知道,在 Xcode 8 中使用新的内存图视图时,我发现当我在下面没有使用 unowned self 构造时有一个捕获:
lazy var locationManager: CLLocationManager = {
[unowned self] in
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = Set.shared.trackingAccuracy
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.activityType = .fitness
_locationManager.distanceFilter = Double(Set.shared.waypointInterval)
return _locationManager
}()
但是当我包含它并且没有进行其他更改时,它并没有发生。
@rondiel请注意,CLLocationManager 的委托声明为分配(iOS 10 SDK)
@property(assign, nonatomic, nullable) id<CLLocationManagerDelegate> delegate;
最有用的评论
作为一个 FYI,Swift 的 Joe Groff 已经确认惰性变量是自动的
@noescape
所以他们不会捕获自我。https://twitter.com/jckarter/status/704100315587477504
请记住,您仍然可以通过其他方式捕获自我(例如,在惰性变量中捕获自我的块变量)