I am using NSURLSessionUploadTask to upload an image.
The upload works. The image is successfully posted to my server.
This is the method that is called creates a new NSURLSessionUploadTask. The NSData is the image in NSData format, and the withPostRequest: is a NSMutableRequest with all the junk that goes in that.
- (void) uploadImage:(NSData*)data withPostRequest:(NSMutableURLRequest*)postRequest {
NSURLSessionUploadTask * uploadTask = [_session uploadTaskWithRequest:postRequest fromData:data completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
//MAIN THREAD ACCESS UPDATE TO THE GUI OR DISPLAY ERRORS
dispatch_async(dispatch_get_main_queue(), ^{
//UPDATE THE GUI ON COMPLETION
});
}];
// START THE TASK
[uploadTask resume];
}
This delegate method gets called with the upload to show the progress. This method IS called upon ever successful byte sent.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
NSData * taskData = [task.originalRequest HTTPBody];
NSLog(#"Task Data: %#", taskData);
}
However, if I have multiple uploads, I need to be able to check which upload has the correct progress. I feel like you can do this by comparing the task.originalRequest NSData, with the known NSData of the passed Image.
However, when the above method is called, [task.originalRequest HTTPBody] is NULL
Does anyone know why that would be null?
You can subclass the NSSessionUploadTask and have an identifier assocciated with it.
#interface NSSessionUploadTaskId : NSObject {
NSUInteger taskIdentifier;
}
NSURLSessionTask also has taskIdentifier property but I'm not sure how to use that.
#property(readonly) NSUInteger taskIdentifier;
An identifier uniquely identifies the task within a given session.
Related
I'm trying to make an equivalent to the .NET recognize() call, which is synchronous, for ios in objective-c. I found code to recognize speech but the string that was recognized is only inside a block.
I've tried making the block not a block (it seems to be part of the API that it be a block), making __block variables and returning their values, also out parameters in the caller/declarer of the block; finally I wrote a file while in the block and read the file outside. It still didn't work like I want because of being asynchronous although I at least got some data out. I also tried writing to a global variable from inside the block and reading it outside.
I'm using code from here: How to implement speech-to-text via Speech framework, which is (before I mangled it):
/*!
* #brief Starts listening and recognizing user input through the
* phone's microphone
*/
- (void)startListening {
// Initialize the AVAudioEngine
audioEngine = [[AVAudioEngine alloc] init];
// Make sure there's not a recognition task already running
if (recognitionTask) {
[recognitionTask cancel];
recognitionTask = nil;
}
// Starts an AVAudio Session
NSError *error;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryRecord error:&error];
[audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
// Starts a recognition process, in the block it logs the input or stops the audio
// process if there's an error.
recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
AVAudioInputNode *inputNode = audioEngine.inputNode;
recognitionRequest.shouldReportPartialResults = YES;
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
BOOL isFinal = NO;
if (result) {
// Whatever you say in the microphone after pressing the button should be being logged
// in the console.
NSLog(#"RESULT:%#",result.bestTranscription.formattedString);
isFinal = !result.isFinal;
}
if (error) {
[audioEngine stop];
[inputNode removeTapOnBus:0];
recognitionRequest = nil;
recognitionTask = nil;
}
}];
// Sets the recording format
AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
[inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[recognitionRequest appendAudioPCMBuffer:buffer];
}];
// Starts the audio engine, i.e. it starts listening.
[audioEngine prepare];
[audioEngine startAndReturnError:&error];
NSLog(#"Say Something, I'm listening");
}
I want to call Listen(), (like startListening() above), have it block execution until done, and have it return the string that was said. But actually I would be thrilled just to get result.bestTranscription.formattedString somehow to the caller of startListening().
I'd recommend you to take another approach. In Objective-C having a function that blocks for a long period of time is an anti-pattern.
In this language there's no async/await, nor cooperative multitasking, so blocking for long-ish periods of time might lead to resource leaks and deadlocks. Moreover if done on the main thread (where the app UI runs), the app might be forcefully killed by the system due to being non-responsive.
You should use some asynchronous patterns such as delegates or callbacks.
You might also try using some promises library to linearize your code a bit, and make it look "sequential".
The easiest approach with callbacks would be to pass a completion block to your "recognize" function and call it with the result string when it finishes:
- (void)recognizeWithCompletion:(void (^)(NSString *resultString, NSError *error))completion {
...
recognitionTask = [speechRecognizer recognitionTaskWithRequest:recognitionRequest
resultHandler:^(SFSpeechRecognitionResult *result, NSError *error)
{
...
dispatch_async(dispatch_get_main_queue(), ^{
completion(result.bestTranscription.formattedString, error);
});
...
}];
...
}
Note that the 2nd parameter (NSError) - is an error in case the caller wants to react on that too.
Caller side of this:
// client side - add this to your UI code somewhere
__weak typeof(self) weakSelf = self;
[self recognizeWithCompletion:^(NSString *resultString, NSError *error) {
if (!error) {
[weakSelf processCommand:resultString];
}
}];
// separate method
- (void)processCommand:(NSString *command) {
// can do your processing here based on the text
...
}
I'm trying to write a simple (toy) program that uses the NSFilePresenter and NSFileCoordinator methods to watch a file for changes.
The program consists of a text view that loads a (hardcoded) text file and a button that will save the file with any changes. The idea is that I have two instances running and saving in one instance will cause the other instance to reload the changed file.
Loading and saving the file works fine but the NSFilePresenter methods are never called. It is all based around a class called FileManager which implements the NSFilePresenter protocol. The code is as follows:
Interface:
#interface FileManager : NSObject <NSFilePresenter>
#property (unsafe_unretained) IBOutlet NSTextView *textView;
- (void) saveFile;
- (void) reloadFile;
#end
Implementation:
#implementation FileManager
{
NSOperationQueue* queue;
NSURL* fileURL;
}
- (id) init {
self = [super init];
if (self) {
self->queue = [NSOperationQueue new];
self->fileURL = [NSURL URLWithString:#"/Users/Jonathan/file.txt"];
[NSFileCoordinator addFilePresenter:self];
}
return self;
}
- (NSURL*) presentedItemURL {
NSLog(#"presentedItemURL");
return self->fileURL;
}
- (NSOperationQueue*) presentedItemOperationQueue {
NSLog(#"presentedItemOperationQueue");
return self->queue;
}
- (void) saveFile {
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
[coordinator coordinateWritingItemAtURL:self->fileURL options:NSFileCoordinatorWritingForMerging error:&error byAccessor:^(NSURL* url) {
NSString* content = [self.textView string];
[content writeToFile:[url path] atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}];
}
- (void) reloadFile {
NSFileManager* fileManager = [NSFileManager defaultManager];
NSFileCoordinator* coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:self];
NSError* error;
__block NSData* content;
[coordinator coordinateReadingItemAtURL:self->fileURL options:0 error:&error byAccessor:^(NSURL* url) {
if ([fileManager fileExistsAtPath:[url path]]) {
content = [fileManager contentsAtPath:[url path]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setString:[[NSString alloc] initWithData:content encoding:NSUTF8StringEncoding]];
});
}
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
#end
Note, reloadFile is called in applicationDidFinishLaunching and saveFile gets called every time the save button is click (via the app delegate).
The only NSFilePresenter method that ever gets called (going by the logs) is presentedItemURL (which gets called four times when the program starts and loads the file and three times whenever save is clicked. Clicking save in a second instance has no noticeable effect on the first instance.
Can anyone tell me what I'm doing wrong here?
I was struggling with this exact issue for quite a while. For me, the only method that would be called was -presentedSubitemDidChangeAtURL: (I was monitoring a directory rather than a file). I opened a technical support issue with Apple, and their response was that this is a bug, and the only thing we can do right now is to do everything through -presentedSubitemDidChangeAtURL: if you're monitoring a directory. Not sure what can be done when monitoring a file.
I would encourage anyone encountering this issue to file a bug (https://bugreport.apple.com) to encourage Apple to get this problem fixed as soon as possible.
(I realize that this is an old question, but... :) )
First of all, I notice you don't have [NSFileCoordinator removeFilePresenter:self]; anywhere (it should be in dealloc).
Secondly, you wrote:
// After this I implement *every* method in the NSFilePresenter protocol. Each one
// simply logs its method name (so I can see it has been called) and calls reloadFile
// (not the correct implementation for all of them I know, but good enough for now).
You're right: it's the incorrect implementation! And you're wrong: it's not good enough, because it's essential for methods like accommodatePresentedItemDeletionWithCompletionHandler: which take a completion block as a parameter, that you actually call this completion block whenever you implement them, e.g.
- (void) savePresentedItemChangesWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler
{
// implement your save routine here, but only if you need to!
if ( dataHasChanged ) [self save]; // <-- meta code
//
NSError * err = nil; // <-- = no error, in this simple implementation
completionHandler(err); // <-- essential!
}
I don't know whether this is the reason your protocol methods are not being called, but it's certainly a place to start. Well, assuming you haven't already worked out what was wrong in the past three years! :-)
My app creates an object (PFUSER) for each user, and an (PF) object for each event they participate in. This works fine. then i have two files associated with that event. i save the first file to a PFFile, then associate it to the event pfobject. when i use blocks and do this in the background, how can then make sure control continues to do the same for the second file?
I am new to blocks so maybe it would be clearer to me why its not working with callbacks, but it seems the block runs the save in another thread and the current one is abandoned before the next steps are taken.
Of course i'd like to do both of these as "save eventually" to allow offline use.
any guidance / examples you can point me to greatly appreciated.
thanks!
saveEventually doesn't support PFFiles yet; it needs a bit more smarts to handle resuming uploads between restarts. One trick that is already available, however, is that PFObject knows how to save its children, including PFFiles. You can just say:
PFUser *user = PFUser.currentUser;
user[#"icon"] = [PFFile fileWithData:iconData];
user[#"iconThumb"] = [PFFile fileWithData:iconThumbData];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// user will automatically save its files & only call this once the
// entire operation succeeds.
}];
I'm not 100% what you mean because you didn't post any codes, but I'd imagine if you want to associate multiple PFFile to PFObject this is all you have to do:
PFObject *object = [PFQuery getObjectOfClass:#"MyFile" objectId:id];
[object addObject:profilePicture forKey:#"Photo"];
[object addObject:coverPicture forKey:#"PhotoCover"];
[object saveEventually];
From Parse's documentation it seems like saveEventually does what you want:
Saves this object to the server at some unspecified time in the
future, even if Parse is currently inaccessible. Use this when you may
not have a solid network connection, and don’t need to know when the
save completes. If there is some problem with the object such that it
can’t be saved, it will be silently discarded. If the save completes
successfully while the object is still in memory, then callback will
be called.
As currently neither saveEvetually nor saving to the local data store are supported, below is a category of PFObject I am using to at least save offline what can be saved or returning error:
- (void) dr_saveWithCompletionHandler: (void(^)(NSError* error)) completionBlock {
__block BOOL canSaveEventually = YES;
[[self allKeys] enumerateObjectsUsingBlock:^(NSString* key, NSUInteger idx, BOOL *stop) {
id object = self[key];
if ([object isKindOfClass:[PFFile class]]) {
PFFile* file = (PFFile*) object;
if (!file.url || file.isDirty) {
canSaveEventually = NO;
}
}
}];
void (^localCompletionHandler) (BOOL, NSError*) = ^(BOOL succeeded, NSError *error) {
if (succeeded) {
if (completionBlock) completionBlock(nil);
} else {
if (completionBlock) completionBlock(error);
}
};
if (canSaveEventually) {
[self saveEventually:localCompletionHandler];
} else {
[self saveInBackgroundWithBlock:localCompletionHandler];
}
}
This is my first question on Stack Overflow, so please excuse me if I'm breaking any etiquette. I'm also fairly new to Objective-C/app creation.
I have been following the CS193P Stanford course, in particular, the CoreData lectures/demos. In Paul Hegarty's Photomania app, he starts with a table view, and populates the data in the background, without any interruption to the UI flow. I have been creating an application which lists businesses in the local area (from an api that returns JSON data).
I have created the categories as per Paul's photo/photographer classes. The creation of the classes themselves is not an issue, it's where they are being created.
A simplified data structure:
- Section
- Sub-section
- business
- business
- business
- business
- business
- business
My application starts with a UIViewController with several buttons, each of which opens a tableview for the corresponding section (these all work fine, I'm trying to provide enough information so that my question makes sense). I call a helper method to create/open the URL for the UIManagedDocument, which was based on this question. This is called as soon as the application runs, and it loads up quickly.
I have a method very similar to Paul's fetchFlickrDataIntoDocument:
-(void)refreshBusinessesInDocument:(UIManagedDocument *)document
{
dispatch_queue_t refreshBusinessQ = dispatch_queue_create("Refresh Business Listing", NULL);
dispatch_async(refreshBusinessQ, ^{
// Get latest business listing
myFunctions *myFunctions = [[myFunctions alloc] init];
NSArray *businesses = [myFunctions arrayOfBusinesses];
// Run IN document's thread
[document.managedObjectContext performBlock:^{
// Loop through new businesses and insert
for (NSDictionary *businessData in businesses) {
[Business businessWithJSONInfo:businessData inManageObjectContext:document.managedObjectContext];
}
// Explicitly save the document.
[document saveToURL:document.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success){
if (!success) {
NSLog(#"Document save failed");
}
}];
NSLog(#"Inserted Businesses");
}];
});
dispatch_release(refreshBusinessQ);
}
[myFunctions arrayOfBusinesses] just parses the JSON data and returns an NSArray containing individual businessses.
I have run the code with an NSLog at the start and end of the business creation code. Each business is assigned a section, takes 0.006 seconds to create, and there are several hundred of these. The insert ends up taking about 2 seconds.
The Helper Method is here:
// The following typedef has been defined in the .h file
// typedef void (^completion_block_t)(UIManagedDocument *document);
#implementation ManagedDocumentHelper
+(void)openDocument:(NSString *)documentName UsingBlock:(completion_block_t)completionBlock
{
// Get URL for document -> "<Documents directory>/<documentName>"
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:documentName];
// Attempt retrieval of existing document
UIManagedDocument *doc = [managedDocumentDictionary objectForKey:documentName];
// If no UIManagedDocument, create
if (!doc)
{
// Create with document at URL
doc = [[UIManagedDocument alloc] initWithFileURL:url];
// Save in managedDocumentDictionary
[managedDocumentDictionary setObject:doc forKey:documentName];
}
// If the document exists on disk
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]])
{
[doc openWithCompletionHandler:^(BOOL success)
{
// Run completion block
completionBlock(doc);
} ];
}
else
{
// Save temporary document to documents directory
[doc saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
// Run compeltion block
completionBlock(doc);
}];
}
}
And is called in viewDidLoad:
if (!self.lgtbDatabase) {
[ManagedDocumentHelper openDocument:#"DefaultLGTBDatabase" UsingBlock:^(UIManagedDocument *document){
[self useDocument:document];
}];
}
useDocument just sets self.document to the provided document.
I would like to alter this code to so that the data is inserted in another thread, and the user can still click a button to view a section, without the data import hanging the UI.
Any help would be appreciated I have worked on this issue for a couple of days and not been able to solve it, even with the other similar questions on here. If there's any other information you require, please let me know!
Thank you
EDIT:
So far this question has received one down vote. If there is a way I could improve this question, or someone knows of a question I've not been able to find, could you please comment as to how or where? If there is another reason you are downvoting, please let me know, as I'm not able to understand the negativity, and would love to learn how to contribute better.
There are a couple of ways to this.
Since you are using UIManagedDocument you could take advantage of NSPrivateQueueConcurrencyType for initialize a new NSManagedObjectContext and use performBlock to do your stuff. For example:
// create a context with a private queue so access happens on a separate thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// insert this context into the current context hierarchy
context.parentContext = parentContext;
// execute the block on the queue of the context
context.performBlock:^{
// do your stuff (e.g. a long import operation)
// save the context here
// with parent/child contexts saving a context push the changes out of the current context
NSError* error = nil;
[context save:&error];
}];
When you save from the context, data of the private context are pushed to the current context. The saving is only visible in memory, so you need to access the main context (the one linked to the UIDocument) and do a save there (take a look at does-a-core-data-parent-managedobjectcontext-need-to-share-a-concurrency-type-wi).
The other way (my favourite one) is to create a NSOperation subclass and do stuff there. For example, declare a NSOperation subclass like the following:
//.h
#interface MyOperation : NSOperation
- (id)initWithDocument:(UIManagedDocument*)document;
#end
//.m
#interface MyOperation()
#property (nonatomic, weak) UIManagedDocument *document;
#end
- (id)initWithDocument:(UIManagedDocument*)doc;
{
if (!(self = [super init])) return nil;
[self setDocument:doc];
return self;
}
- (void)main
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setParentContext:[[self document] managedObjectContext]];
// do the long stuff here...
NSError *error = nil;
[moc save:&error];
NSManagedObjectContext *mainMOC = [[self document] managedObjectContext];
[mainMOC performBlock:^{
NSError *error = nil;
[mainMOC save:&error];
}];
// maybe you want to notify the main thread you have finished to import data, if you post a notification remember to deal with it in the main thread...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Hope that helps.
Initially I was just going to leave a comment, but I guess I don't have the privileges for it. I just wanted to point out the UIDocument, beyond the change count offers
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
Which shouldn't have the delay I've experienced with updating the change count as it waits for a "convenient moment".
I'm using an AFNetworking client object which makes an asynchronous request for an XML document and parses it.
Also using NSNotificationCenter to post a notification when the document has finished parsing.
Is there a way to wait for a notification to be posted without blocking the main thread?
E.g code:
-(void)saveConfiguration:(id)sender {
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching....this works.
[RESTClient fetchActiveUser:[usernameTextField stringValue] withPassword:[passwordTextField stringValue]];
/*
* What do I need here ? while (xxx) ?
*/
NSLog(#"Fetch Complete.");
}
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
As it is right now I'll see "Fetch Complete." in the debug console before the fetch has been completed.
I tried adding a BOOL flag to the TLHTTPClient class:
BOOL fetchingFlag;
and then trying:
while([RESTClient fetchingFlag]) { NSLog(#"fetching...); }
When this class receives the notification it sets RESTClient.fetchingFlag = FALSE; which should technically kill the while loop right? Except my while loop runs infinitely ?!
Basically I'm wondering what sort of code I need in the above specified area to ensure that the function waits until the fetch has been completed ?
You need no code. Don't put anything in the method after you start the fetch, and nothing will happen. Your program will "wait" (it will actually be processing other input) until the notification is recieved.
In the notification handler method, put all the stuff that you need to do when the fetch is completed. This is (one of) the point(s) of notifications and other callback schemes -- your object won't do anything further until it gets the notification that it's time to act.
Is there a way to wait for a notification to be posted without blocking the main thread?
That's exactly how it works already.
If you don't need to inform multiple objects upon completion of the task, you could add a completion handler (objc block) to the -fetchActiveUser:withPassword: method (so it would become something like -fetchActiveUser:withPassword:completionHandler: and add the code to be executed in the completion handler.
An example, lets say your -fetchActiveUser:withPassword:completionHandler: method looks like the following:
- (void)fetchActiveUser:(NSString *)user
withPassword:(NSString *)pass
completionHandler:(void (^)(TLUser *user, NSError *error))handler
{
NSURL *URL = [NSURL URLWithString:#"http://www.website.com/page.html"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSOperationQueue *queue = [NSOperationQueue currentQueue];
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:^ (NSURLResponse *response, NSData *data, NSError *error)
{
if (!handler)
{
return
};
if (data)
{
TLUser *user = [TLUser userWithData:data];
if (user)
{
handler(user, nil);
}
else
{
NSError *error = // 'failed to create user' error ...
handler(nil, error);
}
}
else
{
handler(nil, error);
}
}];
}
The completion handler will be called whenever the task is finished. It will either return a TLUser object or an Error if something went wrong (bad connection, data format changed while parsing, etc...).
You'll be able to call the method like this:
- (void)saveConfiguration:(id)sender
{
TLHTTPClient *RESTClient = [TLHTTPClient sharedClient];
// Performs the asynchronous fetching
[RESTClient fetchActiveUser:[usernameTextField stringValue]
withPassword:[passwordTextField stringValue]
completionHandler:^ (TLUser *user, NSError *error)
{
if (user)
{
NSLog(#"%#", user);
}
else
{
NSLog(#"%#", error);
}
}];
}
Of course, in this example I've used the build in asynchronous methods of NSURLConnection in stead of AFNetworking, but you should be able to get the general idea.