I have an iOS app that can download files from a website. I have created a NSURLSession in a class Downloads to manage them. The Downloads class has a NSMutableArray that keeps track of all current and past downloads using my DownloadItem objects. I am not happy with this setup.
Currently, I have to have the Downloads class as the delegate for all downloads. I see no way to assign the delegate of each NSURLSessionDownloadTask to a DownloadItem object. So, I have to keep it assigned to my Downloads object and then have it figure out which way DownloadItem to forward the message on to.
Currently I do this by making an NSMutableDictionary called tasksDictionary in the Downloads and use the taskIdentifier as a key.
return [self.tasksDictionary objectForKey:[NSNumber numberWithInteger:task.taskIdentifier]];
This seems to work, but it doesn't seem the most efficient method. I'm also concerned that I saw the first taskIdentifier created was 0 which will make it difficult to discern the difference between a completed task and the first task.
Is there a better way to keep track of these things? Is there a way to assign a new delegate for a task?
Perhaps have these ivars:
NSMutableArray *completedDownloads;
NSMutableDictionary *activeDownloadTasks; // #(taskID) => DownloadItem
When you finish, pop the DownloadItem from activeDownloadTasks and add it to completedDownloads.
Otherwise what you are doing sounds peachy.
While I'm at it, this syntax may be helpful (using your example):
return self.tasksDictionary[#(task.taskIdentifier)];
Related
I am developing an iOS application where I want to record the time when a user presses a particular button and keep it. Later I will use this time record in background. Is there a nice way of doing that without invoking NSUserDefaults or CoreData or whatever other database?
I am very new to iOS development. I think this is very likely to be a naive question. But I'm just curious. Please don't laugh at me haha.
Edit: This is indeed a very naive question haha.
A simple way to make sure your data is available everywhere in your app and only persists for each app session would be to use a singleton. Something like this.
// Create a class to store the data
class SessionData : NSObject {
// Create a shared instance of your class
static let sharedInstance = SessionData()
// Create a variable to store the date object
var timeStore:NSDate?
}
This will mean that anywhere in your app you can get and set this data as below.
// Get
if let time = SessionData.sharedInstance.timeStore {
println(time)
}
// Set
SessionData.sharedInstance.timeStore = NSDate()
It is worth mentioning that despite the above being a valid method, You may be able to avoid doing this by re-thinking your app structure and passing data between classes instead. You should have a look at Delegation.
Also as #CouchDeveloper mentions in their comment below, you may want to included a dispatch queue to prevent crashes or locks in the situation where two or more classes try to read and or write data at the same time.
I value the opinions on this site and wanted to know what you all thought. I have an app that contains an array. This array is used in several view controllers. Currently, I create the array each time one of the view controllers opens up. I don't believe this to be the best thing to do. I have used NSUserDefaults before, and singletons, but am open to any ideas you have, plist, etc. I am looking on making things run quick and efficient.
Also, I wanted the user to be able to rearrange the array. Lets say it starts out in alpha order. But the user finds it better to rearrange it some other way. I know how to do that, rearrange, but I wanted to know where to keep the original array so that the user can than reset the order back to the original alpha order.
So I guess this question is twofold. Where to store the original array, and how best to then access the changed array throughout the app. Thanks very much for your help.
EDIT #1
I want to thank everyone for their comments and answers! This is what, I feel, the community is about. Learning new and different techniques is great. I am going to attempt to try all the answers (to see if I can actually program it) and see which one works the best for me. Once again thanks for this excellent dialogue.
Edit#2
I ended up doing a bit of both, and I would have never done that without all of your comments and answers. I made the array in the app delegate and saved it in user defaults. It wasn't a basic array, it was an array of custom objects so I had to use a bit of NSCoding. It also was not just alpha sorted, so I made one array that could be manipulated. Once again, thanks for all the great insight!
You can create a singleton instance of it in your AppDelegate.
Make an #property for the array in AppDelegate.h, and then import that header into your other viewControllers.
Init and store the array to the appDelegate's array #property in "didLoadWithOptions." You could create custom getters/setters to allow for manipulation. The actual data could come from an external XML, JSON or plist file, but to test you can just hard-code an array in AppDelegate to start with.
Each viewController needs to have a reference to the appDelegate, using the methods described in this answer. You can then access the 1 array as you would any other property,
NSArray *dataInVC = myAppDelegate.singletonArray; (...or using NSMutableArray if you want to add/change/manipulate).
...Alternatively, just have a separate class for your data, and create a singleton version of it so you can more easily refactor later if necessary. Examples of how to do this in this answer
As you are accessing your data from five different places, it will soon become confusing to send data to and fro. A singleton class with public properties storing your arrays would be a good solution to this problem.
In case you want to persist the data to disk, NSUserDefaults would be the better option.
Here's how one would go about using NSUserDefaults to do that. For saving data:
[[NSUserDefaults standardUserDefaults] setObject:originalArray forKey:#"OriginalArray"];
[[NSUserDefaults standardUserDefaults] setObject:modifiedArray forKey:#"ModifiedArray"];
Retrieving data:
NSMutableArray *modifiableArray = [[NSUserDefaults standardUserDefaults] arrayForKey:#"ModifiedArray"].mutableCopy;
Don't forget to save the data to disk with a call to [NSUserDefaults synchronize] at appropriate times.
I'd recommend storing the array in your app delegate. One of the advantages of this solution is that your app delegate is already a singleton.
Create an NSMutableArray property in your app delegate header file. Init and fill the array in the app delegate didFinishLaunchingWithOptions: method.
Access it from anywhere with the following code :
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
// appDelegate.myArray holds your values
Going even further, add this to your Prefix.pch file:
#import "MyAppDelegate.h"
#define appDelegate ((MyAppDelegate *)[UIApplication sharedApplication].delegate)
Then the previous code can be reduced simply to calling appDelegate.myArray from anywhere.
You can create methods that handle sorting and other manipulation on your array in your app delegate implementation. If you declare those methods in the app delegate header file, you can also call them from anywhere in the app with :
[appDelegate doStuff:parameter];
I know there are few questions similar to this one, but in all cases the answer is to make it asynchronous.
According to the apple documentation (even though it is not recommended) polling is an available option. However, I just couldn't implement it.
Should probably mention I am doing it in c# using Xamarin, but if you
can give me an answer on how to do this in Objective-C that would be
good too.
Here is my attempt
while(true)
{
if (outStream.HasSpaceAvailable())
{
int output = ((NSOutputStream)outStream).Write(data, (uint)data.Count());
}
}
The problem here is that outStream.HasSpaceAvailable() is false indefinitely.
Reason why I want to do it synchronously:
Currently (in a test app) I am doing it asynchronously and it works for sending one stream of data per call to the method. However in the real App I will need to send lots of small data packets one after the other. Therefore, I need to wait for the data to be sent before I can send another packet.
If I try putting many calls to the delegate the data keeps overwriting the previous call...
It would be great if you could let me know how to do it synchronously first (for the sake of having one answer out there on it). If you think there is a better way to do it in an async way let me know too.
Thanks
EDIT :
Here is how I set up the session
SESSION=new EASession (Accessory, Protocol);
NSStreamStatus outputStream= SESSION.OutputSream;
outputStream.Open();
This is done when a button is pressed and before the while loop above (obviously).
I have a UIDocument based app that uses NSFileWrappers to store data. The 'master' file wrapper contains many additional directory file wrappers, each of which represents a different page of the document.
Whenever I make a change to the document while the UIDocument is saving (in writeContents:andAttributes:safelyToURL:forSaveOperation:error:), the app crashes. Here is the stack trace:
It seems clear that I am modifying the same instance of file wrapper that the UIDocument is enumerating over in the background. Indeed, I checked that when returning a snapshot of the data model in contentsForType:error:, the returned sub file wrappers point to the same objects as the ones currently residing (and being edited) in the data model, and not copies.
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
if (!_fileWrapper) {
[self setupEmptyDocument];
}
return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}
This is the sanctioned approach to implementing this method (according to WWDC 2012 Session 218 - Using iCloud with UIDocument).
So I suppose the question is: How can this approach be thread safe?
Is the situation somehow different when the master file wrapper's fileWrappers are themselves directory file wrappers? If the sanctioned approach is wrong, how should it be done?
If you are calling any of the writeContents:... methods, you shouldn't be. You should be calling saveToURL:forSaveOperation:completionHandler: instead. The writeContents:... methods are meant for advanced subclassing.
UIDocument uses two threads - the main thread and the "UIDocument File Access" thread (which , if you subclass more of UIDocument, you can do things in via performAsynchronousFileAccessUsingBlock:).
Thread safety with UIDocument is like anything in Objective C - only let the thread owning an object modify it. If the object you want to change is being read, queue it to be changed after the write is complete. Perhaps change a different object owned by your UIDocument subclass and pull them into a new NSFileWrapper in contentsForType:error:. Pass a copy of the fileWrappers NSDictionary.
NSFileWrapper actually loads the entire document into memory. The NSFileWrapper is actually created in the "UIDocument File Access" thread in the readFromURL:error: method, which is then passed to the loadFromContents:ofType:error: method. If you have a large document this can take a while.
When saving you typically want to let UIDocument decide when to do this, and let it know something has changed via the updateChangeCount: method (param is UIDocumentChangeDone). When you want to save something right now you want to use the saveToURL:forSaveOperation:completionHandler: method.
One other thing to note is UIDocument implements the NSFilePresenter protocol, which defines methods for NSFileCoordinator to use. The UIDocument only coordinates writing on the root document, not the subfiles. You might think that coordinating subfiles inside the document might help, but the crash you're getting is related to mutating a dictionary while it's being iterated, so that wont help. You only need to worry about writing your own NSFilePresenter if you (1) wanted to get notifications of file changes, or (2) another object or app was reading/writing to the same file. What UIDocument already does will work fine. You do want to, however, use NSFileCoordinator when moving/deleting whole documents.
I know this is an ancient thread, but I ran into this problem recently, and to help future travelers: If you have subdirectories in your main file wrapper, you need to copy those NSFileWrappers as well (in addition to copying the root NSFileWrapper as above).
Otherwise, a crash can occur when the UIDocument background thread enumerates over them while saving and while simultaneous modifications occur on the main thread. It's not clear, but this might be the problem the OP ran into.
Another tip is you need to also copy over the subdirectory's NSFileWrapper's filename, fileAttributes (and possibly preferredFilename) so that incremental saving works.
HTH.
I am making an object that goes to download stuff for all of my view controllers. The object is singleton instance and has a callback method with received data once the download is completed. It also has a delegate property so that it knows which object to call back to after the download is done.
There are multiple controllers that use this shared instance, and my question is how to call back to the correct view controller that requested the download.
My approach is to use delegation, but the problem is that since other view controllers are also its delegate, the download object could call back to every object and this will be hard to track.
I've worked on projects where people have attempted to use multiple delegates and it's basically a bad idea. The delegate pattern is about a 1 to 1 relationship between a class and it's delegate. Whilst it is possible to achieve some level of multiple delegation through switching the delegates in and out, it's more likely to lead to unpredictable behaviour and bugs.
My recommendation would be to change how you are thinking about this. You have two options as I see it:
Switch to an Observer pattern where you can register multiple observers which your main class can interact with. This is useful where your observers all implement the same protocol and where your main class wants to be aware of the observers and interaction with them.
Broadcast NSNotifications to indicate state changes and events. Here is a more decoupled approach because the main class does not need to know who is listening and does not directly interact with them. Other can start and stop being notified at their leisure. It also has the advantage that you do not need to create or implement a separate protocol. Instead you register the classes that need to know about changes with the NSNotificationCenter which in turns handles all the routing of notifications for you.
It actually sounds like the delegate pattern might not be the best approach here.
I would look into NSNotificationCenter instead.
The basic idea is that your singleton doing the net connection posts a notification (with something like postNotificationName:object:userInfo:) , saying that new data is available. Within this notification, you can pass a dictionary object (userInfo) that holds the data you've fetched, or info on what parts of your Model contain updated data.
Then, your other view controllers can register themselves to 'observe' these notifications by calling addObserver:selector:name:object:. Generally speaking, when a vc becomes visible I call addObserver, and removeObserver when it's being hidden or transitioned out.
Good luck!
Delegation doesn't seem like the right solution to this problem. How about requiring the requesting view controller to provide an object (its self) and a selector for you to call as a completion notification? Of course, you'll need a place to store that object and selector until the download completes. Hopefully you have (or could create) an object for this.
i recommend to use one of these ways
observer:
when use data that you want to inform other object are near to primitive ones.for example when you are using 'NSMutableArray' you can not inform the change in one of object by the standard implemented pattern at least you need to implement one for your self that is not reusable that much
Notification
when your interaction with destination object (those need to be inform) is in one-way.it means you don't need any acknowledge or other data back from them.
delegate
when there is one object to inform at each time step.
note:block use for success and fail is not a pattern to broadcast data its about to queue task when you don't know when they are finishing or failing like network operations
EDIT:
how to create notification | multi delegate issues and implementation
While I agree with most of the answers here, if you did actually want to achieve multiple delegates you could potentially declare an array of delegates and send messages to all delegates within that array. If your protocol has optional delegate methods you safely check using responds(to aSelector: Selector!) -> Bool before invoking (being mindful of memory management, as those delegates would be strongly referenced in the array). Again I do agree that multiple delegates is likely a bad architectural idea and using blocks or notification center would suit your needs better.
One approach, which works for me if you only have one other object to forward messages to is to create a forwardingDelegate This does not end up with issues of hard to debug ordering of delegates and it does not unnecessarily create a dependency on the other object. Keep in mind, if you have many objects then this might not be the best approach, it is mainly for one additional object but this could be extended to support an array of objects so long as there is one that receives the SDK and forwards it to the other objects [1]. Note that every method that is needed for the forwarded object needs to pass it along, even if it is not used by the forwarding object.
For example, if I need to forward the messages coming from the mapView delegate:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
// handle this object here.
if ([self.forwardingDelegate respondsToSelector:#selector(mapView:regionDidChangeAnimated:)])
{
[self.forwardingDelegate mapView:mapView regionDidChangeAnimated:animated];
}
// or handle this object here.
}
[self.forwardingDelegate mapView:mapView regionDidChangeAnimated:animated];
The forwarding property would be declared like this.
#property (nonatomic) id<MKMapViewDelegate> forwardingDelegate;
And the other object would adopt the protocol as if it were receiving the original message.
[1] The array approach for multiple delegates may get tricky because then you don't have as much control over what order the delegates get called, as was mentioned in other posts.