cache array of objects iOS - ios

how can I cache array of objects in iOS using swift 3 ??
I know I can use NSCache for it the only problem is this that I know how to cache a single object but dont know how to cache a whole array of it..
var cachedData = NSCache<NSString, myObject>() // i cant define it like NSCache<NSString, [myObject]>() cause NSCache takes anyobject not Array of AnyObject
//cache.setObject(myObject, forKey: "CachedObject")
anyone can guide how can I do it??

Related

Ambiguous use of 'mutableCopy()' Swift3

I tried to update Swift 3 and I got the following error :
Ambiguous use of 'mutableCopy()'
Before update to swift 3. It runs well.
Swift 2.3
NSUserDefaults.standardUserDefaults().objectForKey("listsavednews")?.mutableCopy() as! NSMutableArray
Swift 3.0
(UserDefaults.standard.object(forKey: "listsavednews")? as AnyObject).mutableCopy() as! NSMutableArray
I found that mutableCopy in Swift3 return Any that doesnt have method mutableCopy() so that it needs to cast to AnyObject.
Any helps thanks.
I dont know why I can't comment.
Thanks all, I'll be using :
UserDefaults.standard.mutableArrayValue(forKey: "listsavednews")
mutableCopy is an Objective-C method from NSObject. There's little reason to use it in Swift 3.
Since you are dealing with UserDefaults and mutableCopy, you must be dealing with either an array or dictionary. Or it could be a string.
The proper way to do this in Swift 3 is to use the proper UserDefaults method to get an array or dictionary. And assign the result to a var. That combination will give you a mutable array or mutable dictionary.
var someArray = UserDefaults.standard.array(forKey: "somekey")
or:
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey")
In the two above cases, you end up with an optional since there might not be any data for the given key. And you also get a non-specific array or dictionary which isn't ideal. It would be better to cast the result to the appropriate type.
Let's say you have an array of strings and you want an empty array if there is nothing currently in user defaults. You can then do:
var someArray = UserDefaults.standard.array(forKey: "somekey" as? [String]) ?? []
Adjust as necessary if the array contains something other than String.
If you actually have a dictionary, the code would be similar.
var someDictionary = UserDefaults.standard.dictionary(forKey: "somekey") as? [String:String] ?? [:]
If your original object is just a string, then you could do:
var someString = UserDefaults.standard.string(forKey: "somekey") ?? ""

Updating a property in a struct inside an array

In my app I download a load of JSON.
I then store that as an array of structs and use that to populate a UITableView.
One of the properties of the struct is an NSURL for an image. Another property is an optional UIImage.
The struct has a mutating function downloadImage which uses the URL to download the image and store it in its property.
Like this...
struct SearchItem {
// other properties...
let iconURL: NSURL
var icon: UIImage?
mutating func downloadImage() -> Task<UIImage> {
let tsc = TaskCompletionSource<UIImage>()
NSURLSession.sharedSession().downloadTaskWithURL(iconURL) {
(location, response, error) in
if let location = location,
data = NSData(contentsOfURL: location),
image = UIImage(data: data) {
self.icon = image
tsc.setResult(image)
return
}
tsc.setError(NSError(domain: "", code: 1, userInfo: nil))
}.resume()
return tsc.task
}
}
The problem I'm having is this. (and I have been stumped by this in the past).
I have an array [SearchItem] that I use to populate the tableview.
In cellForRow I have the code... if let searchItem = items[indexPath.row]...
It then checks if the image is nil and downloads...
if let image = searchItem.icon {
cell.imageView.image = image
} else {
searchItem.downloadImage().continueOnSuccessWith(Executor.MainThread) {
_ in
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
But this never goes through to put the image into the cell. This is because the SearchItem is struct and so pass-by-value. So the search item that I am downloading the image for is not the same SearchItem as the one stored in the array.
How can I ensure that the image that is downloaded is then stored into the SearchItem inside the actual array?
Use classes.
You're getting a copy of searchItem in your cellForRow method. Whatever you do to this, will be done only to that copy. What you actually want is for the changes you make to that copy to be applied to the version in the array.
Therefore you want reference semantics, therefore use classes.
You could dance around re-inserting the updated copy into the original array if you liked, but what does that gain you besides a line of extra code and probably some other problems.
Structs are lightweight data objects that are not passed by reference, but instead copies itself as needed when you a) pass it to a new function, b) try and access it in a block. Arrays in Swift also work slightly differently than their Obj-C counterparts. When you have an Array of class objects the array will be a reference type, and you'll be able to achieve what you're trying to achieve here. But on the other hand if the Array is of Structs the array looses its reference semantics and uses copy-by-value instead.
This difference is really powerful when used appropriately, you can greatly optimise your code, make it run faster, have less errors produced by mutable object references having changes happen in multiple parts of your code, etc. But it's up to you as a developer to see where the gains of these optimisations are useful or where it makes sense to use objects instead.

Cannot subscript a value of type anyobject

I've currently upgraded to the new Xcode 7, and the following code never had any errors with swift 1.2, but now its telling me that :
Cannot subscript a value of type any object
var imageArray : NSArray = []
let url = NSURL(string: (self.imageArray[indexPath.row][0] as? String)!)
I know its about [0] but how do i rewrite it to be accepted ?
OK, so first you are using an NSArray. You can drop that and make everything much easier.
In Swift always use strong typing where possible. Avoid Any and AnyObject when they are not needed. (They are VERY RARELY needed).
The error is happening because you're not telling the code what is actually in the imageArray.
Also, imageArray tells me the array is full of images. Name your variables more descriptively. imageUrlStringArray or arrayOfImageUrlArrays. Something more descriptive anyway.
Declare imageArray like...
var imageArray = [[String]]()
This tells the compiler that imageArray is a 2D array with Strings at the second level.
Now you can create your URL easily...
guard
let urlString = imageArray[indexPath.row].first,
let url = NSURL(string: urlString)
else { // Handle the inability to create a URL }
// Do something with the url

How do I save a swift array?

I am creating an app in Swift that manages tasks based off of priority. I currently place the tasks into an array. Does anybody know how I can save this array so that when I open the app I will still be able to access the data?
Use NSUserDefaults.
Save array:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(myArray, forKey: "myarray")
defaults.synchronize()
Read array:
myArray = defaults.dataForKey("myarray") as Array

How to use NSCache

Can someone give an example on how to use NSCache to cache a string?
Or anyone has a link to a good explanation? I can't seem to find any..
You use it the same way you would use NSMutableDictionary. The difference is that when NSCache detects excessive memory pressure (i.e. it's caching too many values) it will release some of those values to make room.
If you can recreate those values at runtime (by downloading from the Internet, by doing calculations, whatever) then NSCache may suit your needs. If the data cannot be recreated (e.g. it's user input, it is time-sensitive, etc.) then you should not store it in an NSCache because it will be destroyed there.
Example, not taking thread safety into account:
// Your cache should have a lifetime beyond the method or handful of methods
// that use it. For example, you could make it a field of your application
// delegate, or of your view controller, or something like that. Up to you.
NSCache *myCache = ...;
NSAssert(myCache != nil, #"cache object is missing");
// Try to get the existing object out of the cache, if it's there.
Widget *myWidget = [myCache objectForKey: #"Important Widget"];
if (!myWidget) {
// It's not in the cache yet, or has been removed. We have to
// create it. Presumably, creation is an expensive operation,
// which is why we cache the results. If creation is cheap, we
// probably don't need to bother caching it. That's a design
// decision you'll have to make yourself.
myWidget = [[[Widget alloc] initExpensively] autorelease];
// Put it in the cache. It will stay there as long as the OS
// has room for it. It may be removed at any time, however,
// at which point we'll have to create it again on next use.
[myCache setObject: myWidget forKey: #"Important Widget"];
}
// myWidget should exist now either way. Use it here.
if (myWidget) {
[myWidget runOrWhatever];
}
#implementation ViewController
{
NSCache *imagesCache;
}
- (void)viewDidLoad
{
imagesCache = [[NSCache alloc] init];
}
// How to save and retrieve NSData into NSCache
NSData *imageData = [imagesCache objectForKey:#"KEY"];
[imagesCache setObject:imageData forKey:#"KEY"];
Sample code for caching a string using NSCache in Swift:
var cache = NSCache()
cache.setObject("String for key 1", forKey: "Key1")
var result = cache.objectForKey("Key1") as String
println(result) // Prints "String for key 1"
To create a single app-wide instance of NSCache (a singleton), you can easily extend NSCache to add a sharedInstance property. Just put the following code in a file called something like NSCache+Singleton.swift:
import Foundation
extension NSCache {
class var sharedInstance : NSCache {
struct Static {
static let instance : NSCache = NSCache()
}
return Static.instance
}
}
You can then use the cache anywhere in the app:
NSCache.sharedInstance.setObject("String for key 2", forKey: "Key2")
var result2 = NSCache.sharedInstance.objectForKey("Key2") as String
println(result2) // Prints "String for key 2"
sample Project
Add CacheController.h and .m file from the sample project to your project. In class where you want to cache data , put the below code.
[[CacheController storeInstance] setCache:#"object" forKey:#"objectforkey" ];
you can set any object using this
[[CacheController storeInstance] getCacheForKey:#"objectforkey" ];
to retrive
Important: The NSCache class incorporates various auto-removal policies. if you want cache the data for permanent or you want to remove cached data in a specific time see this answer.
Shouldn't the cached objects implement the NSDiscardableContent protocol?
From the NSCache class reference:
A common data type stored in NSCache objects is an object that implements the NSDiscardableContent protocol. Storing this type of object in a cache has benefits, because its content can be discarded when it is not needed anymore, thus saving memory. By default, NSDiscardableContent objects in the cache are automatically removed from the cache if their content is discarded, although this automatic removal policy can be changed. If an NSDiscardableContent object is put into the cache, the cache calls discardContentIfPossible on it upon its removal.

Resources