Given a piece of code like this:
func downloadImage() {
// if image is not downloaded yet, get it
// 1
if (post?.image.value == nil) {
// 2
post?.imageFile!.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if let data = data {
let image = UIImage(data: data, scale:1.0)!
// 3
self.post!.image.value = image
}
}
}
}
What is the difference if I turned post from ? to !
Also, how come when I try and do ! I get a:
EXC_BAD_INSTRUCTION, but when I use ? I do not get the error but the screen I am trying to load does not load till a refresh?
Ideas?
dispatch_async(dispatch_get_main_queue()) { \ update UI code }
Related
I realize it's a long shot, but any suggestions would be helpful as I have struggled with this for quite some time. Once in a while, I get a crash while saving multiple items to core data. Attached is a stack. To me it seems that context is being destroyed while it is still being used or something like that. Perhaps I am doing something wrong. Code and crash image is below:
func insert(dtos: [DataType.DTO], finished: #escaping (_ error:Error?) -> Void) {
mutex.lock {
let convertProgress = Progress(totalUnitCount: Int64(dtos.count))
self.progress.addChild(convertProgress, withPendingUnitCount: 48)
//print("save dtos \(dtos)")
self.progress.completedUnitCount = 2
var retError: Error?
dtos.chunked(into: 10).forEach { chunk in
var objects = chunk.toUnsavedCoreData(in: context, progress: convertProgress)
context.performAndWait {
do {
try context.save()
} catch {
print("*************************\nERROR " + error.localizedDescription)
print(error._userInfo ?? "")
retError = error
}
}
objects.removeAll()
}
self.progress.completedUnitCount = 100
NotificationCenter.default.post(name: NSNotification.Name(rawValue: NSStringFromClass(DataType.self)), object: dtos)
finished(retError)
}
}
I am saving hundreds of objects so I am chunking them by 10 otherwise memory goes through the roof. It starts all here (assets are of asset types so types are saved first), once saved assets are stored:
let assettype = AssetTypeDataLayer()
let assetDl = AssetDataLayer(with:assettype.context)
let assets = assetDl.loadObjectsFromDB().toDTOs()
let types = assettype.loadObjectsFromDB().toDTOs()
assettype.save(dtos: types) { (error) in
if let _ = error {
} else {
assetDl.save(dtos: assets) { (error) in
}
}
override func save(dtos: [AssetType.DTO], finished: #escaping (_ error:Error?) -> Void) {
self.insert(dtos: $0, finished: finished)
}
Fatal Exception: NSInvalidArgumentException
*** -_referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]!
So I have an array of images I've accessed from my xcassets for demonstration purposes. There are 150 images I'm trying to save to my parse server at one time using parse frameworks. Here is the code I have so far. The problem I have is my app cpu goes to 100% in the tests and drops to 0. Also the images aren't saving to parse. I was hoping someone could help me find an efficient way to save 150 images to parse.
var imageNameList: [String] {
var imageNameList2:[String] = [] //[NSMutableArray]()
for i in 0...149 {
let imageName = String(format: "pic_%03d", Int(i))
imageNameList2.append(imageName)
}
return imageNameList2
}
#IBAction func Continue(_ sender: Any) {
for imageName in imageNameList {
var objectForSave:PFObject = PFObject(className: "Clo")
let object:UIImage = UIImage(named: imageName)!
let tilesPF = imageNameList.map({ name in
let data = UIImagePNGRepresentation(object as! UIImage)!
let file = PFFile(data: data)
let tile = PFObject(className: "Tile")
tile["tile"] = file
})
objectForSave["tiles"] = tilesPF
objectForSave.saveInBackground(block: { responseObject, error in
//you'll want to save the object ID of the PFObject if you want to retrieve a specific image later
})
}
}
The trouble is that the tight for-loop launches all of those requests concurrently causing some part of the http stack to bottleneck.
Instead, run the requests sequentially as follows (in my best approximation of Swift)...
func doOne(imageName: String, completion: (success: Bool)->()) {
var objectForSave:PFObject = PFObject(className: "Clo")
let object:UIImage = UIImage(named: imageName)!
// ... OP code that forms the request
objectForSave.saveInBackground(block: { responseObject, error in
success(error == nil)
})
}
func doMany(imageNames: Array<String>, completion: (success: Bool)->()) {
if (imageNames.count == 0) return completion(YES)
let nextName = imageNames[0];
self.doOne(imageName:imageNames[0] completion: {(success: Bool) -> Void in
if (success) {
let remainingNames = imageNames[1..imageNames.count-1]
self.doMany(imageNames: remainingNames completion:completion)
} else {
completion(NO)
})
}
In English, just in case I goofed the Swift, the idea is to factor out a single request into it's own function with a completion handler. Build a second function that takes an array of arguments to the network request, and use that array like a to-do list: do the first item on the list, when it completes, call itself recursively to do the remaining items.
I am trying to run loadViews() after the pullData() completes and I am wondering what the best way of doing this is? I would like to set a 10 sec timeout on it as well so I can display a network error if possible. From what I have read, GCD looks like it is the way to accomplish this but I am confused on the implementation of it. Thanks for any help you can give!
//1
pullData()
//2
loadViews()
What you need is a completion handler with a completion block.
Its really simple to create one:
func firstTask(completion: (success: Bool) -> Void) {
// Do something
// Call completion, when finished, success or faliure
completion(success: true)
}
And use your completion block like this:
firstTask { (success) -> Void in
if success {
// do second task if success
secondTask()
}
}
You can achieve like this :-
func demo(completion: (success: Bool) -> Void) {
// code goes here
completion(success: true)
}
I had a similar situation where I had to init a view once the data is pulled from Parse server. I used the following:
func fetchQuestionBank(complete:()->()){
let userDefault = NSUserDefaults.standardUserDefaults()
let username = userDefault.valueForKey("user_email") as? String
var query = PFQuery(className:"QuestionBank")
query.whereKey("teacher", equalTo: username!)
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]?, error:NSError?) -> Void in
if error == nil {
if let objects = objects as? [PFObject] {
var questionTitle:String?
var options:NSArray?
for (index, object) in enumerate(objects) {
questionTitle = object["question_title"] as? String
options = object["options"] as? NSArray
var aQuestion = MultipleChoiceQuestion(questionTitle: questionTitle!, options: options!)
aQuestion.questionId = object.objectId!
InstantlyModel.sharedInstance.questionBank.append(aQuestion)
}
complete()
}
}else{
println(" Question Bank Error \(error) ")
}
}
}
And this is you call the method:
self.fetchQuestionBank({ () -> () in
//Once all the data pulled from server. Show Teacher View.
self.teacherViewController = TeacherViewController(nibName: "TeacherViewController", bundle: nil)
self.view.addSubview(self.teacherViewController!.view)
})
function1();
function2();
Use functions!! Once function1() function completed, function2() will execute.
I have a pretty elaborate problem and I think someone with extensive async knowledge may be able to help me.
I have a collectionView that is populated with "Picture" objects. These objects are created from a custom class and then again, these objects are populated with data fetched from Parse (from PFObject).
First, query Parse
func queryParseForPictures() {
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, err: NSError?) -> Void in
if err == nil {
print("Success!")
for object in objects! {
let picture = Picture(hashtag: "", views: 0, image: UIImage(named: "default")!)
picture.updatePictureWithParse(object)
self.pictures.insert(picture, atIndex: 0)
}
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.filtered = self.pictures
self.sortByViews()
self.collectionView.reloadData()
}
}
}
}
Now I also get a PFFile inside the PFObject, but seeing as turning that PFFile into NSData is also an async call (sync would block the whole thing..), I can't figure out how to load it properly. The function "picture.updatePictureWithParse(PFObject)" updates everything else except for the UIImage, because the other values are basic Strings etc. If I would also get the NSData from PFFile within this function, the "collectionView.reloadData()" would fire off before the pictures have been loaded and I will end up with a bunch of pictures without images. Unless I force reload after or whatever. So, I store the PFFile in the object for future use within the updatePictureWithParse. Here's the super simple function from inside the Picture class:
func updateViewsInParse() {
let query = PFQuery(className: Constants.ParsePictureClassName)
query.getObjectInBackgroundWithId(parseObjectID) { (object: PFObject?, err: NSError?) -> Void in
if err == nil {
if let object = object as PFObject? {
object.incrementKey("views")
object.saveInBackground()
}
} else {
print(err?.description)
}
}
}
To get the images in semi-decently I have implemented the loading of the images within the cellForItemAtIndexPath, but this is horrible. It's fine for the first 10 or whatever, but as I scroll down the view it lags a lot as it has to fetch the next cells from Parse. See my implementation below:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.PictureCellIdentifier, forIndexPath: indexPath) as! PictureCell
cell.picture = filtered[indexPath.item]
// see if image already loaded
if !cell.picture.loaded {
cell.loadImage()
}
cell.hashtagLabel.text = "#\(cell.picture.hashtag)"
cell.viewsLabel.text = "\(cell.picture.views) views"
cell.image.image = cell.picture.image
return cell
}
And the actual fetch is inside the cell:
func loadImage() {
if let imageFile = picture.imageData as PFFile? {
image.alpha = 0
imageFile.getDataInBackgroundWithBlock { [unowned self] (imageData: NSData?, err: NSError?) -> Void in
if err == nil {
self.picture.loaded = true
if let imageData = imageData {
let image = UIImage(data: imageData)
self.picture.image = image
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0.35) {
self.image.image = self.picture.image
self.image.alpha = 1
self.layoutIfNeeded()
}
}
}
}
}
}
}
I hope you get a feel of my problem. Having the image fetch inside the cell dequeue thing is pretty gross. Also, if these few snippets doesn't give the full picture, see this github link for the project:
https://github.com/tedcurrent/Anonimg
Thanks all!
/T
Probably a bit late but when loading PFImageView's from the database in a UICollectionView I found this method to be much more efficient, although I'm not entirely sure why. I hope it helps. Use in your cellForItemAtIndexPath in place of your cell.loadImage() function.
if let value = filtered[indexPath.row]["imageColumn"] as? PFFile {
if value.isDataAvailable {
cell.cellImage.file = value //assign the file to the imageView file property
cell.cellImage.loadInBackground() //loads and does the PFFile to PFImageView conversion for you
}
}
I've subclassed PFUser in my iOS app and I'm using this function to grab the profile picture. profilePicture is the #NSManaged PFFile and profilePictureImage is a UIImage.
This works great except for the fact that getData() and fetchIfNeeded() are potential long running operations on the main thread.
Can anyone think of a good way to implement this method so the scary parts run on a background thread?
Thanks!
func image() -> UIImage!
{
if !(self.profilePictureImage != nil)
{
if self.profilePicture != nil
{
self.fetchIfNeeded()
if let data = self.profilePicture!.getData() {
self.profilePictureImage = UIImage(data: data)
return self.profilePictureImage
}
}else {
return UIImage(named: "no_photo")!
}
}
return self.profilePictureImage
}
Change the method so that rather than returning an image it takes a closure which is called when the image is available and passes it as a parameter. This may be called immediately or after some delay if the image needs to be downloaded.
Just do it as you say, run the task in the background using: fetchIfNeededInBackgroundWithBlock. Also, your image function should look something like this:
func imageInBackgroundWithBlock(block: ((UIImage?, NSError?) -> Void)?) {
var image: UIImage?
self.fetchIfNeededInBackgroundWithBlock({ (user, error) -> Void in
if error == nil {
// load the picture here
image = ...
} else {
println(error!.userInfo)
image = UIImage(named: "no_photo")!
}
// return after fetching the user and the image
block?(image, error)
})
}