8

Is there any way to specify that a particular method argument has weak semantics?

To elaborate, this is an Objective-C sample code that works as expected:

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

This is the Swift version, that doesn't

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

Am I correct in ym assumption that there is no way for the arg0 to become nil between the method calls in Swift version? Thank you!

Update a user from Apple Dev.Forums pointed out that sleep is not a good function to use and consecutive dispatches might cause race conditions. While those might be reasonable concerns, this is just a sample code, the focus of the question is on passing weak arguments.

Sash Zats
  • 5,376
  • 2
  • 28
  • 42
  • It's not possible at the moment, you could do it before Swift 2.0 with `[weak yourObject]` at the start of the closure. See also [here](http://stackoverflow.com/questions/24717460/cant-make-weak-reference-to-closure-in-swift) where they show some workarounds – Kametrixom Jun 18 '15 at 01:08
  • I understand that weak closure might be a solution but it doesn't solve the problem, I don't want all the consumers of the API to be aware of my implementation details. Is it possible in Swift 2 though? – Sash Zats Jun 18 '15 at 01:59
  • How can consumers be aware of your API implementation like this? – Kametrixom Jun 18 '15 at 10:12
  • because it seems to be impossible to encapsulate weak semantics in the function signature, meaning that consumer of my API will always need to be aware of the passing a weak reference. Otherwise I'm extending lifecycle of the passed argument for the lifetime of the function body – Sash Zats Jun 18 '15 at 15:16
  • Why do you think the second dispatch_async call happens before myMethod: is called? Likely, yes - guaranteed? No, I think. – Eiko Jun 18 '15 at 15:29
  • please see my update, imagine nested `dispatch_async` calls instead – Sash Zats Jun 18 '15 at 15:31
  • There are no guarantees about when an object will be deallocated; there are only guarantees about when an object will *not* be deallocated. Even when you think your code doesn't have any more strong references to something, there is no way to know whether or not some API that you called is holding a reference in a cache somewhere or in an autorelease pool. So no code should rely on an object being already deallocated at a given point, and thus there is no need to make local variables weak references. – newacct Jun 22 '15 at 22:12
  • I think you are talking about autorelease pool objects, in most of the cases object wouldn't even be placed in the pool. You can check the disassembly of any ARC-enabled code: you will find all the `retain`s and `release`s you didn't add explicitly. – Sash Zats Jun 22 '15 at 22:16

3 Answers3

9

Swift doesn't have “weak args”… but that's probably only because Swift (3.0) args are immutable (equivalent to lets) and weak things in Swift need to be both a var and an Optional.

That said, there is indeed a pretty-easy way to accomplish an equivalent to weak args— use a weak var local (which frees up the arg-var to be released).  This works because Swift doesn't hang onto vars until the end of the current scope (like C++ so strictly does); but rather it releases vars from the scope after the last usage of them (which makes lldb-ing a PitA sometimes, but whatever).

The following example works consistently in Swift 3.0.2 on Xcode 8.2.1 on macOS 10.11.6:

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

Note that if you try this in a Playground, the playground will finish execution before the DispatchQueues fire.  The simplest way to get the executable to run indefinitely (what I did) is create a new Cocoa application and paste all the code above into the func applicationDidFinishLaunching(_:Notification) { … } (yes, verbatim— Swift allows class definitions nested inside of methods).


In response to the thread-safety-lecturing you've gotten over using dispatch_async & sleep in your example, to prove that weak args are indeed the real deal here's a complete-main.m-source variant of your test that's single-threaded and queue-free:

#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
Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
  • I believe you could also avoid reuse by shadowing the argument: i.e. weak var arg0 = arg0 – Learn OpenGL ES Nov 28 '17 at 01:29
  • My test fails for this case: https://gist.github.com/garvankeeley/e85c2adb8e3079e4fe3c17ae62df478c – Garvan Keeley Dec 05 '17 at 21:50
  • "This works because Swift doesn't hang onto vars until the end of the current scope" --> do you have docs for that? – Garvan Keeley Dec 05 '17 at 21:57
  • @GarvanKeeley Nope, just observation of behavior, especially with LLDB. There have been many times when I've wanted to check a var's value but I've stepped beyond the last usage and it's been deallocated. It's possible that things may have changed in Swift 4 (though I doubt it), nevertheless I've observed this frequently in Swift 2 & 3. – Slipp D. Thompson Dec 24 '17 at 23:30
9

No way with language syntax for now.

I think this workaround is closest one for now.

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 }
}
Ahmadreza
  • 6,950
  • 5
  • 50
  • 69
eonil
  • 83,476
  • 81
  • 317
  • 516
1

Is there any way to specify that a particular method argument has weak semantics?

That isn't what your Objective-C code example is doing. You're getting accidentally almost-weak semantics and you have undefined behavior (race condition) that real weak references don't have.

myMethod can send a message into la-la-land at any sequence point (the first NSLog statement or second, or even in the middle of NSLog somewhere... even if ARC doesn't elide the retain of arg0 you're still racing the main queue release or worse - retaining a zombie object).

Declaring something as __block just means allocate a slot in the heap environment for the block (because dispatch_async is guaranteed to let the block escape it will promote from a stack-allocated block to a heap block, and one of the storage slots in that heap block environment will be for your __block variable. Under ARC the block will automatically have Block_copy called, perhaps more aptly named Block_copy_to_heap).

This means both executing instances of the block will point to this same memory location.

If it helps, imagine this really silly code which has an obvious race condition. There are 1000 blocks queued concurrently all trying to modify unsafe. We're almost guaranteed to execute the nasty statements inside the if block because our assignment and comparison are not atomic and we're fighting over the same memory location.

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

Your Swift example doesn't have the same problem because the block that doesn't modify the value probably captures (and retains) the object so doesn't see the modification from the other block.

It is up to the creator of a closure to deal with the memory management consequences so you can't create an API contract that enforces no retain cycles in a closure, other than marking something as @noescape in which case Swift won't do any retain/release or other memory management because the block doesn't outlive the current stack frame. That precludes async dispatch for obvious reasons.

If you want to present an API contract that solves this you can have a type adopt a protocol protocol Consumer: class { func consume(thing: Type) } then inside your API keep a weak reference to the Consumer instances.

Another technique is to accept a curried version of an instance function and weakly capture 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)
russbishop
  • 16,587
  • 7
  • 61
  • 74
  • I'm not sure this answers the question – doozMen Nov 16 '16 at 09:12
  • Technically you are correct because the original question is based on a misunderstanding of what was actually going on and contains thread and memory safety errors; there is no such thing as a weak method parameter which I tried to explain clearly in my answer. I thought it would be beneficial to explain in detail so everyone could learn. – russbishop Nov 17 '16 at 21:24
  • @russbishop If “there is no such thing as a weak method parameter” then why does the compiler allow the `__weak` keyword on the method parameter? Normally, Clang throws errors if you put kewords in the wrong places. – Slipp D. Thompson Jan 31 '17 at 19:33