Alamorefile upload images timeout when using many threads - alamofire

I'm using moya to upload many images by using OperationQueue to control maxConcurrentOperationCount. Supposing i have 100 images, uploading 5 images everytime. Alamorefire timeout sets to 10 seconds.
Uploading one image is very fast, nerver triggering timeout. But when i uploading 100 images using the method below, even using multithread, it triggering timeout. Why?
Thank you!
queue = OperationQueue()
queue.maxConcurrentOperationCount = 5
var i = 0
for image in photos {
autoreleasepool {
let operation:BlockOperation = BlockOperation(block: {
[weak self] in
guard let strongSelf = self else {return}
print("hyl cur thread %#", Thread.current)
strongSelf.uploadImage(image)
return
})
i += 1
queue.addOperation(operation)
}
}
private func uploadImage(_ image: UIImage) {
AladdinProvider.rx.request(.upload(access_token: UserInfo.instance.access_token!, file_name: "file_name", data: image)).asObservable().mapJSON().mapObject(type: AlbumDatas.self).subscribe(onNext: {
[weak self] result in
guard let strongSelf = self else {return}
// TODO success
}, onError: {
error in
print(\(error))
}).disposed(by: disposeBag)
}

Fundamentally, this is an issue with creating and resuming URLSessionTasks before they're allowed to run. This is exacerbated by Alamofire, which creates the URLSessionTasks immediately upon creation of a corresponding *Request value. Increasing the timeout of the URLRequests you're using for image downloads could help. A more thorough solution would be to stop creating your Alamofire requests immediately, instead only creating them as the first few requests complete. This should prevent the associated tasks from timing out.

Related

WidgetKit not calling urlSessionDidFinishEvents

I'm trying to implement downloading timeline data in a widget and so I created a background URLSession with a corresponding data task to download the JSON:
let session = URLSession(
configuration: .background(withIdentifier: identifier),
delegate: self,
delegateQueue: nil
)
let request = URLRequest(url: ...)
session.dataTask(with: request).resume()
On my widget I then added the onBackgroundURLSessionEvents to store the completion handler, as per the Apple docs:
.onBackgroundURLSessionEvents { identifier in
return SessionCache.shared.isValid(for: identifier)
} _: { identifier, completion in
let data = SessionCache.shared.sessionData(for: identifier)
data.sessionCompletion = completion
}
However, neither the urlSessionDidFinishEvents(forBackgroundURLSession:) nor the onBackgroundURLSessionEvents methods are called. When the network download completes, it just calls the normal urlSession(_:task:didCompleteWithError:) method.
I'm clearly missing something here, but I'm just not seeing what.
Apple's documentation isn't 100% clear on this, but you need to use URLSession.downloadTask instead of dataTask for background sessions. URLSessionDataTasks deliver bytes to those specific delegate methods in your in-memory process. Background download & upload tasks are handed off to nsurlsessiond and only delivered back to your app when they are fully resolved.

Sending HTTP requests in a MacCatalyst app opened in command line [duplicate]

When writing a Command Line Tool (CLT) in Swift, I want to process a lot of data. I've determined that my code is CPU bound and performance could benefit from using multiple cores. Thus I want to parallelize parts of the code. Say I want to achieve the following pseudo-code:
Fetch items from database
Divide items in X chunks
Process chunks in parallel
Wait for chunks to finish
Do some other processing (single-thread)
Now I've been using GCD, and a naive approach would look like this:
let group = dispatch_group_create()
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for chunk in chunks {
dispatch_group_async(group, queue) {
worker(chunk)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
However GCD requires a run loop, so the code will hang as the group is never executed. The runloop can be started with dispatch_main(), but it never exits. It is also possible to run the NSRunLoop just a few seconds, however that doesn't feel like a solid solution. Regardless of GCD, how can this be achieved using Swift?
I mistakenly interpreted the locking thread for a hanging program. The work will execute just fine without a run loop. The code in the question will run fine, and blocking the main thread until the whole group has finished.
So say chunks contains 4 items of workload, the following code spins up 4 concurrent workers, and then waits for all of the workers to finish:
let group = DispatchGroup()
let queue = DispatchQueue(label: "", attributes: .concurrent)
for chunk in chunk {
queue.async(group: group, execute: DispatchWorkItem() {
do_work(chunk)
})
}
_ = group.wait(timeout: .distantFuture)
Just like with an Objective-C CLI, you can make your own run loop using NSRunLoop.
Here's one possible implementation, modeled from this gist:
class MainProcess {
var shouldExit = false
func start () {
// do your stuff here
// set shouldExit to true when you're done
}
}
println("Hello, World!")
var runLoop : NSRunLoop
var process : MainProcess
autoreleasepool {
runLoop = NSRunLoop.currentRunLoop()
process = MainProcess()
process.start()
while (!process.shouldExit && (runLoop.runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 2)))) {
// do nothing
}
}
As Martin points out, you can use NSDate.distantFuture() as NSDate instead of NSDate(timeIntervalSinceNow: 2). (The cast is necessary because the distantFuture() method signature indicates it returns AnyObject.)
If you need to access CLI arguments see this answer. You can also return exit codes using exit().
Swift 3 minimal implementation of Aaron Brager solution, which simply combines autoreleasepool and RunLoop.current.run(...) until you break the loop:
var shouldExit = false
doSomethingAsync() { _ in
defer {
shouldExit = true
}
}
autoreleasepool {
var runLoop = RunLoop.current
while (!shouldExit && (runLoop.run(mode: .defaultRunLoopMode, before: Date.distantFuture))) {}
}
I think CFRunLoop is much easier than NSRunLoop in this case
func main() {
/**** YOUR CODE START **/
let group = dispatch_group_create()
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for chunk in chunks {
dispatch_group_async(group, queue) {
worker(chunk)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
/**** END **/
}
let runloop = CFRunLoopGetCurrent()
CFRunLoopPerformBlock(runloop, kCFRunLoopDefaultMode) { () -> Void in
dispatch_async(dispatch_queue_create("main", nil)) {
main()
CFRunLoopStop(runloop)
}
}
CFRunLoopRun()

UIView got rendered before data from API returned using SwiftyJSON and Alamofire

I want to fetch data using API request.The data is fetch using SwiftyJson and Alamofire. The problem is that the data is fetched but view gets loaded before the values are fetched.How can I Solve the problem?My code is as below:
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
}else{
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
It is important to understand that apps run multiple threads. The main thread, also called the UI thread, performs the actions for the visible parts of the app, including showing views, reacting to buttons and so on.
You cannot block the Main thread.
When you make calls to external resources like API calls or loading external images, those get executed on a separate thread. That thread is independent of the main thread. Neither thread waits for the other. The app will still react to buttons while the data is loading. What you are asking for is to prevent showing the view until the data is loaded. You can do this, but you must understand that this could take time depending on your network connection. There are two approaches you can take.
Transition to the view that shows the data but put a "Loading" element on the screen until the data loads then remove the "Loading" element then redisplay the view with the data.
Load the data before you show the view. Make the previous view load the data then segue to the view that has to show the data.
You must also decide if this data loads ONCE or every time the view is displayed. By placing the call in viewDidLoad, the API call will only happen once until the app is restarted. If you want the data to load every time the screen is shown, put the call in viewWillAppear.
// Approach #1
func fetchData(){
self.showSpinner()
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
self.hideSpinner()
if response.result.isSuccess {
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// Actually update the relevant screen elements here.
} else {
print("Error \(String(describing: response.result.error))")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchData()
}
override func viewWillAppear() {
super.viewWillAppear()
fetchData()
}
func showSpinner() {
// IMPLEMENT ME
}
func hideSpinner() {
// IMPLEMENT ME
}
Alamofire mathod runs asynchronously. Not in UIThread.Then you have to reload those views after completion of method run.
As Example - tableview
func fetchData(){
Alamofire.request(favUrl, method: .get, parameters: [:]).responseJSON {
response in
if response.result.isSuccess{
let dataFetched : JSON = JSON(response.result.value!)
//print(dataFetched)
let titleDisp = dataFetched["title"].arrayObject as? [String]
//print(titleDisp)
self.trackList = dataFetched["track_id"].arrayObject as? [String]
print(self.trackList)
// In here you have to reload, set your uiviews or all calculation
tableview.reloadData()
}else{
print("Error \(String(describing: response.result.error))")
}
}
}

AVCaptureSession Crashing when using webRTC

I am using WebRTC and its using AVCaptureSession. It works fine a few times but sometimes its getting crashed with this Exception.
Assertion failed: (_internal->figCaptureSession == NULL), function
-[AVCaptureVideoPreviewLayer attachToFigCaptureSession:], file /BuildRoot/Library/Caches/com.apple.xbs/Sources/EmbeddedAVFoundation/EmbeddedAVFoundation-1187.37.2.1/Aspen/AVCaptureVideoPreviewLayer.m
I recently had this problem aswell. In my code i kept an instance of AVCaptureOutput and added and removed it. When trying to add the same AVCaptureOutput instance to the same capture session again, this error appeared.
This is how i solved it:
private var _captureOutput: AVCaptureOutput?
var captureOutput: AVCaptureOutput {
guard let captureOutput = _captureOutput else {
let photoOutput = AVCapturePhotoOutput()
photoOutput.isHighResolutionCaptureEnabled = true
_captureOutput = photoOutput
return photoOutput
}
return captureOutput
}
Initialize the instance once when needed and when removed, also nullify it.
captureSession.outputs.forEach { [weak self] output in
self?.captureSession.removeOutput(output)
self?._captureOutput = nil
}
Before using instance of RTCCameraPreviewView you have to nill'ify its captureSession and assert will go away. Faced same issue.

Best practise to Use NSURLSession to receive data from the server on the background(not on the main theard)

How to receive data from the server(the server echo's it as JSON) using NSURLSession in the most efficient way?
Example:
I'm trying to use Instagram's news feed as a save template. They show 10 images and then it will load 10 more(from the server) when you pull down and will not block the user (They receive/download it on the background - while the users are still using the app).
I want to do the same thing in the most efficient way. In my example the screen is frozen until data is fetched. How can I avoid blocking the user?
var imgurl = "http://www.joomlaworks.net/images/demos/galleries/abstract/7.jpg"
var sessionConfog = NSURLSessionConfiguration.defaultSessionConfiguration()
var sessions : NSURLSession = NSURLSession(configuration: sessionConfog, delegate: nil, delegateQueue: nil)
var getImageTask = sessions.downloadTaskWithURL(NSURL(string: imgurl)!, completionHandler: { (location : NSURL!, response : NSURLResponse!, error : NSError?) -> Void in
var image : UIImage = UIImage(data: NSData(contentsOfURL: location)!)!
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.iamgeview.image = image
let m = self.saveImage(image, path: self.documentsDirectory())
println(m)
})
})
getImageTask.resume()
EDIT:
I just noticed, you are NOT using NSURLSession correctly! In your completion block I see this:
var image : UIImage = UIImage(data: NSData(contentsOfURL: location)!)!
DON'T do this. The NSURLSession download task already downloaded the data for you, you don't have to download it again with the method 'contentsOfURL'. Also that method is synchronous, and blocks the calling thread until the contents of the URL are loaded. You should never use that method to make network URLs.
What you need to do is implement the delegate methods for the download task so you get the data when it completes. An easier way for you is to use dataTaskWithURL:completionHandler - for that message, the completion handler receives the NSData from the response.
END EDIT
You are using NSURLSession correctly, but if you profile your app, most probably you will see that the most expensive operation here, and probably the one that blocks the UI thread, is:
let m = self.saveImage(image, path: self.documentsDirectory())
If you can save your image in a separate serial queue, then you'll see a big improvement.