NSXMLParser: how do I wait until loading has finished? - cocoa-touch

Let's say I'm using NSXMLParser to load a level (stored as an XML document, obviously) into my iPhone game. NSXMLParser works by assigning a delegate and sending it messages at key moments. How do I ensure that my entire level is loaded before doing anything else? I know I can make my main class the delegate and implement parserDidEndDocument, but this feels very hacky. My main class shouldn't have to know anything about the way the parsing is done! On the other hand, if I make a separate class/delegate for parsing my level, my main class has no way of knowing when the parsing is finished, unless it queries the parsing class continuously or the parsing class sends it a message. Either way, the main class would be tied to the implementation of the parsing class.
Can I hide all this event-driven business from the main class and simply make the parser return the level object when it's done? (i.e., newLevel = [[GameLevel alloc] initFromXML:xmlfile], which would in turn use NSXMLParser to load the level and then somehow return when finished.) At the moment, I'm using an external DOM parser, but I'm curious how this would be done with NSXMLParser.
Sorry if this is a stupid question -- I'm a bit new to this!
EDIT: According to the NSXMLParser class reference, the parse method, which "starts the event-driven parsing operation", returns YES if successful and NO if there's an error. Does this mean that it returns only after the parsing has finished? This would solve my problem, as I had assumed that parsing continued even after the parse method returned.

NSXMLParser blocks until the parsing is done. You don't have to do anything because the level will be loaded after the parse method returns.

Related

Long delay with NSFileCoordinator coordinateWritingItemAtURL

I'm setting up NSFileCoordinator and NSFilePresenter in my app so I can do file IO from my AppleWatch app safely. There are some places in my code where I write to a file a couple of times in quick succession. This is a problem in and of itself and I'm working to correct it, but I'm noticing some weird behavior in the process.
I wrap my writes like this:
//In a class that implements NSFilePresenter:
NSFileCoordinator *coord = [[NSFileCoordinator alloc]initWithFilePresenter:self];
[coord coordinateWritingItemAtURL:self.presentedItemUrl options:0 error:nil byAccessor:^(NSURL *url)
{
//do my writing here using CFWriteStreamRef or NSOutputStream
}];
On the first write, the write block happens within 1 ms. But after that, there's about a 0.5 second delay between calling coordinateWritingItemAtURL and the write block being executed.
Is this expected behavior?
Some of the documentation for NSFileCoordinator and NSFilePresenter says to use prepareForReadingItemsAtURLs:writingItemsAtURLs:options:error:byAccessor: for batch operations, but it seems weird to get such a long delay when I don't batch.
Update: This happens with reading too.
Update 2: Here is an example project reproducing the problem.
Update 3: Using this API for coordination between an app and its extension is apparently a bad idea. But the question still stands.
Referring to File System Programming Guide , you can read following:
you might want to avoid incorporating changes directly from your file
presenter method. Instead, dispatch a block asynchronously to a
dispatch queue and process the changes at a later time. This lets you
process the changes at your app’s convenience without causing
unnecessary delays to the file coordinator that initiated the change.
Of course, when saving or relinquishing control of a file (such as in
the relinquishPresentedItemToReader:,
relinquishPresentedItemToWriter:, or
savePresentedItemChangesWithCompletionHandler: methods) you should
perform all necessary actions immediately and not defer them.
I think this is your case where you are defering actions.
Possible Solution:
Please read this well , to properly handle multiple successive writing operations , the relinquishPresentedItemToWriter , can do the job , same will work with reading file , relinquishPresentedItemToReader , supposing that multiple different objects are trying to read and write the same file.
P.S :
I dont know what your app does exactly , but i hope you have read this :
If you are implementing a document-based app, you do not need to
incorporate file presenter semantics into your NSDocument subclasses.
The NSDocument class already conforms to the NSFilePresenter protocol
and implements the appropriate methods. Thus, all of your documents
automatically register themselves as presenters of their corresponding
file and do things like save changes and track changes to the
document.
Is it possible to use options NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly for reading and NSFileCoordinatorWritingContentIndependentMetadataOnly for writing in some cases? Looks like this iOS8 options can help you.

How does KVC deal with speed and errors?

I've been reading about KVC and Cocoa Scripting, and how properties can be used for this. I have a model class in mind, but the element/property data has to be obtained from the Internet. But the design of properties and KVC looks like it assumes fast & in-memory retrieval, while network calls can be slow and/or error-prone. How can these be reconciled?
For speed, do we just say "screw it" and post a waiting icon? (Of course, we should keep things multi-threaded so the UI doesn't stop while we wait.)
If your property is supposed to be always available, we could set it to nil if the resource call gets an error. But we would have no way to get the specifics. Worse would be a property that supports "missing values," then nil would represent that and we would have no spare state to use for errors.
Although Apple-events support error handling, I couldn't use it because between my potentially error-generating model calls and the Apple event, the KVC layer would drop the error to the floor (of oblivion). The Scripting Bridge API saw this problem, since its designers added a secret protocol to handle errors.
Am I wrong? Is there a way to handle errors with KVC-based designs?
Addendum
I forgot to mention exceptions. Objective-C now supports them, but the little I read about them implies that they're meant for catastrophic "instead of straight crashing" use, not for regular error handling like in C++. Except for that, they could've been useful here....
I think I understand what you're asking now. I would say using KVC (or property getters) is not a good way to accomplish what you're trying to do. If the code gets called on the main thread, you will then block that thread. which you don't want to do. As you have discovered you'll also have a hard time returning other state information such as errors.
Instead, you should use block syntax to create an asynchronous method that operates on a background queue. Here is a basic template for what this might look like:
// called from main thread
- (void) fetchDataInBackgroundWithCompletionHandler:(void (^)(id responseData, NSError *error))handler
{
// perform in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^()
{
// perform your background operation here
// call completion block on main thread
dispatch_async(dispatch_get_main_queue(), ^{
if(// whatever your error case is)
{
handler(nil, error);
}
else // success
{
handler(responseData, nil);
}
});
});
}
This also gives you the benefit of being able to pass in as many other parameters are you want as well as return as many values as you want in the completion block.
Some very good examples of this pattern can be seen in AFNetworking, which is one of the more popular networking libraries written for iOS. All of the calls in the library can be made from the main queue and will return on the main queue asycnhronously while performing all networking in the background.

Wait for condition before continuing method

I'm writing a class which retrieves and parses a file downloaded from a server.
I have a method inside the class which parses the information, and the information is downloaded when the class is initialized.
The problem is, sometimes, the method that parses the information is called before the information itself is downloaded.
FileParser *instance = [[FileParser alloc] initWithURL:#"somewhere"];
[instance parseData];
Every time the parseData method is called directly after the class is initialized, it fails because not all of the content is available.
How can I wait until all the information is downloaded before I continue executing the parseData method, without interrupting the main thread?
Any help appreciated.
Simple: You need to either perform this entire operation on a background thread/queue, or refactor your FileParser class towards a more asynchronous design. That design would be centered around a callback mechanism, such as delegation or blocks.
you should think about using delegates.
You pass your dowloading class a delegate that will handle the downloaded data.
See here

Can I use blocks in asynchronous results with restkit?

--UPDATE: I've decided to give AFNetworking a try. Even though RestKit has a really nice object mapping functionality, the way the networking calls were designed have made some things difficult for us.
I'm hoping for some advice on how to organize my project that's using RestKit.
I have several REST calls from a repository class and its results get passed to controllers. For example I have a getProfile method in the repository class that is getting called from our browse view controller. The view controller is set as the delegate to the get profile calls while the repository class is set to the delegate for the restkit calls.
The problem is if the browse controller makes several get profile requests, it is difficult to distinguish which result should go to which delegate function since all the restkit calls share the same delegate method objectLoaderDidFinishLoading. I then have 4 delegates that I have to match the results of the 4 asynchronous restkit requests.
Is there any way I can use blocks so that I can pass a function to execute as the asnynrhounous result comes back so that I can assign a proper delegate? The block support that I saw allowed a block to be used before the request was sent out in rest kit but I am interested in using it for when the asynchronous result is returned.
The alternative of examining the results or setting user data and sleuthing what delegate goes with what asynchronous results seems unreliable and bulky.
You can solve your issues with disambiguating between your profile requests by using the userData opaque pointer on RKObjectLoader. That will allow you to hang any object you want on the request, which can then be used to help distinguish between multiple profile requests. Also, if those profile requests are going to different resourcePaths then you can just use the wasSentToResourcePath: method on RKObjectLoader to distinguish between them.
I just stumbled upon this question while trying to figure out this problem for my own REST interface. I'm glad I did, I'll probably use RestKit now.
I digress, back to your question. As you noted it doesn't seem like the block argument in the RKObjectManager is meant to be used this way. Instead, how about writing a class that implements RKObjectLoaderDelegate, takes in a block, and calls that block on any of the delegate calls.
Maybe something like this?
#interface MyObjectLoaderDelegate : NSObject <RKObjectLoaderDelegate>
#property (nonatomic, copy) void (^callback)(RKObjectLoader *loader, NSDictionary *objectDictionary, NSError *error)
- (id)initWithCallback:(void (^)(RKObjectLoader*, NSDictionary*, NSError*)aCallaback;
#end
And on any implemented delegate method you can execute the block. Since blocks retain scoped variables you can run code against the calling delegate.
Whatcha think?
I am not sure using blocks is the right way to solve your issue.
How about having a class GetProfile that implements RKObjectLoaderDelegate.
So you call the request from within here and set itself to be the delegate.
Then you have an objectLoader per request.
So in your view controller, each time you what GetProfile, you create an instance. And then when that instance messages your controller back (via delegates?) you know which it is.
I am just grappling with this issue as well, so am keen to hear feedback.
Switching over to AFNetworking seems to be the way to go... it was a much easier implementation for what I needed.

Understanding NSManagedObject

In an existing project I have tried to introduce Core Data long after the project was created, so its model is already in place.
I have created the xcdatamodel and added my only class to it.
That class should act as a global storage for objects in my application.
The class properly implement NSManagedObject and I have verified it gets created and saved in context, also retrieved with a fetch result.
The way of saving data in this class is by means of NSMutableArray. But this is just not working. Here's a fragment of this class:
#interface WZMPersistentStore : NSManagedObject<NSCoding> {
NSMutableArray *persistentStorage;
}
#property(nonatomic,retain) NSMutableArray *persistentStorage;
-(void)add:(id)element;
-(void)remove:(id)element;
-(id)objectAtIndex:(NSUInteger)index;
-(NSUInteger)num;
#end
In the implementation I also override the initWithEntity like this:
- (id)initWithEntity:(NSEntityDescription*)entity insertIntoManagedObjectContext:(NSManagedObjectContext*)context {
NSLog(#"init with entity");
[super initWithEntity:entity insertIntoManagedObjectContext:context];
return [self init];
}
The init method only initialize the mutable array, and I can see from the log that it gets properly called by the app delegate when creating entity.
The add method just send message insertObject to persistentStorage.
The questions that come from this:
Am I doing "conceptually" right ? I
mean, is it correct to have instance
variable in managed object and
initialize like I did ?
when ns logging the size of the
persistentStorage I always get 0
even when logging a moment after the
addObject message (edit: that's not
true, I have verified again and I
correctly got 1 added).
The object stored in managed object
class trough persistentStorage are
normal class with attribute. Is
there something I need to do with
them ? I suppose not because I am
not getting any error at runtime.
No, that is not the "right" approach. You can perform initialization of instance variables in awakeFromFetch. Apple guidelines for NSManagedObject subclasses include the following:
You are also discouraged from
overriding
initWithEntity:insertIntoManagedObjectContext:,
dealloc, or finalize. Changing values
in the
initWithEntity:insertIntoManagedObjectContext:
method will not be noticed by the
context and if you are not careful,
those changes may not be saved. Most
initialization customization should be
performed in one of the awake…
methods. If you do override
initWithEntity:insertIntoManagedObjectContext:,
you must make sure you adhere to the
requirements set out in the method
description [...] (NSManagedObject Class Reference)
To really help, I'd need a deeper understanding of what you're trying to accomplish. Regardless, I strongly suggest combing through Apple's Core Data Programming Guide and sample code before proceeding.
I finally manage to solve this issue. Even if I am a newbie in objective-c, I think that introducing core data after the project is done, is not a good idea. Even if many claim it's easy. Unfortunately, all the people saying so, are showing as proof some really simple tutorial of one entity with one string attribute to change.
Instead for my project I ended up writing much code in addition to the existing one, plus some subclassing (NSManagedObject for example) which break the original model. This added code has also to be written carefully. Derived problem can be as simple as an attribute not saved, or dangerous as deleting wrong entities.
Infact, my problem was due to a wrong configuration in decode and encode method in the classes involved in the process of serialization.
For my questions:
-Point one still remain unanswered, because I am not yet confident in objective-c
-Point two, as I said the related object had some problem with encode/code.
-Point three, I was wrong, there's a lot of code to write, depending how complex is the relevant class.