Loading GIFs in iOS consumes too much memory - ios

I've been testing around a lot of open-source animated-gif libraries to load GIF files into our Swift project.
Most of them claim to be high-performance libraries, however, whenever I load an animated gif my application uses around 8MB of memory.
The problem is that this dedicated memory space seems to never be released. We can see it growing linearly:
And it makes me wonder if I'm doing the right thing here. Is this behavior correct, or is it potentially bad for the user?

you would need to remove the Gif images manually for them to get cleared from the memory.
Example: gifViwer can be your Gif Viewer in this example
self.gifViewer.removeFromSuperview()
self.gifViewer = nil
if you had to assign a delegate then you can also add
self.gifViewer.Delegate = nil
Note: you need to run this when the viewControler is being completely unloaded as the app will start looking for gifViewer in the view while it has been removed. if you need to clear the gif image from the memory without unloading the view then I suggest to use the steps above and then adding the view to the superView programatically. this is not going to work with the interface designer and IBOutlets
let gifViewer = yourGifViewerClass(frame: CGRect(x: 20, y: 20, width: self.view / 2, height: self.view / 2))
// setup your gifViewer

Related

Rendering GIF images causes extensive memory usage in Swift?

When I try to render a GIF image (selected from photo library, whose data type is PHAsset), I use the following code:
PHImageManager().requestImageData(for: asset, options: nil) { (data, _, _, _) in
if let imageData = data {
imageView.image = UIImage.gif(data: imageData)
}
}
Where .gif is an extension to UIImage I copied from here, I believe many people use it.
The problem is, when I run the above code, the memory usage rises by about 20+MB, which is not outrageous, however, when I delete this selected GIF asset, the memory usage does not drop. And if I go ahead and select more GIF assets, for each one I select and run the above code, the memory usage rises by 20+MB. Now it's not acceptable anymore as the memory usage will just go up and never drop until the app crashes.
I understand why the memory usage goes up when I render a GIF image, I mean, the image data sits in the memory. What I don't know is how to I deallocate the chunk of memory when I wish to delete the GIF image?
--------------UPDATE-----------------
The UIImageView on "TestScreen" displays the thumbnail of the selected GIF image
When I press the GIF image, the app will open the image in full screen mode and if it's a GIF image, it'll play the animated image by running the above code
The memory goes up and never drops when I repeatedly open the GIF image in full screen
The memory leak may be in your own code rather than in the .gif extension. Maybe the view controller that displays the .gif does not deinitiazile when you close it. Wherever the leak is, here are two ways to find it:
a) A very simple approach is to add a print command to the de-/initialization of your objects. So you can see in the console when an object should be deinitialized and free up memory but in fact doesn't, e.g.:
class MyClass {
init() {
print("init: \(self)")
}
deinit {
print("deinit: \(self)")
}
}
b) A more insightful and convenient method is to use Xcode Instruments.
It is a much more powerful way of inspecting the memory management of your app than adding print commands. Also, once you have figured out how to use it, you will love it as it automates a lot of steps and eventually you will be able to check for memory leaks with just a few clicks.
Here you can find a tutorial on how to use Xcode Instruments.
If you post the code for the screen with the black background that opens and displays the GIF it may give a hint what the problem might be. In most cases it is something like a delegate that is not declared weak or another form of circular strong reference.

iOS Photo Gallery Memory Leak / Crash when using Push Segues

I've created a small photo gallery which is presenting a new view controller with a larger version of the photo and some additional text when it is clicked:
The problem is - after going through a handful of images - the application crashes due to overuse of memory. I attempted to resolve this by compressing the images in order to leave a smaller memory footprint, but the issue remains and I'm not sure what else I can do to resolve this issue.
Also - there is almost no code to doing this since I'm using storyboard's push segues as well as the built in navigation item to go back between viewControllers.
P.S.
If you feel source code is necessary to provide insight in this instance - it can be found here:
https://www.dropbox.com/s/q1qq8pq4tzv8wyo/EXAMPLE%20BUILD.zip?dl=0
To resolve this issue you have to use this trick; Put a "placeHolder" image in your cell's imageview in "StoryBoard". Don't load the images all at once in your "ViewController", load them one by one by running a loop or in your "cellForRowAtIndexPath()" method and add a delay in each iteration (Load first image then add a delay, load second image and add a delay, then for third one and so on up to the last image).
If you want to know how to add a delay then check this link:
NSTimer - how to delay in Swift
To resolve this issue I simply resized the images - I noticed I accidentally used a gigantic (6000 x 4000) image and even though I compressed the images iOS had to crunch pretty hard to resize them into the view... thereby causing the memory leak and subsequent crash.
Resizing them to 600x400 did the trick.

UICollectionView & AVPlayer Performance

I have a UICollectionView whose cells each have an AVPlayerLayer, all playing a video at the same time. About 9 cells fit on the screen at a time and it's pretty laggy so I need ways to boost the performance to achieve smooth scroll. There's a few things I've tried already:
1) Each cell has only one instance of AVPlayer. It doesn't get created, instead, player.replaceCurrentItemWithPlayer is called when the video url changes.
2) Because I'm using ReactiveCocoa, it's trivial to skip repeat urls to avoid playing the same video twice.
What else can I do to speed up the scroll and performance?
First I want to say it's a bit crazy to see several players in action at the same time: it's a heavy task of rendering anyway.
As far as I know, simply scaling/re-framing the video to smaller size doesn't make it less content to render: if you have a 100x100 picture to render and you render it in a frame of 10x10, it still consumes the same amount of memory as the original picture would consume; same thing goes for videos. So, try to make your video assets having similar resolution as the frame where you would present them.
Store each cell in an NSCache or NSMapTable using the NSIndexPath as the key for each; then, whenever you need a cell, call one directly from the cache or map table.
Obviously, you'll have to create all the cells at once, but you'll get the same scrolling performance as you do with nothing in the cells at all.
Need sample code?
Here's my latest iteration of a perfectly smooth-scrolling collection view with real-time video previews (up to 16 at a time):
https://youtu.be/7QlaO7WxjGg
It even uses a cover flow custom layout and "reflection" view that mirrors the video preview perfectly. The source code is here:
http://www.mediafire.com/download/ivecygnlhqxwynr/VideoWallCollectionView.zip
Unfortunately, UIKit will hit bottlenecks and drop frames when put under pressure for this use case. If you can muster refactoring code to use Facebook's AsyncDisplayKit / then this would be your best bet.
For objective-c / this project here is a good reference.
https://github.com/TextureGroup/Texture/tree/master/examples/ASDKTube
- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row];
VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject];
return cellNode;
}
there is swift code available if you dig deep enough in github.

Preloading mathjax into a collection iOS Swift

(Please excuse the amount of text)
I'm trying to render a bunch of math questions in an app. Say there's 30 questions, and each question has about 7-8 fields (instructions, question, answer choices, and review).
I am able to do this successfully when I load them into containers with MathJax rendering (I also use a callback in the JS to make sure the content is loaded before being viewed).
The thing is, the process takes a few seconds each time, and I'd like to preload the entire set of questions, and the views, so that after the initial load, question loading will be instantaneous.
I think using a collection would be the best way to do this, and I've been trying two methods with no luck:
load the collection with all the content as cells/items, then jump to the cell (I was facing the issue that only the viewed cell would load, and then I figured if they did all load, I would face the issue that I am facing now with the next option)
load all the content into an invisible subview, then load the subview into a cell when it is selected (may offer more control over how things are loaded)
In either case, I run into the same issue (and this even happened in the initial one-by-one loading with multiple containers, where if I jumped around too quickly, the app would crash).
When loading a bunch of subviews, each with Mathjax, the app gets overwhelmed and crashes (I believe due to memory or cpu, it says "lost connection to iPad"). This doesn't seem to happen if there is a lot of Mathjax within one view. I think it's because the processes don't wait for each other to finish before beginning, and the app can't handle running multiple Mathjax rendering at the same time.
I think a possible solution is to make sure that the loop that loads the view does not reloop until the Mathjax is done rendering, but cannot figure out a way to make the loop wait (once it initiates the loading it keeps incrementing).
Hopefully someone can provide a good idea for:
how to structure the preloading mechanism in general
or
how to specifically make the loop wait for the view to render before relooping (I have a process that recognizes when the Mathjax has been fully rendered, but I can't figure out how to make the loop wait for it. This process is currently used to simply unhide the view once it's done).
for var i = 0; i < objectsKeyValues.count; i++ {
let iInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["instructions"]! + cDParentVC.string2
let qInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["question"]! + cDParentVC.string2
... for a few more of these
let iView = WKWebView.init(frame: CGRectMake(5,5,300,35), configuration: config)
...same
iView.loadHTMLString(iInput, baseURL: NSBundle.mainBundle().bundleURL)
..same
viewDictArray.append(["iView":iView,"qView":qView,"a1View":a1View,"a2View":a2View,"a3View":a3View,"a4View":a4View,"a5View":a5View,"rView":rView,"pView":pView])
cDLargeContent.invisiView.addSubview(viewDictArray[i]["iView"]!)
...same
}
I suspect NSOperations or GCD could also provide the necessary controls, but I am pretty green with programming and haven't been able to implement those concepts successfully, let alone verify if they would solve this issue.

Suspend redrawing while changing multiple children

I have a UIViewController that after asynchronously loading some data needs to resize some child views (Some UITableViews, and some UIScrollViews). This works just fine in the simulator, but on an actual device, it hangs for a long time (as much as 30 seconds in one case). I think the problem is that it wants to recalculate everything after each changed to the child view and I would like to be able to tell it to defer any recalculations until I've resized everything, but I'm drawing a blank on finding a way to do that. Is there a mechanism to do this?
So what I'm doing is something like this (I'm using C# with Xamarin but input in Obj-C would also be appreciated!):
// Need to suspend layout here...
MyTable.Frame = new RectangleF(...);
SomeOtherTable.Frame = new RectangleF(...);
ScrollView.ContentSize = new SizeF(...);
AnotherScrollView.ContentSize = new SizeF(...);
AnotherScrollView.Frame = new RectangleF(...);
// Ok - now you can redraw!
Update: Jacob's suggestion of disabling animation didn't seem to help, but I think I've isolated the problem to the table resizing. They seem to have the biggest impact on performance, but I'm not sure why, or how to mitigate the problem. I may follow up with a separate question on that.

Resources