Semantics of the weak method argument

Is it possible to indicate that the argument of a particular method has weak semantics?

To develop, this is an example of Objective-C 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 a version of Swift that does not

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

Do I correctly assume that the argument arg0 cannot be zero between method calls in the Swift version? Thanks!

Update user from Apple Dev.Forums noted that sleep not a good function to use, and successive submissions can lead to race conditions. Although this may be a reasonable problem, this is just code example, the focus of this issue is on passing weak arguments.

+6
source share
3 answers

Swift does not have "weak args" ... but this is only possible because the Swift (3.0) arguments are immutable (equivalent to let s) and the weak things in Swift must be both a var and Optional .

However, there really is a fairly simple way to fulfill the equivalent of weak args - use a weak var local (which frees arg-var to be released). This works because Swift does not hang on vars until the end of the current region (for example, C ++ is so strict); but rather, it frees vars from its scope after their last use (which makes lldb use PitA sometimes, but whatever).

The following example runs sequentially 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 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() 

Please note that if you try this on the playground, the playground will complete before the DispatchQueue fire. The easiest way to make the executable work indefinitely (which I did) is to create a new Cocoa application and paste all the code above into func applicationDidFinishLaunching(_:Notification) { … } (yes, verbatim-Swift allows class definitions nested inside methods) .sub >


In response to the thread safety lecture you received using dispatch_async and sleep in your example to prove that weak arguments are indeed the real deal here, the full version of main.m -source is your test for single-threaded and no queues:

 #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 
+6
source

There is no language syntax yet.

I think this workaround is the closest at the moment.

 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
source

Is it possible to indicate that the argument of a particular method has weak semantics?

This is not what your Objective-C code example does. You accidentally get almost weak semantics, and you have undefined behavior (race condition) that is not found in real weak links.

myMethod can send a message to la-la-land at any point in the sequence (the first NSLog statement or the second, or even in the middle of NSLog somewhere ... even if the ARC does not disappear while maintaining arg0 , you are still chasing the release of the main queue or worse - save the zombie object).

Declaring something as __block means only allocating a slot in the heap environment for the block (since dispatch_async guaranteed to allow the block to escape, it will move from the block allocated by the stack to the heap block and one of the storage slots in this heap block environment will be for your __block variable In ARC, a block will automatically have Block_copy , possibly aptly named Block_copy_to_heap ).

This means that both instances of the block instance will point to the same memory location.

If this helps, imagine this really stupid code that has an obvious race condition. Each of the 1000 blocks queues all attempts to change unsafe . We are almost guaranteed to make unpleasant statements inside the if block, because our purpose and comparison are not atomic, and we fight for the same place in memory.

  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 does not have the same problem, because a block that does not change the value probably captures (and saves) the object, so it does not see the change from another block.

It is up to the closure creator to deal with the consequences of memory management, so you cannot create an API contract that does not provide for any save cycles in closure other than naming something like @noescape , in which case Swift won't save / release or other memory management, because the block does not survive the current stack stack. This prevents sending an asynchronous message for obvious reasons.

If you want to present an API contract that allows this, you can have a type that accepts the protocol protocol Consumer: class { func consume(thing: Type) } , then weak links to instances of Consumer are stored inside your API.

Another method is to accept the instance version of the instance instance and weak capture of 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) 
+1
source

Source: https://habr.com/ru/post/989308/


All Articles