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.
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 firstNSLog
statement or second, or even in the middle ofNSLog
somewhere... even if ARC doesn't elide the retain ofarg0
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 (becausedispatch_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 haveBlock_copy
called, perhaps more aptly namedBlock_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.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 theConsumer
instances.Another technique is to accept a curried version of an instance function and weakly capture
self
: