Post

Replies

Boosts

Views

Activity

Reply to What are the benefits to Apple's Dispatch Queue API designing for future functionality?
I suppose you're right since C doesn't support function overloading in the traditional sense. That said, any benefit to dropping down to C rather than exposing the dispatch queues via Objective-C instead? For example, NSNotification exposes the default notification as a class method, why not expose the queues in a similar manner? It seems that these are methods are used in conjunction in the context of Objective C for the most part. I get I can simply write my own wrappers, but just curious from a deeper perspective as to possible motivations behind their architecture. Is there a huge performance hit or something I'm missing?
Jun ’21
Reply to Variable not captured unless referencing explicitly from local assignment
Task was just a wrapper for a BOOL showing implementation that worked, but you make a good point that it was somewhat irrelevant to what I was trying to get to. Here it is distilled down: typedef void(^MyBlock)(void); - (void)primitiveStopBlockTest { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); __block BOOL stop; [self primitive_cancellable_dispatch_after:delay block:^{ NSLog(@"Ran primitive implementation with calling inline. (stop: %@ / %p)", @(stop), &stop); } stop:&stop]; stop = YES; } - (void)primitiveStopBlockWithLocalAssignmentTest { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); __block BOOL stop; MyBlock blockWithLocalAssignment = ^{ NSLog(@"Ran primitive implementation with local assignment. (stop: %@ / %p)", @(stop), &stop); }; [self primitive_cancellable_dispatch_after:delay block:blockWithLocalAssignment stop:&stop]; stop = YES; } - (void)primitive_cancellable_dispatch_after:(dispatch_time_t)delay block:(MyBlock)block stop:(BOOL*)stop { dispatch_after(delay, dispatch_get_main_queue(), ^{ BOOL shouldStop = *stop; if(block && !shouldStop) { NSLog(@"Executing block. (shouldStop: %@ / %p) (block: %p)", @(shouldStop), &shouldStop, &block); block(); } else { NSLog(@"Block execution cancelled. (shouldStop: %@ / %p) (block: %p)", @(shouldStop), &shouldStop, &block); } }); } Output for primitiveStopBlockTest is: Executing block. (shouldStop: 0 / 0x16bc04bef) (block: 0x600003095340) Ran primitive implementation with calling inline. (stop: 1 / 0x600003edaf18) Output for primitiveStopBlockWithLocalAssignmentTest is: Block execution cancelled. (shouldStop: 1 / 0x16b574bef) (block: 0x600002adcd70) I am just curious as to why stop is getting set successfully in the primitiveStopBlockWithLocalAssignmentTest function but not the primitiveStopBlockTest function.
Jun ’21
Reply to Variable not captured unless referencing explicitly from local assignment
I setup a new project in the same method you described and ran with different results. I am on Xcode 12.5, macOS 11.4 as well (using MacBook Air (M1, 2020)). main.m: #import <Foundation/Foundation.h> typedef void(^MyBlock)(void); @interface Main : NSObject - (void)primitiveStopBlockTest; - (void)primitiveStopBlockWithLocalAssignmentTest; @end @implementation Main - (void)primitiveStopBlockTest { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); __block BOOL stop; [self primitive_cancellable_dispatch_after:delay block:^{ NSLog(@"Ran primitive implementation with calling inline. (stop: %@ / %p)", @(stop), &stop); } stop:&stop]; stop = YES; } - (void)primitiveStopBlockWithLocalAssignmentTest { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); __block BOOL stop; MyBlock blockWithLocalAssignment = ^{ NSLog(@"Ran primitive implementation with local assignment. (stop: %@ / %p)", @(stop), &stop); }; [self primitive_cancellable_dispatch_after:delay block:blockWithLocalAssignment stop:&stop]; stop = YES; } - (void)primitive_cancellable_dispatch_after:(dispatch_time_t)delay block:(MyBlock)block stop:(BOOL*)stop { dispatch_after(delay, dispatch_get_main_queue(), ^{ BOOL shouldStop = *stop; if(block && !shouldStop) { NSLog(@"Executing block. (shouldStop: %@ / %p) (block: %p)", @(shouldStop), &shouldStop, &block); block(); } else { NSLog(@"Block execution cancelled. (shouldStop: %@ / %p) (block: %p)", @(shouldStop), &shouldStop, &block); } }); } @end int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"will start"); Main *m = [[Main alloc] init]; [m primitiveStopBlockTest]; [m primitiveStopBlockWithLocalAssignmentTest]; dispatch_main(); } return 0; } Output: 2021-06-18 17:31:36.054948-0700 Test[12721:383872] will start 2021-06-18 17:31:38.234779-0700 Test[12721:384213] Executing block. (shouldStop: 0 / 0x16fe86b9f) (block: 0x10067a580) 2021-06-18 17:31:38.235058-0700 Test[12721:384213] Ran primitive implementation with calling inline. (stop: 1 / 0x10067a5a8) 2021-06-18 17:31:38.235157-0700 Test[12721:384213] Block execution cancelled. (shouldStop: 1 / 0x16fe86b9f) (block: 0x10067acb0) I will email you the project itself as well.
Jun ’21
Reply to Variable not captured unless referencing explicitly from local assignment
From my understanding, in theory, our code should be doing everything on the main thread, so why is the undefined behavior of concern? Why is it switching to a different thread in practice when we are sending dispatch_get_main_queue() to the dispatch_queue_t queue parameter of dispatch_after? In reference to the ARC timing issue—is creating a locally assigned block and referencing the __block variable (as demonstrated in primitiveStopBlockWithLocalAssignmentTest) forcing the correct timing or is this unrelated? In general, what is the timing and what can we reliably conclude across different architectures? When you mention "something that’s appropriate to be shared between threads," Apple itself uses primitives in its example of demonstrating block storage. Is there something I'm misunderstanding or should the caveat be added to the documentation? I had a hunch that it had something to do with some specific quirks I was glossing over, which is what I was alluding to first with the original post with the Task object which was essentially a object wrapper for BOOL. Interestingly though, the property was marked as nonatomic but it still worked fine on my machine. What originally spawned all this was I that I was originally trying to implement some variation of enumerateObjectsUsingBlock: (documentation) but lack of exact remembering caused me to write the function as sending the stop as part of the function rather than as part of the block. Is it better practice / safer to send primitive variables as part of the block inline vs. as a function parameter? In regards to undefined behavior, I was always somewhat aware of issues between system architectures in regards to data types, but had not considered it in the context of multithreading so thanks for bringing that to my attention.
Jun ’21
Reply to Variable not captured unless referencing explicitly from local assignment
Got it, I've never actually seen dispatch_main used (as an iOS Developer), from what I am reading, this routine is never really used in any production apps, correct? ARC works hard to automate this. If it sees that a block might escape it does the copy for you. I think you’ve hit a situation where that’s not working properly. In regards, to ARC, you mentioned that it works hard to automate things, but it seems this is at the expense of giving up exact control. Is it a bug in the language that it’s not capturing the primitive BOOL variable? In my original example of code, I implemented the Task wrapper object and it seemed the system had no issues seeing changes to the shouldCancel property. So, my specific advice here is that you change stop from being a simple BOOL to something that’s appropriate to be shared between threads (something protected by locks, an atomic, a Dispatch ‘or’ source, or whatever). I suspect that you’ll end up fixing this problem in the process. I followed your link and didn’t see a specific problem. The doc has code that uses __block and code that uses queues but it doesn’t seem to mix them in an undefined way. You mentioned not to use BOOL, and the documentation uses char: __block char localCharacter; So, I was just curious as to where you were going with that or if I’m misunderstanding. By primitive, I am referring to things like int, char, BOOL, etc. BOOL is essentially just defined as: typedef signed char BOOL;  Objective-C code can simply send the -copy message to the block Like this? I don't think I've ever seen this done in practice, but it seemed to work. - (void)primitiveStopBlockWithLocalAssignmentAndCopyTest { dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); __block BOOL stop; [self primitive_cancellable_dispatch_after:delay block:[^{ NSLog(@"Ran primitive implementation with calling inline. (stop: %@ / %p)", @(stop), &stop); } copy] stop:&stop]; stop = YES; } I suspect that the final answer here will be that this pattern — having a __block variable that you access by capturing it in an escaping block and via a pointer — is causing problems and that the way to avoid those problems would be to stop doing that. This is essentially what I have came to the conclusion as well. The paradigm worked for objects but not primitives unless explicitly creating a local block and referencing it, or just manually send the block the copy message. It just seems messy because if I am essentially passing variables through, I had to locally reference the variables and do a log, if I didn't want that, I pretty much had to do a no-op with the variable to quiet the compiler warnings. Overall, I definitely understand workarounds, solutions, etc. in getting what I am trying to do to work, but I am trying to understand fundamentally why the primitive variable was not captured properly to determine if my mental model of how everything works is flawed. Is it better practice / safer to send primitive variables as part of the block inline vs. as a function parameter? I don’t understand this part of your question. Please elaborate. Wondering if it's safer to implement by coalescing the block and primitive variable like this: typedef void(^MyStoppableBlock)(MyBlock block, BOOL *stop); - (void)primitive_cancellable_dispatch_after:(dispatch_time_t)delay block:(MyStoppableBlock)block; Rather than the original: - (void)primitive_cancellable_dispatch_after:(dispatch_time_t)delay block:(MyBlock)block stop:(BOOL*)stop;
Jun ’21