这是Apple's document的一个例子:
@place.all_versions.order('created_at DESC')
我理解为什么这个闭包属性会导致强引用周期,我知道如何解决它。我不打算这么说。
让我感到困惑的是以下代码:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
据我所知Swift documentation以及我自己对Objective-c的经验,闭包将捕获变量var heading: HTMLElement? = HTMLElement(name: "h1")
let defaultText = "some default text"
heading!.asHTML = {
// confusing, this closure are supposed to retain heading here, but it does not
return "<\(heading!.name)>\(heading!.text ?? defaultText)</\(heading!.name)>"
}
print(heading!.asHTML())
heading = nil
// we can see the deinialization message here,
// it turns out that there is not any strong reference cycle in this snippet.
,因此应该引起一个强大的参考周期。但事实并非如此,这让我很困惑。
我还写了一个这个例子的Objective-c对应物,它确实引起了我预期的强引用周期。
heading
typedef NSString* (^TagMaker)(void);
@interface HTMLElement : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, strong) TagMaker asHTML;
@end
@implementation HTMLElement
- (void)dealloc {
NSLog(@"%@", [NSString stringWithFormat:@"%@ is being deinitialized", self.name]);
}
@end
任何提示或指南都将不胜感激。
答案 0 :(得分:5)
因为,在后一种情况下
Swift 关闭保留heading
的强引用,而不是指向
heading
实例
在图片中,它显示如下
如果我们按设置heading = nil
打破红线,那么参考圆就会被打破。
更新开始:
但是,如果你没有将标题设置为nil,那么仍然有一个参考圆圈,就像我在上面发布的图像一样。你可以像这样测试它
func testCircle(){
var heading: HTMLElement? = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
}
testCircle()//No dealloc message is printed
更新结束
我还编写了下面的测试代码来证明闭包没有对内存中实例的强引用
var heading: HTMLElement? = HTMLElement(name: "h1")
var heading2 = heading
let defaultText = "some default text"
heading!.asHTML = {
// confusing, this closure are supposed to retain heading here, but it does not
return "<\(heading!.name)>\(heading!.text ?? defaultText)</\(heading!.name)>"
}
let cloureBackup = heading!.asHTML
print(heading!.asHTML())
heading = HTMLElement(name: "h2")
print(cloureBackup())//<h2>some default text</h2>
你会看到来自游乐场的日志
<h1>some default text</h1>
<h2>some default text</h2>
从我的测试和理解中找不到任何关于此的文档,希望它会有所帮助
答案 1 :(得分:3)
我认为devi是详细的。 documentation州:
...可能会发生捕获,因为闭包的主体访问实例的属性,例如
self.someProperty
,或者因为闭包调用实例上的方法,例如self.someMethod()
。
请注意 self
,我相信这是问题的关键。
另一篇documentation建议:
闭包可以从定义它的周围上下文中捕获常量和变量。然后闭包可以引用并修改其体内的常量和变量的值,即使定义常量和变量的原始范围不再存在。
因此,换句话说,它是被捕获的常量和变量,而不是对象本身。它只是self
是一种特殊情况,因为当{<1}}用于从在对象中初始化的闭包中时,那么契约是这样的self
是当触发闭包时,总是。换句话说,在任何情况下都不会发生其身体中self
的闭包确实执行但此self
指向的对象消失的情况。考虑一下:这种封闭可以在其他地方进行,例如:分配给另一个对象的另一个属性,因此即使被捕获对象的原始所有者“忘记”它,它也必须能够运行。要求开发人员检查self
是否self
是对的,对吗?因此需要保持强有力的参考。
现在,如果你转向另一个案例,其中闭包没有使用nil
,但有些(明确解开)可选,那么它完全是另一个球类游戏。这样的可选可以是self
,开发人员必须接受这个事实,并负责处理。当这样的闭包运行时,可能就是它正在使用的可选属性实际上从不甚至被分配了具体的值!那么,持有强有力的参考是什么意思?
举例说明。这是基础课:
nil
这是强参考周期的镜像:
class Foo {
let name: String
lazy var test: Void -> Void = {
print("Running closure from \(self.name)")
}
init(name: String) {
self.name = name
}
}
现在,在对象外部使用可选属性的下一个案例:
var closure: Void -> Void
var captureSelf: Foo? = Foo(name: "captureSelf")
closure = captureSelf!.test
closure() // Prints "Running closure from captureSelf"
captureSelf = nil
closure() // Still prints "Running closure from captureSelf"
..即我们仍然“记住”闭包,但闭包应该能够处理它所使用的属性实际为var tryToCaptureOptional: Foo? = Foo(name: "captureSomeOptional")
tryToCaptureOptional?.test = {
print("Running closure from \(tryToCaptureOptional?.name)")
}
closure = tryToCaptureOptional!.test
closure() // Prints "Running closure from Optional("captureSomeOptional")"
tryToCaptureOptional = nil
closure() // Prints "Running closure from nil"
的情况。
但“乐趣”现在才开始。例如,我们可以这样做:
nil
..换句话说,即使对象不真的“消失”,但只是某个特定的属性不再指向它,所讨论的闭包仍然会适应并采取行动事实上,该属性没有对象 (原始对象仍然存在,它刚刚移动到另一个地址)。
总结一下,我认为“占位符”属性var tryToCaptureAnotherOptional: Foo? = Foo(name: "tryToCaptureAnotherOptional")
var holdItInNonOptional: Foo = tryToCaptureAnotherOptional!
tryToCaptureAnotherOptional?.test = {
print("Running closure from \(tryToCaptureAnotherOptional?.name)")
}
closure = tryToCaptureAnotherOptional!.test
closure() // Prints "Running closure from Optional("tryToCaptureAnotherOptional")"
tryToCaptureAnotherOptional = nil
closure() // Prints "Running closure from nil"
print(holdItInNonOptional.name) // Prints "tryToCaptureAnotherOptional" (!!!)
holdItInNonOptional.test() // Also prints "Running closure from nil"
与其他“具体”属性之间存在差异。后者附有隐含的合同,而前者只有或不是。
答案 2 :(得分:3)
灵感来自@Leo和@Anton Bronnikov的答案,以及Turton先生的这篇文章:
Understanding Optionals in Swift
我发现我所有的困惑来自于我对Swift中Optional Types
的外在理解。
我们可以在Swift文档中看到有关Optional和ImplicitlyUnwrappedOptional的说明以及Optional
的定义:
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
/// Construct a `nil` instance.
public init()
/// Construct a non-`nil` instance that stores `some`.
public init(_ some: Wrapped)
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
/// Create an instance initialized with `nil`.
public init(nilLiteral: ())
}
可选类型,无论是显式展开还是隐式展开,实际上都是 Enumeration ,它被称为值类型。
所以在上面的示例代码中:
var heading: HTMLElement? = HTMLElement(name: "h1")
我们所讨论的变量heading
实际上是枚举,值类型不是对HTMLElement实例的引用。因此,它是一个枚举而不是一个被闭包捕获的引用类型。当然,HTMLElement实例的引用计数还没有在闭包内加起来。
heading!.asHTML = {
return "<\(heading!.name)>\(heading!.text ?? defaultText)</\(heading!.name)>"
}
print(heading!.asHTML())
此时,HTMLElement实例的保留计数为+1,实例由枚举heading
保存。关闭的保留计数为+1,由HTMLElement实例保存。虽然枚举heading
是通过关闭捕获的。参考周期与他的答案中所示的图像@Leo完全相同,
当我们设置heading = nil
时,枚举heading
持有的HTMLElement实例的引用将被释放,实例的引用计数将变为0,然后闭包的引用计数将变为0同样,随后,枚举本身将通过关闭释放。最后,所有东西都会被正确释放。
总结:对于曾经像我这样的Objective-c开发人员的Swift初学者来说,深入了解两种语言之间的差异对我们来说非常重要。非常感谢所有的回复者,你的答案非常鼓舞人心,也很有帮助,谢谢。
作为一个快速的初学者,在这个回复中不可避免地会有一些错误,如果你找到了,请告诉我,因为这对后来的读者来说非常重要。
答案 3 :(得分:1)
您的asHTML是一个包含闭包的变量。闭包拥有对HTMLElement对象的强引用。并且该闭包被存储,再次保持强大的参考。所以你有自己的周期。
您需要做的只是拥有一个返回闭包的函数而不是变量。
或者,您可以声明闭包捕获的值,因此让它捕获self的弱副本。
答案 4 :(得分:0)
我认为swift
在这方面的表现略有不同:
来自this:
如果在闭包的作用域之外声明了任何变量,则在闭包的作用域内引用该变量会创建另一个对该对象的强引用。对此唯一的例外是使用值语义的变量,例如 Swift 中的Ints,Strings,Arrays和Dictionaries。