I'm trying to render some views in background thread to not affect the main thread. That was never a problem before Xcode 9.
DispatchQueue.global(qos: .background).async {
let customView = UIView(frame: .zero)
DispatchQueue.main.async {
self.view.addSubview(customView)
}
}
UIView.init(frame:) must be used from main thread only
This error occurs in the second line.
Update
The Apple UIView Documentation actually says in the Threading Considerations section:
Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself, but all other manipulations should occur on the main thread.
Xcode 9 has a new runtime Main Thread Checker that detects call to UIKit from a background thread and generate warnings.
I know its meant to generate warnings and not crash the app, but you can try disabling Main Thread Checker for your test target.
I tried this code in a sample project, the debugger paused at the issue (as it is supposed to), but the app didn't crash.
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
let v = UIView(frame: .zero)
}
}
Main Thread Entry
You can enter the main thread as follows
DispatchQueue.main.async {
// UIView usage
}
You can use this function
func downloadImage(urlstr: String, imageView: UIImageView) {
let url = URL(string: urlstr)!
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else { return }
DispatchQueue.main.async { // Make sure you're on the main thread here
imageview.image = UIImage(data: data)
}
}
task.resume()
}
How to use this function?
downloadImage(urlstr: "imageUrl", imageView: self.myImageView)
For Objective C:
dispatch_async(dispatch_get_main_queue(), ^{
// UIView usage
self.view.userInteractionEnabled = YES;
});
Related
Why does this code crash the app if done on the main queue?
DispatchQueue.main.sync
{
NSLog("Start again")
}
Whereas, if done on any other queue, it works.
private let internalQueue = DispatchQueue(label: "RealmDBInternalQueue")
internalQueue.async
{
DispatchQueue.main.sync
{
NSLog("Start again")
}
}
I wanted to know how exactly these instructions were passed to Main from the internal queue and how the internal queue gets to know when work is done.
I have an NSManagedObjectContext which is initialised from newBackgroundContext of the persistentContainer as following:
managedContext = coreDataStack.persistentContainer.newBackgroundContext()
This is how persistentContainer looks like:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myXCDataModelName")
container.loadPersistentStores(completionHandler: { [weak self] (_, error) in
if let self = self,
let error = error as NSError? {
print("error!")
}
})
return container
}()
I'm using newBackgroundContext to make sure any CRUD operation with CoreData can be done safely, regardless what thread attempts to make changes on the managedContext, instead of making sure or forcing each operation is done on main thread.
I have a saveContext method where I try to perform save operation on managedContext inside performAndWait block as following:
managedContext.performAndWait {
do {
guard UIApplication.shared.isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It looks like performAndWait is most of the time runs on main thread but when it is run on another thread, the thread checker generates a warning for following check UIApplication.shared.isProtectedDataAvailable since it should be done on main thread.
I decided to run a selector on main thread for this check so I declared a isProtectedDataAvailable Bool by defaulting it to false at class level and then update its value when this selector runs.
#objc private func checker() {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
Now I refactored the performAndWait block as following to run the check on main thread if it is called from another thread.
managedContext.performAndWait {
do {
if Thread.isMainThread {
checker()
} else {
print("RUNNING ON ANOTHER THREAD")
Thread.current.perform(#selector(checker),
on: Thread.main,
with: nil,
waitUntilDone: true,
modes: nil)
}
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It seems to be working fine when I run it on simulator or real device, I generate different core data related operations which would trigger saving context both on main and background threads.
But what happens is, if I put some breakpoints inside performAndWait block and stop execution to examine how the code block is working, it sometimes results in application freeze when I continue execution, like a deadlock occurs. I wonder if it is somehow related to stopping execution with breakpoints or something is wrong with my implementation even though it is working fine without breakpoints, no app freeze or anything.
I'm worried because before going with this solution, I tried synchronizing on main thread by the inspiration from this answer to just switch to main thread to make this check, as following (which resulted in app freeze and I assume a deadlock) inside performAndWait block:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
I had to use sync instead of async, because I had to retrieve updated value of isProtectedDataAvailable before proceeding execution.
Any ideas on this?
I assume your question is about the right approach so that the code is working fine and it allows to set any breakpoints without running into deadlocks. :)
Why not try it this way (from what you stated i cannot see reasons against it):
First evaluating UIApplication.shared.isProtectedDataAvailable on the main thread. (I guess that there are more complex conditions that must hold not only UIApplication.shared.isProtectedDataAvailable = true, but to keep it simple ...)
If UIApplication.shared.isProtectedDataAvailable (and otherConditions) evaluates as true continue with data processing and saving on background thread. As managedContext was created as a newBackgroundContext it can be used there.
Instructions on main thread
if UIApplication.shared.isProtectedDataAvailable && otherConditions {
DispatchQueue.global(qos: .userInitiated).async {
dataProcessing()
}
}
Instructions on background thread
static func dataProcessing() {
// get the managedContext
let context = AppDelegate.appDelegate.managedContext
context.performAndWait {
if context.hasChanges {
do {
try context.save()
} catch {
print("error!")
}
}
}
}
For some reason, moving the following check and synchronising with main queue outside of the performAndWait solves all the problems.
No deadlock or app freeze occurs either with break points or without, regardless which thread triggers the method which contains performAndWait
So the method body looks like following now:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
managedContext.performAndWait {
do {
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error")
}
}
I know any updates to UI should be run inside the main queue using the below syntax:
dispatch_async(dispatch_get_main_queue()) {
UI update code here
}
What about these other cases?
In my viewDidLoad(), I have code to stylize the navbar and toolbar, like below:
let nav = self.navigationController?.navigationBar
nav?.barStyle = UIBarStyle.Default
nav?.tintColor = UIColor.blackColor()
nav?.barTintColor = UIColor(red:133.0/255, green:182.0/255, blue:189.0/255, alpha:1.0)
let toolBar = self.navigationController?.toolbar
toolBar?.barTintColor = UIColor(red:231/255, green:111/255, blue:19.0/255, alpha:1.0)
toolBar?.tintColor = UIColor.blackColor()
Should I wrap this code inside the main queue as well?
In my tableView cellForRowAtIndexPath function, should I wrap all the code setting up the UI of each table cell in the main queue as well?
When I present a new modal controller (self.presentViewController(modalController, animated: true, completion: nil), should I wrap this inside the main queue?
The answer to all your questions is "no"
Unless specified in the documentation, all UIKit functions will be called on the main queue.
Generally, you'll need to specifically run on the main queue after calling an async func with a completion handler that runs on a background queue. Something like…
// downloadImage is some func where the
// completion handler runs on a background queue
downloadImage(completion: { image in
DispatchQueue.main.async {
self.imageView.image = image
}
})
Yeah, calling the main queue is something you should only have to do if a function was already being performed asynchronously on a background queue. And that doesn't tend to happen by itself.
Like Ashley says, UIKit methods are automatically called from the main queue – that's just how they are. It stands to reason, then, that some frameworks have methods that automatically call from a background queue.
I know that URLSession's dataTask's resume function automatically executes on a background queue (so that the app isn't slowed down by a web connection), which means the completion handler that executes afterward ALSO works on a background queue. That's why any UI updates that happen in the completion handler certainly require a call on the main queue.
let dataTask = URLSession.shared.dataTask(with: request) {
// begin completion handler
(data, response, error) in
guard error == nil else { print("error \(error.debugDescription)"); return }
guard let data = data else { print("no data"); return }
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: Any]] {
print("sending returned JSON to handler")
self.responseHandler.handleAPIResponse(jsonArray: json)
>>>> DispatchQueue.main.async { <<<<
self.tableView.reloadData()
}
}
} catch {
print("get tasks JSONSerialization error")
}
}
dataTask.resume()
My app gets stuck in the icon for about 4-5 seconds when entering foreground, if the networking is bad. In almost all the main loading pages I am fetching images from a server. I have nothing on applicationDidEnterBackground() nor applicationWillEnterForeground().
Here the code in the Main View controller where I have a collection view with images:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(homeReuseIdentifier, forIndexPath: indexPath)as! HomeViewCell
// Reload the collection view after insert item at indexPath
cvHome.reloadItemsAtIndexPaths([indexPath])
let entry = dataCell.infoCell[indexPath.row]
cell.backImageView.image = nil
let stringURL = "https://.....\(entry.filename)"
let image = UIImage(named: entry.filename)
cell.backImageView.image = image
if cell.backImageView.image == nil {
if let image = dataCell.cachedImage(stringURL) {
cell.backImageView.image = image
} else {
request = dataCell.getNetworkImage(stringURL) { image in
cell.backImageView.image = image
}
}
}
cell.titleLabel.text = entry.title
cell.authorLabel.text = entry.author
// Fix the collection view cell in size
cell.contentView.frame = cell.bounds
cell.contentView.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
// To edit the cell for deleting
cell.editing = editing
return cell
}
And this is the getNetworkImage function with Alamofire:
func getNetworkImage(urlString: String, completion: (UIImage -> Void)) -> (ImageRequest) {
let queue = decoder.queue.underlyingQueue
let request = Alamofire.request(.GET, urlString)
let imageRequest = ImageRequest(request: request)
imageRequest.request.response(
queue: queue,
responseSerializer: Request.imageResponseSerializer(),
completionHandler: { response in
guard let image = response.result.value else {
return
}
let decodeOperation = self.decodeImage(image) { image in
completion(image)
self.cacheImage(image, urlString: urlString)
}
imageRequest.decodeOperation = decodeOperation
}
)
return imageRequest
}
I'm not sure if you're networking code is bad since you haven't provided any.
My guess is, fetching the data is taking way too long, and it's blocking the UI. So currently, your app is synchronous. So tasks are executed one at a time. the reason your UI is blocked is because it has to wait for the networking to finish.
What you need to do is perform the fetching in the background, and always keep your UI on the main queue.
If you are calling Synchronous you have to wait till the task is finishing.We can start next task once we finish the current task.When you are fetching image from server make sure that whether you are using Synchronous or not.When you don't wait for a second go with Asynchronous.You don't need to wait while fetch the images from server.You can do other tasks when fetching the images from server.
For this GCD is good one to use.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Do the back ground process here
......Fetch the image here
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI here
.....Do your UI design or updation design part here
});
});
Global Queue
Asynchronous task will be performed here.
It does not wait.
Asynchronous function does not block the current thread of execution from proceeding on to the next function.
Main Queue
We must always access UI Kit classes on the main thread.
If you don't want to wait a seconds,go with GCD
i am trying to load set of images from the server and updating UI with returned images and displaying it by fade animation. it will be repeated forever.
Code snippet:
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated)
self.loadImages()
}
func loadImages(){
var urlImage:UIImage?
//self.array_images contains preloaded images (not UIImage) objects which i get from different api call.
if imageCount >= self.array_images!.count{
imageCount = 0
}
var img = self.array_images[imageCount]
var url = NSURL(string: img.url!)
var data = NSData(contentsOfURL: url!)
if (data != nil){
urlImage = UIImage(data: data!)
UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
println("Begin Animation")
self.imageView_Promotion.image = realImage
}, completion:{ finished in
println("Completed")
self.imageCount++
self.loadImages() //another issue: it calls the function more than couple of times
})
}
}else{
var image:UIImage = UIImage(named: "test_Image.jpg")!
imageView_Promotion.image = image
}
The above one hangs the UI. i have tried to call loadImages in dispatch_async and animation in dispatch_main queue but the issue still persist.
let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
self.loadImages()
}
dispatch_async(dispatch_get_main_queue(),{
//UIView animation
})
What is the proper way to handle this.
Thanks
Thread-safe problem
Basically, UIKit APIs are not thread-safe. It means UIKit APIs should be called only from the main thread (the main queue). But, actually, there are some exceptions. For instance, UIImage +data: is thread-safe API.
Your code includes some unsafe calls.
UIView.transitionWithView(self.imageView_Promotion, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
I don't think UIView.transitionWithView is thread-safe.
var image:UIImage = UIImage(named: "test_Image.jpg")!
UIImage +named: is not thread-safe, it uses a global cache or so on. According to UIImage init(named:) Discussion.
You can not assume that this method is thread safe.
Block the main thread
If the thread-safe problem was fixed, it would block the main thread.
Your code calls loadImages method, that downloads an image from the server or loads an image from the file system, from the completion block of UIView.transitionWithView. UIView.transitionWithView should be called from the main thread, so the completion block also will be called from the main thread. It means downloading or loading an image on the main thread. It blocks the main thread.
Example code
Thus, loadImages should be like the following.
Swift Playground code:
import UIKit
import XCPlayground
func loadImage(file:String) -> UIImage {
let path = NSBundle.mainBundle().pathForResource(file, ofType: "")
let data = NSData(contentsOfFile: path!)
let image = UIImage(data: data!)
return image!
}
var index = 0
let files = ["image0.png", "image1.png", "image2.png"]
let placementImage = loadImage(files[0])
let view = UIImageView(image:placementImage)
// loadImages function
func loadImages() {
let priority = DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_async(dispatch_get_global_queue(priority, 0)) {
/*
* This block will be invoked on the background thread
* Load an image on the background thread
* Don't use non-background-thread-safe APIs like UIImage +named:
*/
if index >= files.count {
index = 0
}
var file = files[index++]
let image = loadImage(file)
dispatch_async(dispatch_get_main_queue()) {
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
UIView.transitionWithView(view, duration: 1.2, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
view.image = image
}, completion:{ finished in
/*
* This block will be invoked on the main thread
* It is safe to call any UIKit APIs
*/
loadImages()
})
}
}
}
// call loadImages() from the main thread
XCPShowView("image", view)
loadImages()
XCPSetExecutionShouldContinueIndefinitely()
Your UI freezes as the comletion block for transitionWithView is called on the main thread (a.k.a. UI thread)- that's basically how you end up running "loadImages()" on the main thread. Then, when the load method is being called on the main thread, you create an instance of NSData and initialize it with contents of a URL - this is being done synchronously, therefore your UI is freezing. For what it's worth, just wrap the loadImages() call (the one which is inside the completion block) into a dispatch_async call to a background queue and the freeze should be gone.
P.S. I would recommend queuing calls of loadImages() instead of relying on the completion blocks of UI animations.
You have to separate loadImages & update View into 2 GCD thread. Something like that:
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated)
dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil {
() -> Void in
self.loadImages()
}))
}
/**
Call this function in a GCD queue
*/
func loadImages() {
... first, download image
... you have to add stop condition cause I see that self.loadImages is called recursively in 'completion' clause below
... then, update UIView if data != nil
if (data != nil) {
dispatch_async(dispatch_get_main_queue(),nil, {
() -> Void in
UIView.transitionWithView.... {
}, completion: { finished in
dispatch_async(dispatch_queue_create("com.company.queue.downloadImages", nil, {
() -> Void in
self.imageCount++
self.loadImages()
}))
})
})
}
}