iOS - Terminated due to memory issue error - ios

So i am doing a network calls to retrieve image and using that i am show a kind of a video.
After a while i can see the memory lose and energy impact:
after a while my app crushed and i got :"Terminated due to memory issue error"
Before that i got that error from the image caller method:"error from dataResponse:The operation couldn’t be completed. No space left on device"
This are the two method i use:
class InstallationViewController: BaseViewController {
func imageCaller(url: String , success: #escaping (UIImage) -> Void, failure: #escaping () -> Void) {
let handler = AuthenticateHandler()
self.urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: handler, delegateQueue: OperationQueue.main)
self.imageThumbnailTask = urlSession?.dataTask(with: URL(string:url)!) { data, res, err in
if err != nil {
print("error from dataResponse:\(err?.localizedDescription ?? "Response Error")")
failure()
return
}
DispatchQueue.main.async {
if let imageData = data, let image = UIImage(data: imageData) {
success(image)
URLCache.shared.removeAllCachedResponses()
}
}
}
self.imageThumbnailTask?.resume()
}
func imageThumbnailcall() {
self.indicaotrTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.HandleOverTime), userInfo: nil, repeats: false)
self.imageCaller( url: self.isShowingThermal ? self.thermalUrl : self.visualUrl, success: { (image) in
self.indicaotrTimer?.invalidate()
DispatchQueue.main.async{
self.imageLoaderIndicator.stopAnimating()
self.backGroundImageView.image = image
}
if self.isInVC {
self.imageThumbnailcall()
}
}) {
self.imageLoaderIndicator.stopAnimating()
}
}
Worth mentioning that this line:
let handler = AuthenticateHandler()
self.urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: handler, delegateQueue: OperationQueue.main)
for digest protocol

Looks like you have a retain cycle in your closure in the imageThumbnailcall function.
The closure creates a strong reference to self, and since it's a recursive function you will run out of memory pretty quick.
You need to capture self as [weak self] or [unowned self] in the closure.
Example using [unowned self]:
func imageThumbnailcall() {
self.indicaotrTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.HandleOverTime), userInfo: nil, repeats: false)
self.imageCaller( url: self.isShowingThermal ? self.thermalUrl : self.visualUrl, success: { [unowned self] (image) in
self.indicaotrTimer?.invalidate()
DispatchQueue.main.async{
self.imageLoaderIndicator.stopAnimating()
self.backGroundImageView.image = image
}
if self.isInVC {
self.imageThumbnailcall()
}
}) {
self.imageLoaderIndicator.stopAnimating()
}
}
If you want to learn more about

Looks like you're recursively calling imageThumbnailcall. If you don't end the recursion at some point, you could see exactly the symptoms that you are reporting.
if self.isInVC {
self.imageThumbnailcall()
}
Are you making sure to set isInVC properly so you break out of the recursive loop?

Related

Cancel a URLSession based on user input in Swift

I've centralized API calls for my App in a class called APIService.
Calls look like the one below:
// GET: Attempts getconversations API call. Returns Array of Conversation objects or Error
func getConversations(searchString: String = "", completion: #escaping(Result<[Conversation], APIError>) -> Void) {
{...} //setting up URLRequest
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let _ = data
else {
print("ERROR: ", error ?? "unknown error")
completion(.failure(.responseError))
return
}
do {
{...} //define custom decoding strategy
}
let result = try decoder.decode(ResponseMultipleElements<[Conversation]>.self, from: data!)
completion(.success(result.detailresponse.element))
}catch {
completion(.failure(.decodingError))
}
}
dataTask.resume()
}
I'm executing API calls from anywhere in the Application like so:
func searchConversations(searchString: String) {
self.apiService.getConversations(searchString: searchString, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
}
What I would like to achieve now is to execute func searchConversations for each character tapped by the user when entering searchString.
This would be easy enough by just calling func searchConversations based on a UIPressesEvent being fired. Like so:
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
searchConversations(searchString: SearchText.text)
}
}
My problem is this now:
Whenever a new character is entered, I'd like to cancel the previous URLSession and kick-off a new one. How can I do that from inside the UIPressesEvent handler?
The basic idea is to make sure the API returns an object that can later be canceled, if needed, and then modifying the search routine to make sure to cancel any pending request, if any:
First, make your API call return the URLSessionTask object:
#discardableResult
func getConversations(searchString: String = "", completion: #escaping(Result<[Conversation], APIError>) -> Void) -> URLSessionTask {
...
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
...
}
dataTask.resume()
return dataTask
}
Have your search routine keep track of the last task, canceling it if needed:
private weak var previousTask: URLSessionTask?
func searchConversations(searchString: String) {
previousTask?.cancel()
previousTask = apiService.getConversations(searchString: searchString) { result in
...
}
}
We frequently add a tiny delay so that if the user is typing quickly we avoid lots of unnecessary network requests:
private weak var previousTask: URLSessionTask?
private weak var delayTimer: Timer?
func searchConversations(searchString: String) {
previousTask?.cancel()
delayTimer?.invalidate()
delayTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { [weak self] _ in
guard let self = self else { return }
self.previousTask = self.apiService.getConversations(searchString: searchString) {result in
...
}
}
}
The only other thing is that you probably want to change your network request error handler so that the “cancel” of a request isn’t handled like an error. From the URLSession perspective, cancelation is an error, but from our app’s perspective, cancelation is not an error condition, but rather an expected flow.
You can achieve this by using a timer,
1) Define a timer variable
var requestTimer: Timer?
2) Update searchConversations function
#objc func searchConversations() {
self.apiService.getConversations(searchString: SearchText.text, completion: {result in
switch result {
case .success(let conversations):
DispatchQueue.main.async {
{...} // do stuff
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
}
3) Update pressesEnded
override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
{...} // handle special cases
default:
super.pressesEnded(presses, with: event)
self.requestTimer?.invalidate()
self.requestTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(searchConversations), userInfo: nil, repeats: false)
}
}

How can I update a view's progress bar using the AppSync S3ObjectManager?

I'm using AWSAppSyncClient to upload files but I'm struggling to connect the upload progress hook with the view.
AWSAppSyncClient is a property of the the application delegate initialized with an S3ObjectManager. The object manager method upload has access to the upload progress via the AWSTransferUtilityUplaodExpression:
expression.progressBlock = {(task, progress) in
DispatchQueue.main.async(execute: {
// Can we update the controller's progress bar here?
print("Progress: \(Float(progress.fractionCompleted))")
})
}
My controller invokes the upload by calling perform:
var appSyncClient: AWSAppSyncClient? // retrieved from the app delegate singleton
appSyncClient?.perform(mutation: CreatePostMutation(input: input)) { (result, error) in ...
What I am struggling with: how do I provide the S3ObjectManager a reference to the controller? I thought of instantiating the AWSAppSyncClient in each controller, and maybe using some sort of delegate pattern?
It's probably overkill to instantiate a new client on each view controller. Setup & teardown take a bit of time & system resources to perform, and you'd probably prefer to keep those activities separate from the view controller in any case, just for separation of responsibilities.
There isn't really a good way of registering a per-object listener, since mutations are queued for eventual, asynchronous delivery. Your delegate idea seems like the best approach at this point.
NOTE: Code below is untested, and not thread-safe.
For example, you could declare a singleton delegate that manages watchers for individual views that need to report progress:
class AppSyncS3ObjectManagerProgressWatcher {
typealias ProgressSubscription = UUID
static let shared = AppSyncS3ObjectManagerProgressWatcher()
private var watchers = [UUID: AppSyncS3ObjectManagerProgressDelegate?]()
func add(_ watcher: AppSyncS3ObjectManagerProgressDelegate) -> ProgressSubscription {
let subscription = UUID()
weak var weakWatcher = watcher
watchers[subscription] = weakWatcher
return subscription
}
func remove(_ subscription: ProgressSubscription?) {
guard let subscription = subscription else {
return
}
watchers[subscription] = nil
}
}
extension AppSyncS3ObjectManagerProgressWatcher: AppSyncS3ObjectManagerProgressDelegate {
func progressReportingExpression(forDownloadingObject object: AWSS3ObjectProtocol) -> AWSS3TransferUtilityDownloadExpression {
let expression = AWSS3TransferUtilityDownloadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forDownloadingObject: object, progress: progress)
}
return expression
}
func progressReportingExpression(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol) -> AWSS3TransferUtilityUploadExpression {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forUploadingObject: object, progress: progress)
}
return expression
}
func didReportProgress(forDownloadingObject object: AWSS3ObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forDownloadingObject: object, progress: progress)
}
}
func didReportProgress(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forUploadingObject: object, progress: progress)
}
}
}
Wherever you conform S3TransferUtility to S3ObjectManager, you would do something like:
extension AWSS3TransferUtility: AWSS3ObjectManager {
public func download(s3Object: AWSS3ObjectProtocol, toURL: URL, completion: #escaping ((Bool, Error?) -> Void)) {
let completionBlock: AWSS3TransferUtilityDownloadCompletionHandlerBlock = { task, url, data, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forDownloadingObject: s3Object)
let _ = self.download(
to: toURL,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
expression: progressReportingExpression,
completionHandler: completionBlock)
}
public func upload(s3Object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, completion: #escaping ((_ success: Bool, _ error: Error?) -> Void)) {
let completionBlock : AWSS3TransferUtilityUploadCompletionHandlerBlock = { task, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forUploadingObject: s3Object)
let _ = self.uploadFile(
s3Object.getLocalSourceFileURL()!,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
contentType: s3Object.getMimeType(),
expression: progressReportingExpression,
completionHandler: completionBlock
).continueWith { (task) -> Any? in
if let err = task.error {
completion(false, err)
}
return nil
}
}
}
And then in the progress reporting view:
override func awakeFromNib() {
super.awakeFromNib()
progressSubscription = AppSyncS3ObjectManagerProgressWatcher.shared.add(self)
}
func didReportProgress(forUploadingObject object: AWSS3InputObjectProtocol & AWSS3ObjectProtocol, progress: Progress) {
// TODO: Filter by object local URI/key/etc to ensure we're updating the correct progress
print("Progress received for \(object.getKeyName()): \(progress.fractionCompleted)")
self.progress = progress
}
As I noted, this code is untested, but it should outline a general approach for you to start from. I'd welcome your feedback and would like to hear what approach you eventually settle on.
Finally, please feel free to open a feature request on our issues page: https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues

How to manage download queue?

I am taking user input to download files from the server. The downloading task may include requesting web services.
I am expecting something like this:
1) Whenever the user selects a file to download or requesting for web
service, then it should be treated as one block of operation or task
and should go in the queue which will be managed globally at the app
level.
2) At the same time if the queue is empty then it should
automatically start executing the current task.
3) If queue contains
any operation then it should execute all old operation in
synchronously then execute the last one.
Can any one suggest how this can be done by the optimized way?
Take a look what I tried:
class func downloadChaptersFromDownloadQueue() {
let gbm = GlobalMethods()
for chapterDetail in gbm.downloadOpertationQueue.array.enumerated() {
if chapterDetail.element.chapterdata.state == .non || chapterDetail.element.chapterdata.state == .paused || chapterDetail.element.chapterdata.state == .downloading {
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloading
let s = DispatchSemaphore(value: 0)
self.downloadActivty(courseId: chapterDetail.element.courseId, mod: chapterDetail.element.chapterdata, selectedIndexpath: chapterDetail.element.cellForIndexpath, success: { (result) in
if (result) {
if (WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .downloaded)) {
s.signal()
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloaded
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": 1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
else {
s.signal()
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
}
else {
_ = WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .non)
s.signal()
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
})
s.wait()
}
}
}
Create an async queue. First use a dispatch group to track how many requests are completed and get notified when all are finished (completely async).
Next, enqueue ALL your requests. Each request should have a unique identifier so you know which request completed or failed (the chapterId & pageNumber in your case should be enough).
Execute all the requests at once (again it is async) and you will be notified when each one completes (on the main queue through your completion block). The completion block should be called with all the requests responses and their unique identifiers.
Example:
class NetworkResponse {
let data: Data?
let response: URLResponse?
let error: Error?
init(data: Data?, response: URLResponse?, error: Error?) {
self.data = data
self.response = response
self.error = error
}
}
class NetworkQueue {
static let instance = NetworkQueue()
private let group = DispatchGroup()
private let lock = DispatchSemaphore(value: 0)
private var tasks = Array<URLSessionDataTask>()
private var responses = Dictionary<String, NetworkResponse>()
private init() {
}
public func enqueue(request: URLRequest, requestID: String) {
//Create a task for each request and add it to the queue (we do not execute it yet). Every request that is created, we enter our group.
self.group.enter();
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
//Only one thread can modify the array at any given time.
objc_sync_enter(self)
self.responses.updateValue(NetworkResponse(data: data, response: response, error: error), forKey: requestID)
objc_sync_exit(self)
//Once the request is complete, it needs to leave the group.
self.group.leave()
}
//Add each task to the queue.
self.tasks.append(task)
}
public func execute(completion: #escaping (_ responses: Dictionary<String, NetworkResponse>) -> Void) {
//Get notified on the main queue when every single request is completed (they all happen asynchronously, but we get one notification)
self.group.notify(queue: DispatchQueue.main) {
//Call our completion block with all the responses. Might be better to use a sorted dictionary or something here so that the responses are in order.. but for now, a Dictionary with unique identifiers will be fine.
completion(self.responses)
}
//Execute every task in the queue.
for task in self.tasks {
task.resume()
}
//Clear all executed tasks from the queue.
self.tasks.removeAll()
}
}
EDIT (Using your own code):
class func downloadChaptersFromDownloadQueue() {
let gbm = GlobalMethods()
let group = DispatchGroup()
let lock = NSLock()
//Get notified when ALL tasks have completed.
group.notify(queue: DispatchQueue.main) {
print("FINISHED ALL TASKS -- DO SOMETHING HERE")
}
//Initially enter to stall the completion
group.enter()
defer {
group.leave() //Exit the group to complete the enqueueing.
}
for chapterDetail in gbm.downloadOpertationQueue.array.enumerated() {
if chapterDetail.element.chapterdata.state == .non || chapterDetail.element.chapterdata.state == .paused || chapterDetail.element.chapterdata.state == .downloading {
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloading
//Enter the group for each downloadOperation
group.enter()
self.downloadActivty(courseId: chapterDetail.element.courseId, mod: chapterDetail.element.chapterdata, selectedIndexpath: chapterDetail.element.cellForIndexpath, success: { (result) in
lock.lock()
defer {
group.leave() //Leave the group when each downloadOperation is completed.
}
if (result) {
if (WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .downloaded)) {
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .downloaded
lock.unlock()
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": 1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
else {
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
lock.unlock()
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
}
else {
_ = WC_SQLite.shared.updateChapterState(courseId: chapterDetail.element.courseId, chapterId: chapterDetail.element.chapterdata.id, state: .non)
gbm.downloadOpertationQueue[chapterDetail.offset].chapterdata.state = .non
lock.unlock()
NotificationCenter.default.post(name: NSNotification.Name(("DownloadChapter")), object: self, userInfo: ["progress": -1.0, "notifIdentifier":(chapterDetail.element.cellForIndexpath)])
}
})
}
}
}
Again, this is asynchronous because you don't want the user waiting forever to download 100 pages..
For tasks like these the first thing you need to do is to do them asynchronously using dispatch_async, so that they are on a different thread and don't impact the performance of the application (or freeze it.)
Whenever your downloads succeeds/fails, you can always control what happens next in it's completion block. (I suggest you use recursion for what you're trying to achieve as it suits your needs).
Hope this helps!

Nested asynchronous blocks of code in Swift

I am trying to nest a NSTimer.scheduledTimerWithTimeInterval call inside of an asynchronous NSURLSession.sharedSession().dataTaskWithRequest in Swift 2.0, but the code block inside of the test does not seem to get evaluated.
For example:
class testc{
#objc func test()
{
print("hello");
}
func loop()
{
if let url = NSURL(string : "https://www.google.com")
{
let url_req = NSURLRequest(URL: url);
let task = NSURLSession.sharedSession().dataTaskWithRequest(url_req)
{ data, response, error in
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(testc.test), userInfo: nil, repeats: true)
}
task.resume()
}
}
}
If we initialize this class and run loop nothing happens, the function test was never evaluated.
You need two changes to your code.
After creating the dataTask. You need to ask it to resume() to send the request.
The timer needs to be called on the main thread.
In your case, the dataTask is an asynchrnous task that runs on a background thread. In the implementation below, we are jumping back to the main thread to fire the timer.
I have added a counter to verify if the timer is firing repeatedly.
See updated code below.
class testc{
static var counter : Int = 0
#objc func test()
{ testc.counter++
print("hello -> \(testc.counter)");
}
func loop()
{
if let url = NSURL(string : "https://www.google.com")
{
let url_req = NSURLRequest(URL: url);
let task = NSURLSession.sharedSession().dataTaskWithRequest(url_req){ data, response, error in
dispatch_async(dispatch_get_main_queue(), {
self.setupTimer()
})
}.resume()
}
}
func setupTimer() {
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(testc.test), userInfo: nil, repeats: true)
}
}
let theBoy = testc()
theBoy.loop()

Alamofire and Concurrent Operation Queues

I'm using Alamofire (AF) in a concurrent operation queue to run network commands in my project. Sometimes AF's completionHandler doesn't fire, leaving my NSOperation hanging (waiting for a finish message that it will never receive).
Eg. I'll see the "response" log, but no corresponding "see me" log from AF's dispatch_async below:
public func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
NSLog("markse-response")
dispatch_async(self.delegate.queue, {
NSLog("markse-see me")
dispatch_async(dispatch_get_global_queue(priority, 0), {
if var error = self.delegate.error {
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, nil, error)
})
} else {
let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data, nil)
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(self.request, self.response, responseObject, serializationError)
})
}
})
})
return self
}
This is my NSOperation (AsynchronousCommand is an NSOperation subclass):
import Alamofire
class SettingsListCommand: AsynchronousCommand {
override func execute() {
if cancelled { return }
let endpoint = "https://api.github.com/users/cog404/orgs"
DLogVerbose("AF request")
weak var weakSelf = self
Alamofire.request(.GET,
endpoint,
parameters:nil)
.responseJSON {(request, response, JSON, error) in
DLogVerbose("AF response")
if let strongSelf = weakSelf {
if strongSelf.cancelled {
strongSelf.finish()
return
}
DLogVerbose(JSON)
strongSelf.finish()
}
}
}
}
This only happens 'occasionally', making this very difficult to debug.
Does anyone with a good understanding of threading know what could be going wrong?
Any advice very much appreciated. A project to help illustrate the problem is here.
The request delegate's dispatch queue is serial, meaning that it will only process a single block at a time, in order of when the blocks were dispatched (FIFO). If the second log statement isn't firing, it's because the previous block didn't yet finish.

Resources