Why is it in an infinite loop? - ios

I am trying to capture the data from a json page, and store it in a database. I can currently get the artist and title from the json page. I am filling a array in my program with the values captured from data to avoid repeats. When run the for loop and get the values and put them in the Parsearray, an infinite loop starts. It does not end, a it's always being called. I know this because i see "called" being printed in the console multiple times and it does not stop. How do I fix this?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
let query = PFQuery(className: "Pointer")
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
var objectIDs = objects as! [PFObject]
for i in 0...objectIDs.count-1{
self.Parsearray.append((objectIDs[i].valueForKey("title") as? String)!)
print(self.Parsearray)
print("called")
}
})
self.getSpotify()
}
func getSpotify(){
let searchTerm = "tgirish10"
var endpoint = NSURL(string: "<api URL>")
var data = NSData(contentsOfURL: endpoint!)
let task = NSURLSession.sharedSession().dataTaskWithURL(endpoint!) {(data, response, error) -> Void in
do {
if let jsonData = data,
let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as? NSDictionary,
let recent = dict["recenttracks"] as? NSDictionary,
let items = recent["track"] as? NSArray {
for item in items {
if let spotifytitle = item["name"] as? String {
print("title: \(spotifytitle)")
if let spotifyartist = item["artist"]!!["#text"] as? String{
print("artist: \(spotifyartist)")
let query = PFQuery(className: "Pointer")
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
var objectIDs = objects as! [PFObject]
for i in 0...objectIDs.count-1{
self.Parsearray.append((objectIDs[i].valueForKey("title") as? String)!)
print(self.Parsearray)
print("called")
}
if self.Parsearray.contains(spotifytitle){
print("already in db")
}else{
let objectPointer = PFObject(className: "Pointer")
objectPointer["title"] = spotifytitle
objectPointer["user"] = PFUser.currentUser()
objectPointer["artist"] = spotifyartist
objectPointer.saveInBackgroundWithBlock({ (success: Bool, error: NSError?) -> Void in
if(error != nil){
print(error)
}else{
print("saved")
}
})
}
})
}
}
}
}
} catch let jsonError as NSError {
print(jsonError)
}
}
task.resume()
}

Beyond the issues in the comments above, the main problem is that your code does the following:
get some data from Spotify
for each track, get all Pointer objects from Parse
for each of those objects, add title to parseArray (so after a while, the list will contain multiple copies of all titles)
then check if title of the track is in this list
So the loop does not seem to be infinite, but you are unnecessarily adding a lot of data.
Two options:
move the loading of the titles outside of the loop on tracks
or use a PFQuery which only returns Pointer objects which match, and just check if the returned list is empty or not

Related

Swift: Saving Firebase data to CoreData in async operation

I am attempting to pull data from Firebase and then save it to CoreData but am having trouble with the async operation. I have a custom function that returns [ConversationStruct] upon completion. I then do a forEach to save it to CoreData.
However, my current implementation saves the object multiple times, ie Firebase have 10 entries, but CoreData would somehow give me 40 over entries which most are repeated. I suspect the problem is in my completionHandler.
//At ViewDidLoad of my VC when I pull the conversations from Firebase
FirebaseClient.shared.getConversationsForCoreData(userUID) { (results, error) in
if let error = error {
print(error)
} else if let results = results {
print(results.count)
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
results.forEach({ (c) in
let conversation = Conversation(context: privateContext)
conversation.conversationStartTime = c.conversationStartTime
conversation.recipientID = c.recipientID
conversation.shoutoutID = c.shoutoutID
conversation.unreadMessagesCount = Int32(c.unreadMessagesCount!)
conversation.profileImage = c.profileImage
conversation.recipientUsername = c.recipientUsername
})
do {
try privateContext.save()
} catch let error {
print(error)
}
}
}
//At FirebaseClient
func getConversationsForCoreData(_ userUID: String, _ completionHandler: #escaping (_ conversations: [ConversationStruct]?, _ error: Error?) -> Void) {
var conversations = [ConversationStruct]()
ref.child("conversations").child(userUID).observeSingleEvent(of: .value) { (snapshot) in
for snap in snapshot.children {
let snapDatasnapshot = snap as! DataSnapshot
let snapValues = snapDatasnapshot.value as! [String: AnyObject]
let recipientUID = snapDatasnapshot.key
for (key, value) in snapValues {
//Some other logic
self.getUserInfo(recipientUID, { (results, error) in
if let error = error {
print(error.localizedDescription)
} else if let results = results {
let username = results["username"] as! String
let profileImageUrl = results["profileImageUrl"] as! String
URLClient.shared.getImageData(profileImageUrl, { (data, error) in
if let error = error {
print(error.localizedDescription)
} else if let imageData = data {
let convo = ConversationStruct(conversationStartTime: conversationStartTime, shoutoutID: shoutoutID, recipientID: shoutoutID, unreadMessagesCount: unreadMessagesCount, recipientUsername: username, profileImage: imageData)
conversations.append(convo)
}
completionHandler(conversations, nil)
})
}
})
}
}
}
}
struct ConversationStruct {
var conversationStartTime: Double
var shoutoutID: String
var recipientID: String
var unreadMessagesCount: Int?
var recipientUsername: String?
var profileImage: Data?
}
The print statement would print the count as and when the operation completes. This seems to tell me that privateContext is saving the entities when the results are consistently being downloaded which resulted in 40 over entries. Would anyone be able to point me out in the right direction how to resolve this?
Also, the implementation does not persist.

Why is the method being run at the end?

I am trying to recover all the animal objects based on certain parameters. First I need to retrieve their location from parse as well as the name, but since I am importing more than one and using geocoder, I am using strings, and not an array. So instead of appending the imported information into an array, I am mutating a variable. What I though would happen is the query would go through the first object then run the retrieveLocation method, then proceed to the next object imported from parse, but instead it imports everything then runs the method, so in the end I only get 1 object instead of how many are supposed to be imported.
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) in
if(error == nil){
for object in objects!{
if let addressobj = object["Add"] as? NSDictionary{
if let address = addressobj["address"] as? String{
self.addr = address
print("sdfadsf \(self.addr)")
}
}
if let name = object["Name"] as? String{
self.impname = name
print("rturrty \(self.impname)")
self.retrieveLocation()
}
}
}
}
func retrieveLocation(){
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(addr, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
let coordinates = PFGeoPoint(location: placemark.location)
if(whatever is true){
append the name and address into an array. This is the part where I just get repeats of the LATEST imported object.
}
}
})
}
This should work if you use a local variable and pass this local variable to an implementation of retrieveLocation that takes a string as a parameter retrieveLocation(address: String)
let query = PFQuery(className: "Animals")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) in
if(error == nil){
for object in objects!{
if let addressobj = object["Add"] as? NSDictionary{
if let address = addressobj["address"] as? String{
let newAddress = address
print("sdfadsf \(self.addr)")
}
}
if let name = object["Name"] as? String{
self.impname = name
print("rturrty \(self.impname)")
self.retrieveLocation(newAdress)
}
}
}
}
func retrieveLocation(address: String){
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
let coordinates = PFGeoPoint(location: placemark.location)
if(whatever is true){
append the name and address into an array. This is the part where I just get repeats of the LATEST imported object.
}
}
})
}
Problem seems to be that by the time self.addr is being used in the geocodeAddresString method, the for-loop has finished and thus overwritten all the previous values that were at one point individually held by self.addr. By using a local variable, it will be sure to use a unique value to geocodeAddressString each time it is executed

Conditional cast from PFFile toPFFile always succeed

I get a warming of "Conditional cast from PFFile toPFFile always succeed" which highlighted if let tempProductPicture = self.askProductImageArray[0] as? PFFile. What is the best way to solve it? Thanks
var askProductImageArray = [PFFile]()
override func viewDidLoad() {
let query = PFQuery(className: "products")
query.whereKey("title", equalTo: labelTitleText)
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error:NSError?) -> Void in
if error != nil {
}
for object in objects! {
self.askProductImageArray.append(object.objectForKey("detailsImage") as! PFFile)
self.askTable.reloadData()
}
if let tempProductPicture = self.askProductImageArray[0] as? PFFile {
tempProductPicture.getDataInBackgroundWithBlock { data, error in
if data == nil {
} else if error != nil {
} else {
self.productPicture.image = UIImage(data: data!)
}
}
}
}
Your self.askProductImageArray is an array of type PFFIle so there is no need to do as? PFFile because swift is strongly typed language and it will hold only PFFile so remover as? PFFile at the end.
In your case you don't need to do if let at all, you just need to check if item already exist.
you can do something like this
if self.askProductImageArray.count > 0 {
let tempProduct = self.askProductImageArray[0]
// and do rest
}

Swift Parse - Nested Calls Issue

In my app, I need to load user details in different view controllers. The details I load are not necessarily of the user that is currently signed in (which could be retrieved from currentUser, but sometimes are of other users depending on scenario.
So far I find myself whenever presented with the above need, I do two queries: First to load the details (e.g. name, phone, address, etc.) and second to load the profile image of that user.
So I end up with a nested call such as the below:
let query = PFQuery(className: "_User")
query.whereKey("appUsername", equalTo: self.friendObject.username!)
query.findObjectsInBackgroundWithBlock { (results, error) -> Void in
let data = results as [PFObject]!
if(error == nil)
{
self.friendObject.name = data[0]["name"] as? String
self.friendObject.profilePic = data[0]["ProfilePic"] as? UIImage
self.nameLabel.text = self.friendObject.name
self.friendObject.objectId = data[0].objectId! as String
self.getProfilePicture(self.friendObject.username!) { (result)->Void in
self.profilePicImageView.image = result
}
}else{
print("Error retrieving user details - try again")
}
}
and here is the definition of the getProfilePicture function:
func getProfilePicture(username: String, completion: (result: UIImage) -> Void)
{
var tempImage:UIImage? = UIImage(named: "sample-qr-code.png")!
let query: PFQuery = PFQuery(className: "_User")
query.whereKey("appUsername", equalTo: username)
query.findObjectsInBackgroundWithBlock {
(objects:[PFObject]?, error:NSError?) -> Void in
for object in objects! {
if(object["ProfilePic"] != nil)
{
let imageFiles = object["ProfilePic"] as! PFFile
imageFiles.getDataInBackgroundWithBlock({
(imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
tempImage = UIImage(data:imageData!)!
let temp2Image: UIImage = Toucan(image: tempImage!).resize(CGSize(width: 100, height: 150)).maskWithEllipse(borderWidth: 3, borderColor: UIColor.whiteColor()).image
completion(result: temp2Image)
}
})
}else{
let invalidImage = UIImage(named: "Contacts-100.png")
completion(result: invalidImage!)
}
}
}
}
The ProfilePic column in the User parse class is of File type. How can I optimize this so that I only do one call to load image and details (given that requests should be minimized as much as possible).
Thanks,
Make your imageView a PFImageView and then you can set the appropriate file of the PFImageView like self.friendObject.profilePic = data[0]["ProfilePic"] as? PFFile and then you can do self.profilePicImageView.file = self.friendObject.profilePic and then self.profilePicImageView.loadInBackground

How do I have the program run in the background?

I have a program which will record the currently playing song, but I want the app to run in the background, so when the user is in a different app or when the device is locked and my app is in the background and they go to the next song, the program will know. Currently I can take the current playing song and put it into parse when the app is open.
func applicationDidEnterBackground(application: UIApplication) {
print("entered background")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "getNowPlayingItem", name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification, object: nil)
musicPlayer.beginGeneratingPlaybackNotifications()
}
func getNowPlayingItem() {
if let nowPlaying = musicPlayer.nowPlayingItem {
let title = nowPlaying[MPMediaItemPropertyTitle] as? String
let artisttest = nowPlaying[MPMediaItemPropertyTitle]
if let artist = nowPlaying[MPMediaItemPropertyArtist] as? String{
let objectPointer = PFObject(className: "Pointer")
let object = PFObject(className: "MasterSongs")
// print("Artist: " + artist)
let query = PFQuery(className: "Pointer")
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
var objectIDs = objects as! [PFObject]
for i in 0...objectIDs.count-1{
self.Parsearray.append((objectIDs[i].valueForKey("title") as? String)!)
// print(self.Parsearray)
}
if self.Parsearray.contains(title!){
print("already in db")
}else{
objectPointer["title"] = title
objectPointer["user"] = PFUser.currentUser()
objectPointer["artist"] = artist
objectPointer.saveInBackground()
}
})
}else{
let object = PFObject(className: "MasterSongs")
object.setObject(title!, forKey: "title")
//object.setObject(artist!, forKey: "artist")
object.saveInBackground()
let objectPointer = PFObject(className: "Pointer")
let query = PFQuery(className: "Pointer")
query.findObjectsInBackgroundWithBlock({
(objects: [AnyObject]?, error: NSError?) -> Void in
var objectIDs = objects as! [PFObject]
for i in 0...objectIDs.count-1{
self.Parsearray.append((objectIDs[i].valueForKey("title") as? String)!)
// print(self.Parsearray)
}
if self.Parsearray.contains(title!){
print("already in db")
}else{
objectPointer["title"] = title
objectPointer["user"] = PFUser.currentUser()
objectPointer["artist"] = "No artist found :("
objectPointer.saveInBackground()
}
})
}
}
}
Apple support some of the background mode. If you are using any Apple framework for audio play then select your App target and go in Capabilites options and enable background mode and choose the appropriate options.
Check the screenshot.

Resources