I am wondering how to do the following correctly: I have a method that is to return an NSData object. It gets the NSData object from a UIDocument. The NSData object can get large, so I want to make sure it is fully loaded before the response starts. I would therefore like to return the value of the method from within the block itself. So something like this:
- (NSData*)getMyData {
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
return doc.myResponseData; // this is to be the return for the method not the block
}
}];
}
This causes an error because the return apparently refers to the block's return.
How can I accomplish this without having to make a thread blocking wait/while loop?
Thanks.
You can't. Embrace the fact that what you're trying to do is asynchronous and add a completion block parameter to your getMyData method which is called when the inner completion handler is called. (And remove the return from the method signature):
- (void)getMyDataWithCompletion:(void(^)(NSData *data))completion {
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
completion((success ? doc.myResponseData : nil));
}];
}
The same problem exists in swift and you can add a similar completion block:
func getMyData(completion: ((data: NSData?) -> Void) {
data = ...
completion(data)
}
The open method is asynchronous which is why you have to provide a block to be run when the open is completed. You need to copy this and make your method also receive a block of code that you will execute when the open is finished.
You should also pass through the success argument of the call you are wrapping or create an error, you need to do this so that the calling code can take the right action.
- (void)getMyDataWithCompletion:(void(^)(NSData *data, BOOL success))completion
{
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
completion(doc.myResponseData, success);
}];
}
Following Are method how to declare method with completionHandler:
Objective-C
- (void)getMyDataWithCompletionHandler:(void(^)(NSString *str))completionHandler
{
completionHandler(#"Test");
}
Swift-3
func showDatePicker(superc: UIViewController, completionHandler:#escaping (String) -> Void) {
completionHandler("Test")
}
Related
I have a simple function that takes a completion handler as a JSValue. It's a JSValue because I'm using this function as part of JSExport protocol.
This function then calls another internal method with another completion handler. When this second handler is called, I want to callWithArguments on the JSValue.
This all works as expected when I callWithArguments from outside the second completion handler, but I get a BAD_ACCESS when calling from the second handler.
func myFunction(completion: JSValue) {
// If I put completion.callWithAttributes([]) here, everything works fine.
self.mySecondFunction(completion: {(result: Dictionary<String, AnyObject>) -> Void in
// If I put completion.callWithAttributes([]) here, I get a BAD_ACCESS
})
}
Any help greatly appreciated. Thanks!
I strongly suggest you to do the following
[self.callback.context[#"setTimeout"]
callWithArguments:#[callback, #0, items]];
when you are going to send response to the JavaScriptCore counterpart. This will prevent the TVML UI MainThread to hang. As you can see it's a call of the setTimeout javascript function with delay 0, your callback and items as parameters like:
setTimeout(callback,0,items)
I'm not sure how you are creating the alert anyways here is one from Apple:
createAlert : function(title, description) {
var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
<button class="btn_close">
<text>OK</text>
</button>
</alertTemplate>
</document>`
var parser = new DOMParser();
var alertDoc = parser.parseFromString(alertString, "application/xml");
return alertDoc
}
There is no direct relationship with the alert and the behavior you are seeing here, it's more a side effect of calling this
completion.callWithArguments([])
in a unexpected way. It's better that you save your completion somewhere, and get a reference to it on the object instance. Then, when the long task ends, you call it. Also if you are performing a long task, it's reasonable that you move everything in a NSOperation like this:
/** JavaScriptCore Callback Operation */
#interface JSCallbackOperation: NSOperation
#property(nonatomic, strong) JSValue*callback;
#property(nonatomic, strong) id items;
#end
#implementation JSCallbackOperation
- (id)initWithItems:(id)items callback:(JSValue*)callback {
if(self = [super init]) {
self.items=items;
self.callback=callback;
}
return self;
}
- (void)main {
#autoreleasepool {
if(self.callback) {
NSLog(#"Dispatching %#", self.callback);
[self.callback.context[#"setTimeout"]
callWithArguments:#[self.callback, #0, self.items]];
}
}
}
At this point you define a helper a call the callbacks with parameters then:
#pragma mark - API Helper
- (void)handleResponseWithItems:(id)items callback:(JSValue*)callback {
NSArray *active_and_pending_operations = operationQueue.operations;
NSInteger count_of_operations = operationQueue.operationCount;
NSLog(#"Running operations: %ld of %ld", active_and_pending_operations.count, count_of_operations);
JSCallbackOperation *op = [[JSCallbackOperation alloc] initWithItems:items callback:callback];
[op setQueuePriority:NSOperationQueuePriorityNormal];
[op setCompletionBlock:^{
NSLog(#"Operation completed.");
}];
[operationQueue addOperation:op];
}
what's is more prefered way to write multi threaded apps. I see two ways.
Implement method with GCD inside and then just simple call (myMethodA), or just implement method and then call it with GCD? Thanks in advance.
My point:
ClassA / method implementation
- (void)myMethodA
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// doSomething1
// doSomething2
});
}
- (void)myMethodB
{
// doSomething1
// doSomething2
}
ClassB / method call
{
[myClassA methodA];
// or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[myClassA methodB];
};
}
IMHO, neither.
The preferred way should be having an object which knowns where to execute its actions:
completion_block_t completionHandler = ^(id result) { ... };
AsyncOperation* op = [AsyncOperation alloc] initWithCompletion:completionHandler];
[op start]; // executes its actions on a private execution context
Then, one can wrap those AsyncOperation objects into a convenient method:
- (void) fetchUsersWithCompletion:(completion_block_t)completionHandler
{
NSDictionary* params = ...;
self.currentOperation = [[HTTPOperation alloc] initWithParams:params
completion:completionHandler];
[self.currentOperation start];
}
The client may only be interested in specifying where its completionHandler should be executed. The API may be enhanced as follows:
- (void) fetchUsersWithQueue:(NSOperationQueue*)handlerQueue
withCompletion:(completion_block_t)completionHandler
{
NSDictionary* params = ...;
self.currentOperation = [[HTTPOperation alloc] initWithParams:params
completion:^(id result){
// As per the documentation of HTTPOperation, the handler will be executed
// on an _unspecified_ execution context.
// Ensure to execute the client's handler on the specified operation queue:
[handlerQueue:addOperationWithBlock:^{
completionHandler(result);
}];
}];
[self.currentOperation start];
}
The latter API can be used as this:
[self fetchUsersWithQueue:[NSOperation mainQueue] completion:^(id result){
self.users = result;
[self.tableView reloadData];
}];
Personal preference. Choose whichever makes the code more readable / understandable / obvious. Also, consideration of whether the code should be possible to run on the 'current' thread or whether it should always be run on a background thread. You need to design your threading configuration, describe it and then implement with that in mind. If you're calling methods between classes like in your example then I'd generally say that any threading should be handled inside that class, not inside the calling class. But that's about distribution of knowledge.
It doesn't make much of a difference - it just depends on what you want to do.
If you want to execute the method on different queues each time, then the myMethodB system is more appropriate. If, however, you always want to run the method on the same queue, then myMethodA will save you time writing code (you only have to write the GCD code once).
Not overriding autosaveWithCompletionHandler:, whenever the document is changed(
[doc updateChangeCount: UIDocumentChangeDone]) autosaveWithCompletionHandler: is periodically called.
But if I override this method, it is called only once.
Document has been changed -> Time is passing... -> Overrided method has been called -> Document has been changed -> Time is passing... -> Time is passing... -> Document has been changed -> Time is passing... -> Time is passing...
I make the document change by calling [doc updateChangeCount: UIDocumentChangeDone].
(overriding method)
- (void) autosaveWithCompletionHandler: (void (^(BOOL success))completionHandler {
if ([self hasUnsavedChanges]) {
[self saveToURL: self.fileURL forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) {
if (success) {
NSLog(#"%# has been autosaved", [self description]);
completionHandler(YES);
}
else {
NSLog(#"Failed to autosave %#", [self description]);
completionHandler(NO);
}
}];
}
} // autosaveWithCompletionHandler:
Thank you for your reading.
You shouldn't be overriding saveWithCompletionHandler: or autosaveWithCompletionHandler:; those methods make changes to private properties which help the system to deterine whether the object needs saving, and when you override the methods those changes don't get made. Instead, you should be overriding contentsForType:error:.
How can I wait for some method to complete and then continue work ??
- (void)loadMoreDataOnBottomOfTableview
{
NSLog(#"LOADING ITEMS ON BOTTOM");
[self refreshStream];
[self.mainTableView reloadData];
...
}
So I need to wait refreshStream method to complete and then reload tableview data and rest of loadMoreDataOnBottomOfTableview (...).
Use a completion block. That's what they were designed for.
See the completion handler section in this guide. http://developer.apple.com/library/ios/#featuredarticles/Short_Practical_Guide_Blocks/index.html
Redefine refreshStream
-(void)refreshStream:(void (^)(void))complete;
-(void)loadMoreDataOnBottomOfTableview
{
[self refreshStream:^{
[self.mainTableView reloadData];
}];
}
This should do you right also check out this page, using typedef is the unspoken standard.
http://developer.apple.com/library/mac/#featuredarticles/BlocksGCD/_index.html
[self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
You can call your method using this.
I can answer your query in swift. Similarly you can use completion block in your code to achieve the task.
class TestClosure
{
func calculateAdditionData() {
countNumbers({ (result) -> Void in
println("[self refreshStream] completed")
//[self.mainTableView reloadData];
})
}
func refreshStream(completion: (() -> Void)!) {
//Your refresh code
completion()
}
}
Completion blocks/Closures are a proper way to wait for something to complete.
You can use performSelectorOnMainThread:withObject:waitUntilDone: as follows:
[self performSelectorOnMainThread:#selector(refreshStream) withObject:nil waitUntilDone:YES]
[self.mainTableView reloadData];
Note however that this is NOT a recommended design pattern. Async calls should use callbacks (your refreshStream method should call back to a method in your view controller which should then trigger reloadData
I have a singleton class the handle all the Game Center logic:
typedef void (^GameCenterCallbackFinishUpdating)();
- (void)getAllMatches:(GameCenterCallbackFinishUpdating)onComplete
{
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
//Do stuff here...
onComplete();
}];
}
From another viewController I use:
[[GameCenterHelper sharedHelper] getAllMatches:^{
[self.myTableView reloadData];
}];
It works great when I'm in the app, but once I close the app (background) and then start it up again, I get:
onComplete(); ---- Thread 1: EXC_BAD_ACCESS (code=2, address=0xc)
What am I doing wrong here?
some background info: the blocks are objects and if any block is nil and you try to call them, it crashes the application.
somewhere and somehow the block onComplete becomes nil before you call it. the following if (...) statement helps you to prevent to call a nil pointer, so the application won't crash.
if (onComplete) onComplete();
Thanks to #holex and #Paul.s for explaining it well.
I had the similar situation where I was sending block as method parameter(completionHandler).
- (void)callX:(NSString *)xyz withCompletionHandler:(void (^)(NSString *response))completion
{
completion(something);
}
And there are two situations either I am using this block like:
[MyClass sharedInstance] callX:#"abc" withCompletionHandler:^(NSString *response) {
if (response) {
//do something
}
}];
or this block could be nil as method parameter:
[MyClass sharedInstance] callX:#"abc" withCompletionHandler:nil];
In second case when block was being passed nil as method parameter this caused EXC_BAD_ACCESS on completion(). So as #holex states that the blocks are objects and if any block is nil and you try to call them, it crashes the application.
A single if saves lot of my time
- (void)callX:(NSString *)xyz withCompletionHandler:(void (^)(NSString *response))completion
{
if (completion)
completion(something);
}
P.S: this explanation only for NERDS like me. | ' L ' |