弱方法参数语义

问题描述 投票:0回答:3

有什么方法可以指定特定方法参数具有弱语义吗?

详细说明一下,这是一个按预期工作的 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>)  
}  

我的 ym 假设是否正确,即 Swift 版本中的方法调用之间 arg0 无法变为 nil? 谢谢!

Update 来自 Apple Dev.Forums 的用户指出

sleep
不是一个好用的函数,连续调度可能会导致竞争条件。虽然这些可能是合理的担忧,但这只是示例代码,问题的重点是传递弱参数。

swift arguments automatic-ref-counting weak-references
3个回答
9
投票

Swift 没有“弱参数”……但这可能只是因为 Swift (3.0) 参数是不可变的(相当于

let
)并且 Swift 中的
weak
东西需要同时是
var
Optional 
.

也就是说,确实有一种相当简单的方法可以实现与弱参数等效的功能——使用

weak var
本地(这可以释放要释放的arg-var)。这是可行的,因为 Swift 在当前作用域结束之前不会挂起变量(就像 C++ 那样严格执行);而是在最后一次使用变量后从作用域中释放变量(这有时会导致
lldb
-ing成为PitA,但无论如何)

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

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()

请注意,如果您在 Playground 中尝试此操作,Playground 将在

DispatchQueue
触发之前完成执行。让可执行文件无限期运行的最简单方法(我就是这样做的)是创建一个新的 Cocoa 应用程序并将上面的所有代码粘贴到
func applicationDidFinishLaunching(_:Notification) { … }
(是的,逐字逐句 - Swift 允许类定义嵌套在方法中).


为了回应线程安全讲座,您已经在示例中使用了

dispatch_async
sleep
,为了证明弱参数确实是真正的交易,这里有一个完整的
main.m
源变体您的测试是单线程且无队列的:

#import <Foundation/Foundation.h>


@interface Test : NSObject 
- (void)runTest;
- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback;
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[Test new] runTest];
    }
    return 0;
}


@implementation Test

- (void)runTest {
    __block NSObject *object = [NSObject new];
    [self myMethod:object callback:^{
        object = nil;
    }];
}

- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback {
    NSLog(@"%@", arg0); // <NSObject: 0x100400bc0>
    callback();
    NSLog(@"%@", arg0); // (null)
}

@end

9
投票

目前无法使用语言语法。

我认为这个解决方法是目前最接近的解决方法。

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

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

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

1
投票

有什么方法可以指定特定方法参数具有弱语义吗?

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

myMethod
可以在任何序列点(第一个
NSLog
语句或第二个,甚至在
NSLog
中间的某处)向 la-la-land 发送消息......即使 ARC 不消除保留
arg0
,你仍在与主队列释放竞争,或者更糟 - 保留僵尸对象)。

将某些内容声明为

__block
只是意味着在堆环境中为该块分配一个槽(因为
dispatch_async
保证让该块逃逸,它将从堆栈分配的块提升为堆块,并且存储之一该堆块环境中的槽将用于您的
__block
变量。在 ARC 下,该块将自动调用
Block_copy
,也许更恰当地命名为
Block_copy_to_heap
)。

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

如果有帮助的话,想象一下这个非常愚蠢的代码,它有一个明显的竞争条件。有 1000 个块同时排队,都在尝试修改

unsafe
。我们几乎可以保证执行 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
实例的弱引用。

另一种技术是接受实例函数的柯里化版本并弱捕获

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)
© www.soinside.com 2019 - 2024. All rights reserved.