Get-only property stays in memory - ios

I have an enum with get-only property image. It seems like every time I read it, it keeps this image in memory and deletes it only after memory warning (since I use multiple enum instances and display their images in a sequence) even after I set another image to the UIImageView it was displayed on.
How do I force them to be removed from memory?

Well, the best way to not overload the memory is not to cache UIImages in the first place, which can be done with:
extension UIImage{
static func getUcachedImage(named name: String) -> UIImage?{
let path = NSBundle.mainBundle().pathForResource(name, ofType: nil)!
let image = UIImage(contentsOfFile: path)
return image
}
}
This is how I solved my problem.

Related

Store sequence of images in CoreData

I tried to store images using CoreData. It is fine and I can store a single image. However how can I turn it to multiple images?
Currently, I have set the image field to Binary Data.
I save the image into the object by:
let imageData = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
item.image = imageData
try? self.viewContext.save()
How can I turn it into an array of imageData that can store in CoreData?
I tried to do this but it fails:
let imageData1 = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
let imageData2 = UIImage(imageLiteralResourceName: "Dummy2").jpegData(compressionQuality: 1)
item.image = [imageData1, imageData2]
try ? self.viewContext.save()
The compiler said that the attribute 'image' is Data? but not [Data]? type.
I have also tried to use the Type Transformable:
However, there is warning:
warning: Misconfigured Property: Items.imageArray is using a nil or insecure value transformer. Please switch to NSSecureUnarchiveFromDataTransformerName or a custom NSValueTransformer subclass of NSSecureUnarchiveFromDataTransformer
Any idea on the warning and how to resolve it?
On using the Transformable type, actually I can achieve this:
let imageData1 = UIImage(imageLiteralResourceName: "Dummy1").jpegData(compressionQuality: 1)
let imageData2 = UIImage(imageLiteralResourceName: "Dummy2").jpegData(compressionQuality: 1)
item.image = imageData1
item.imageArray = [imageData1!, imageData2!]
However, a few issues here:
It force to add ! to imageData, which indeed, should be optional in my case, I have no way to use ?? properly to give it a dummy imageData if that is found to be nil.
The same problem appear when I tried to display the array:
Image(uiImage: UIImage(data: (self.item.imageArray![0]))!)
.frame(width:300, height:300)
You can see that I have added ! to both imageArray! which can be nil and also the UIImage!
I would like to, instead provide default values for both cases, but I failed to use ?? to provide default value. Any idea?
You're trying to jam a square peg in a round hole. You can't assign an array of data into a data property or a transformable property because an array of data is neither data nor a transformable.
What you can do is to make your property a one-to-many relationship with another entity, let's call it ImageContainer, this other entity would have an imageData property. Now you could add as many images (within an image container) to your object.
Another alternative is to use a transformable value, which as I understand uses NSSecureCoding to transform your values into something CoreData can understand.
You should not save images directly in database, you will hit big performance issues.
Instead save the image name in the database and save the actual image with FileManager in the storage.
Then you can load the image/s from the file manager.

Trying to get the current value of a UIImage

My app is a slideshow that changes once a minute with a photo corresponding to each minute in the day. I currently have it checking the time and updating the image every tenth of second, as this will be on many devices at a large public event.
The relevant bits are:
#IBOutlet weak var theBrokenWatch: UIImageView!
if theBrokenWatch.image != UIImage(named: theImageName as String) {
theBrokenWatch.image = getUncachedImage(named: theImageName)
print("changed image to", theCurrentTime as String)
}
func getUncachedImage (named name : String) -> UIImage? {
if let imgPath = NSBundle.mainBundle().pathForResource(name, ofType: nil) {
return UIImage(contentsOfFile: imgPath)
}
return nil
}
Right now the if statement doesn't work. It did work when I was setting the image using imageNamed:, but that had major memory issues with 720 photos. (The CurrentTime is calculated and the matching image path built as theImageName.)
How can I check to see if the image needs to be changed (i.e. it's not what I just calculated as the current time)?
You don't want to use an uncached image to check for equality. Each time its generated, the image object will be different (the image data will be the same, but the wrapper object will be re-generated, and therefore not equal; image equality is not based of of actual bit contents).
Instead, key off the image name. Create a new var, imageName, and set that when you set the image. Then, when your timer fires, check your cached imageName against the new one, and only change the .image var if they're different.

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.

Swift keep pointer/reference to object or another a pattern?

I am converting a project from another tool and language:
Suppose I have a
singleimagecache: UIImage;
I now pass this to a structure which does
var myimage: UIImage = singleimagecache;
Now, this struct is passed to a function that does some work.
This function determines another image should be cached. In my original code, it would simply use myimage and assign its content some other image-bitmap data. Since the object-reference itself was not changed (only its content) singleimagecache would still point to valid fresh new cache data.
However, this is not possible in Swift since UIImage requires to be reconstructed like this:
myimage = UIImage(...)
But doing that would leave singleimagecache with wrong data
So that leaves me the following options:
Any support in Swift for keeping references in sync?
Any support in Swift for keeping pointers to objects (that themselves possibly can be nillable)
Wrap UIImage inside another object that is persistant and use that.
There is no built-in Swift support for what you wish to do. I would just make a wrapper class with 2 UIImage properties. One would be myimage and the other would be singleimagecache. You could then use the didSet property observer on myimage to achieve the desired synchronization so that singleimagecache will always be up to date with myimage. didSet will be called everytime a new value is stored/set in the property.
class imageCache
{
var myimage:
= UIImage() {
didSet {
singleimagecache = myimage
}
}
var singleimagecache = UIImage()
}

What's the best way to assert on a UIImage in a unit test?

Say I'm writing a unit test for a tableView:cellForRowAtIndexPath: delegate method on a view controller. This method could return a couple of different configurations of cells depending on the index path I pass in.
I can easily assert on the cell.textLabel.text property. But how can I assert that the cell.imageView.image property contains the correct image? Neither the image or the imageView have (public API) properties I can use to find out the image name or file name.
The best I've come up with is creating the smallest possible valid .png (using [UIImage imageWithData:] so I don't touch the disk in my unit tests) and asserting the byte array I get from cell.imageView.image is the one I expect. I've created an OCHamcrest matcher to make this a little nicer but it's an unsatisfying and inflexible approach.
Has anyone got a better idea?
If you're using [UIImage imagedNamed:], the images are cached. Excerpt from the UIImage class reference for imagedNamed::
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.
This means you can assert on cell.imageView.image == [UIImage imagedName:#"my_image"] as this is a pointer comparison and since the images are cached multiple calls to imageNamed: with the same name will return the same pointer. Even if memory gets tight, you should still be fine, as the UIImage class reference explains:
In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself.
Converting the images to Data and then comparing the Data. Since the image is just a pointer to memory location.
guard let data1 = image1?.pngData(), let data2 = image2.pngData() else {
XCTFail("Data should not be nil")
return
}
XCTAssertEqual(data1, data2)
swift5
You can compare the contents of UIImage directly using the isEqual method on a UIImage which will compare that the two images are like for like.
So in your tests, you can do:
let expectedImage = UIImage(named: "my_picture")
let returnedImage = SomeImageReturnedFromFunction()
XCTAssertEqualObjects(expectedImage, returnedImage) // will return true if SomeImageReturnedFromFunction returns my_picture
Reference: https://developer.apple.com/documentation/uikit/uiimage#overview
You can indeed compare with the "isEqual" method but rather like this :
let expectedImage = UIImage(named: "an_image")
let returnedImage = myFunctionRet() // optional ?
XCTAssert(returnedImage?.isEqual(expectedImage) == true)

Resources