I have this code:
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {
let url = URL(string: itemImageURL )
let data = try? Data(contentsOf: url!)
if data != nil {
DispatchQueue.main.async{
cell.advImage!.image = UIImage(data: data!)
}
}
}
I get this warning in Swift 3:
'default' was deprecated in iOS 8.0: Use qos attributes instead
on the first line.
Haven't found yet a solution. Has anybody?
try qos: DispatchQoS.QoSClass.default instead of priority: DispatchQueue.GlobalQueuePriority.default
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
let url = URL(string: itemImageURL )
let data = try? Data(contentsOf: url!)
if data != nil {
DispatchQueue.main.async{
cell.advImage!.image = UIImage(data: data!)
}
}
}
Instead of using priority parameter:
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {
// ...
}
use qos parameter that uses a different enum DispatchQoS.QoSClass.default but you can also use its enum value as just .default:
DispatchQueue.global(qos: .default).async {
// ...
}
Swift 3 has brought many changes on GCD(Grand Central Dispatch).
If you're creating a property using the Dispatch Framework and updating the UI with some animation within a function it might look something like this.
let queue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
// dispatch_after says that it will send this animation every nsec
queue.asyncAfter(deadline: when) {
DispatchQueue.main.async(execute: {
self.animate(withDuration: 0.5, animations: {
self.image.setWidth(35)
self.image.setHeight(35)
})
})
}
Below code is tested for Swift 3.0 on Xcode 8.2.1
DispatchQueue.global(qos: .background).async {
let img2 = Downloader.downloadImageWithURL(imageURLs[1])
// Background Thread
DispatchQueue.main.async {
// Run UI Updates
self.imageView2.image = img2
}
}
where property of QoS are :
background, utility, `default`, userInitiated, userInteractive and unspecified
Refer this apple document for more details.
Related
So the app works in test mode but as soon as I went to build for release I got this main thread issue.
UIImageView.image must be used from main thread only
According the the error I am not calling something on the main thread, yet the line it has thrown the thread error at is blank (see screenshot)
So I can only guess what they talking about is the code directly under that line?
code
#objc func nowplaying(){
let jsonURLString = "https://api.drn1.com.au/station/playing"
guard let feedurl = URL(string: jsonURLString) else { return }
URLSession.shared.dataTask(with: feedurl) { (data,response,err)
in
guard let data = data else { return }
do{
let nowplaying = try JSONDecoder().decode(Nowplayng.self, from: data)
nowplaying.data.forEach {
DispatchQueue.main.async {
self.artist.text = nowplaying.data.first?.track.artist
self.song.text = nowplaying.data.first?.track.title
}
print($0.track.title)
if var strUrl = nowplaying.data.first?.track.imageurl {
strUrl = strUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
self.imageurl.kf.setImage(with: URL(string: strUrl), placeholder: nil)
//MusicPlayer.shared.nowplaying(artist: $0.track.artist, song: $0.track.title, cover:strUrl)
MusicPlayer.shared.getArtBoard(artist: $0.track.artist, song: $0.track.title, cover:strUrl)
}
}
I can only guess it is because kingfisher wants a loading picture or something. But unclear?
It is your responsibility to call Kingfisher's UI-extension methods on UI thread.
Before:
self.imageurl.kf.setImage(with: URL(string: strUrl), placeholder: nil)
After:
DispatchQueue.main.async {
self.imageurl.kf.setImage(with: URL(string: strUrl), placeholder: nil)
}
I believe you could always run your code in main thread in your own processor, by using:
king fisher process image on download thread so that can cause this problem
public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image?
return DispatchQueue.main.sync {
let image = ... // Your code needs to be performed in UI thread
return image
}
}
I have two UICollectionView:
1 UICollectionView - Post
2 UICollectionView - Categories
Logic:
When user tap on any categories, that we send new api request and then update post collection view
In my UICollectionViewCell have two conditions.
1 Image Item
2 Video Item
Below my code:
class PopularPostsCell: UICollectionViewCell {
#IBOutlet weak var myImage : UIImageView!
func setupItems(childPostsItem: ChildPostsModel, index: Int) {
if childPostsItem.media_type != "video" {
myImage.image = nil
myImage.sd_setImage(with: URL(string: childPostsItem.media), placeholderImage: UIImage(named: ""))
} else {
DispatchQueue.global(qos: .background).async {
let locImage = Helpers.videoSnapshot(filePathLocal: childPostsItem.media)
DispatchQueue.main.async {
self.myImage.image = nil
self.myImage.image = locImage
}
}
}
}
override func prepareForReuse() {
super.prepareForReuse()
myImage.image = nil
}
}
if video, I need get image from Video URL below my code:
static func videoSnapshot(filePathLocal: String) -> UIImage? {
do {
let asset = AVURLAsset(url: URL(string: filePathLocal)!, options: nil)
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(0, 1), actualTime: nil)
let thumbnail = UIImage(cgImage: cgImage)
return thumbnail
} catch let error {
print("*** Error generating thumbnail: \(error.localizedDescription)")
return nil
}
}
Problem: If I choose several item in category very fast, my cell use random image. If I choose category slowly, that my cell use image ok.
My screen first: if I choose category fast
second: if I choose category slowly, in the screen everything is good.
If I don't use background async thread, all ok, but everything starts to slow down
Please, any help.
You are using a background thread and the main thread in a sequential manner while by definition they are parallel and don't wait for one another. Therefore in :
DispatchQueue.global(qos: .background).async {
let locImage = Helpers.videoSnapshot(filePathLocal: childPostsItem.media)
DispatchQueue.main.async {
self.myImage.image = nil
self.myImage.image = locImage
}
}
The two threads are completely asynchronous. You can't expect the background thread to be finished before the main thread starts.
There are ways to communicate thread events with signals and semaphores but in your case I think using asynchronous functions with completion handlers would be way more appropriate.
Check out how it works : https://stackoverflow.com/a/50531255/5922920
I have an array of up to 6 images. I use a loop to loop through all of the images, turn them into metadata, send the metadata to Storage and then when done I send the url strings to Firebase Database.
I'm using DispatchGroup to control the loop as the Url is changed to Data so I can send the data to Firebase Storage.
If this loop is happening in tabOne, if i go back and forth to tabTwo or tabThree, when the loop finishes and the alert appears, tabTwo is temporarily locked or tabThree gets temporarily locked for around 2-3 seconds. I cannot figure out where I'm going wrong?
I'm not sure if it makes a difference but I'm using a custom alert instead of the UIAlertController. It's just some UIViews and a button, it's nothing special so I didn't include the code.
var urls = [URL]()
picUUID = UUID().uuidString
dict = [String:Any]()
let myGroup = DispatchGroup()
var count = 0
for url in urls{
myGroup.enter() // enter group here
URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
guard let data = data, let _ = error else { return }
DispatchQueue.main.async{
self.sendDataToStorage("\(self.picUUID)_\(self.count).jpg", picData: data)
self.count += 1
}
}).resume()
// send dictionary data to firebase when loop is done
myGroup.notify(queue: .main) {
self.sendDataToFirebaseDatabase()
self.count = 0
}
}
func sendDataToStorage(_ picId: String, picData: Data?){
dict.updateValue(picId, forKey:"picId_\(count)")
let picRef = storageRoot.child("pics")
picRef.putData(picData!, metadata: nil, completion: { (metadata, error) in
if let picUrl = metadata?.downloadURL()?.absoluteString{
self.dict.updateValue(picUrl, forKey:"picUrl_\(count)")
self.myGroup.leave() // leave group here
}else{
self.myGroup.leave() // leave group if picUrl is nil
}
}
}
func sendDataToFirebaseDatabase(){
let ref = dbRoot.child("myRef")
ref.updateChildValues(dict, withCompletionBlock: { (error, ref) in
displaySuccessAlert()
}
}
I don't know much about Firebase, but you are dispatching your sendDataToFirebaseDatabase method to main queue which probably explains why your UI becomes unresponsive.
Dispatch sendDataToFirebaseDatabase to a background queue and only dispatch your displaySuccessAlert back to main queue.
I have below for loop to download files from server to my iPad 3 device.
Running hundred of files, I got error and app crash. The console shown me "Received memory warning. Same logic running on my iPad Air was passed. Anyone can adivse how to resolve the problem.
iPad 3 -> iOS 9.3.5
iPad Air -> iOS 10.3.3
func download() {
for (index, subJson): (String, JSON) in serverJson! {
for (_, subJson): (String, JSON) in subJson {
let filepath = subJson["path"].stringValue
let nUpdated = subJson["updated"].stringValue
if let oUpdated = localJson?[index].array?.filter({ $0["path"].string == filepath}).first?["updated"].stringValue {
if (oUpdated == nUpdated)
{
DispatchQueue.main.async { () -> Void in
self.progressView.progress = Float(self.count) / Float(self.totalCount)
}
count += 1
continue
}
}
var absPath = filepath
let strIdx = absPath.index(absPath.startIndex, offsetBy: 2)
if (absPath.hasPrefix("./"))
{
absPath = absPath.substring(from: strIdx)
}
let sourceUrl = URL(string: self.sourceUrl.appending(absPath))
do {
let fileData = try NSData(contentsOf: sourceUrl!, options: NSData.ReadingOptions())
try writeFileToLocal(absPath, fileData)
} catch {
print(error)
}
DispatchQueue.main.async { () -> Void in
self.progressView.progress = Float(self.count) / Float(self.totalCount)
}
//print("Path: \(absPath)")
//print("%: \(Float(count) / Float(totalCount))")
count += 1
}
}
do {
// Remove temp json file first if exists.
if fileManager.fileExists(atPath: oldManifestPath) {
try? fileManager.removeItem(atPath: oldManifestPath)
}
// Write temp json file to local.
try data?.write(to: oldManifestUrl)
self.defaults.set(hashes, forKey: "LastHash")
} catch {
print(error)
}
DispatchQueue.main.async {
self.progressView.isHidden = true
self.changeViewProperties(2)
}
}
}
private func writeFileToLocal(_ url:String, _ data:NSData) throws {
let url = URL(string: url)
let path = url?.deletingLastPathComponent().relativePath
let file = url?.lastPathComponent
let documentsPath = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let filePath = documentsPath.appendingPathComponent(path!)
var isDir: ObjCBool = false
if !FileManager.default.fileExists(atPath: (filePath?.path)!, isDirectory:&isDir) {
try FileManager.default.createDirectory(atPath: filePath!.path, withIntermediateDirectories: true, attributes: nil)
}
FileManager.default.changeCurrentDirectoryPath((filePath?.path)!)
try data.write(toFile: file!, options: .atomicWrite)
print("Update: \(filePath!), \(file!)")
FileManager.default.changeCurrentDirectoryPath(documentsPath.path!)
}
Then I call the function "download" in DispatchQueue.
DispatchQueue.global().async {
self.downloadFiles()
}
Memory waring is occuring as you are processing hundreds of Things on main thread just a Trick that you can try out if it helps you take that function in background queue instead of main Queue , you may get some ui warnings which you need to manage as updating ui in background thread
I do not know if it will help you or not but you an just give it a try for once
DispatchQueue.global(qos: .background).async
{
//BackGround Queue Update
self.downloadFiles()
DispatchQueue.main.async
{
//Main Queue Update With All UI
}
}
as background thread is always good to manage huge processing for example kingfisher library take data on main thread and return on main thread but process it in background thread
I have the following function that suppose to return [CIImage] for my purpose - displaying some metadata of photos in tableView.
func getCIImages() -> [CIImage] {
var images = [CIImage]()
let assets = PHAsset.fetchAssetsWithMediaType(.Image, options: nil)
for i in 0..<assets.count {
guard let asset = assets[i] as? PHAsset else {fatalError("Cannot cast as PHAsset")}
let semaphore = dispatch_semaphore_create(0)
asset.requestContentEditingInputWithOptions(nil) { contentEditingInput, _ in
//Get full image
guard let url = contentEditingInput?.fullSizeImageURL else {return}
guard let inputImage = CIImage(contentsOfURL: url) else {return}
images.append(inputImage)
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
return images
}
but it stucks in semaphore wait and didn't go further. I have walked through many tutorials but other variants of GCD don't works. I think it's because of simulator, I don't know, can't test on real device. Please help.
guards inside requestContentEditingInputWithOptions callback closure prevents signal sent to semaphore.
In such cases (when you need cleanup actions) it is good to use defer. In your case:
asset.requestContentEditingInputWithOptions(nil) { contentEditingInput, _ in
defer { dispatch_semaphore_signal(semaphore) }
//Get full image
guard let url = contentEditingInput?.fullSizeImageURL else {return}
guard let inputImage = CIImage(contentsOfURL: url) else {return}
images.append(inputImage)
}
UPDATE
Apart from cleanup bug there is another one. Completion closure of requestContentEditingInputWithOptions called on main thread. Which means that if you blocking main thread with semaphore: completion closure is blocked form executing as well. To fix blocked semaphore issue you need call getCIImages on a different thread than main.
Anyway making asynchronous things synchronous is wrong. You should think of different approach.