弱方法参数语义

时间:2015-06-18 00:43:14

标签: swift arguments automatic-ref-counting weak-references

有没有办法指定特定方法参数具有弱语义?

详细说明,这是一个按预期工作的Objective-C示例代码:

- (void)runTest {  
    __block NSObject *object = [NSObject new];  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
        [self myMethod:object];  
    });  
    // to make sure it happens after `myMethod:` call  
    dispatch_async(dispatch_get_main_queue(), ^{  
        object = nil;  
    });  
}  
- (void)myMethod:(__weak id)arg0 {  
    NSLog(@"%@", arg0); // <NSObject: 0x7fb0bdb1eaa0>  
    sleep(1);  
    NSLog(@"%@", arg0); // nil  
}  

这是Swift版本,不是

public func runTest() {  
    var object: NSObject? = NSObject()  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {  
        self.myMethod(object)  
    }  
    dispatch_async(dispatch_get_main_queue()) {  
        object = nil  
    }  
}  
private func myMethod(arg0: AnyObject?) {  
    println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>)  
    sleep(1)  
    println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>)  
}  

我是否正确假设在Swift版本的方法调用之间没有办法让arg0成为零? 谢谢!

来自Apple Dev.Forums的用户

更新指出sleep不是一个好用的函数,连续调度可能会导致竞争条件。虽然这些可能是合理的问题,但这只是一个示例代码,问题的焦点在于传递弱论点。

3 个答案:

答案 0 :(得分:7)

Swift没有“弱args”......但这可能只是因为Swift(3.0)args是不可变的(相当于let s)而且weak Swift中的东西需要同时为{ {1}}和var

也就是说,确实有一种非常简单的方法来完成与弱args的等价 - 使用Optional本地(释放arg-var以释放)。这是有效的,因为Swift在当前作用域结束之前不会挂起变量(就像C ++一样严格);但是它会在最后一次使用它们之后从范围中释放变量(这有时会使weak var成为PitA,但无论如何)

以下示例在Mac OS 10.11.6上的Xcode 8.2.1上的Swift 3.0.2中一致地工作:

lldb

请注意,如果您在Playground中尝试此操作,操场将在class Test { func runTest() { var object:NSObject? = NSObject() myMethod(arg0: object) DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + 1.0, qos: .userInteractive, flags: DispatchWorkItemFlags.enforceQoS ){ object = nil } } func myMethod(arg0:AnyObject?) { weak var arg0Weak = arg0 // `arg0` get “released” at this point.  Note: It's essential that you // don't use `arg0` in the rest of this method; only use `arg0Weak`. NSLog("\(arg0Weak)"); // Optional(<NSObject: 0x600000000810>) DispatchQueue.main.asyncAfter( deadline: DispatchTime.now() + 2.0, qos: .userInteractive, flags: DispatchWorkItemFlags.enforceQoS ){ NSLog("\(arg0Weak)"); // nil } } } Test().runTest() 开火之前完成执行。让可执行文件无限期运行的最简单方法(我所做的)是创建一个新的Cocoa应用程序并将上面的所有代码粘贴到DispatchQueue 中(是的,逐字的 - Swift允许嵌套在方法内的类定义)

为了回应线程安全讲座,您已经使用func applicationDidFinishLaunching(_:Notification) { … }&amp;在你的例子中dispatch_async来证明弱args确实是真正的交易,这是一个完整的 - sleep - 你的测试的源代码,它是单线程和无队列的:

main.m

答案 1 :(得分:5)

目前暂不使用语言语法。

我认为这种解决方法目前最接近。

public struct Weak<T> where T: AnyObject {
    public weak var object: T?

    public init(_ object: T?) {
        self.object = object
    }
}

func run(_ a: Weak<A>) { 
    guard let a = a.object else { return }
}

答案 2 :(得分:1)

  

有没有办法指定特定方法参数具有弱语义?

这不是你的Objective-C代码示例所做的。你会意外地获得几乎弱的语义,并且你有未定义的行为(竞争条件),真正的弱引用没有。

myMethod可以在任何序列点(第一个NSLog语句或第二个,甚至在某个地方的NSLog中间)向la-la-land发送消息...甚至如果ARC没有忽略arg0的保留,你仍然在主队列释放或更糟糕 - 保留一个僵尸对象。)

将某些内容声明为__block只意味着在堆环境中为块分配一个插槽(因为dispatch_async保证让块转义它将从堆栈分配块提升到堆块,该堆块环境中的一个存储槽将用于__block变量。在ARC下,块将自动调用Block_copy,可能更恰当地命名为Block_copy_to_heap)。

这意味着执行块的两个实例都将指向相同的内存位置。

如果它有所帮助,想象一下这个非常愚蠢的代码,它具有明显的竞争条件。所有尝试修改unsafe的1000个块同时排队。我们几乎可以保证在if块中执行令人讨厌的语句,因为我们的赋值和比较不是原子的,我们正在争夺同一个内存位置。

    static volatile size_t unsafe = 0;

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_apply(1000, queue, ^(size_t instance) {
        unsafe = instance;
        if (unsafe != instance) {
            FORMAT_ALL_STORAGE();
            SEND_RESIGNATION_EMAIL();
            WATCH_THE_WORLD_BURN();
        }
    });

您的Swift示例没有相同的问题,因为不修改值的块可能会捕获(并保留)对象,因此不会看到来自其他块的修改。

由闭包的创建者来处理内存管理后果,所以你不能创建一个在闭包中不强制执行保留循环的API契约,除了在这种情况下标记为@noescape之外Swift不会执行任何保留/释放或其他内存管理,因为该块不会超过当前堆栈帧。由于显而易见的原因,这就排除了异步调度。

如果您想提供解决此问题的API合约,您可以使用类型protocol Consumer: class { func consume(thing: Type) },然后在您的API中保留对Consumer个实例的弱引用。

另一种技术是接受实例函数的curried版本并弱捕获self

protocol Protocol: class { }
typealias FuncType = () -> Void
var _responders = [FuncType]()

func registerResponder<P: Protocol>(responder: P, usingHandler handler: (P) -> () -> Void) {
    _responders.append({ [weak responder] in
        guard let responder = responder else { return }
        handler(responder)()
    })
}

class Inst: Protocol {
    func myFunc() {

    }
}
let inst = Inst()
registerResponder(inst, usingHandler: Inst.myFunc)