I found a lot of information concerning completionHandlers. However, I don't yet get how to handle it in this case. Maybe you can help me.
I'm trying to get a list of all Facebook friends and store it in an array.
getFacebookFriends
func getFacebookFriends() -> NSMutableArray
{
var retFriendIDs:NSMutableArray = []
FBRequestConnection.startForMyFriendsWithCompletionHandler {
(connection:FBRequestConnection!, result: AnyObject!, error:NSError!) -> Void in
if (error == nil){
var resultdict = result as! NSDictionary
self.data = resultdict.objectForKey("data") as! NSArray
var friendIDs:NSMutableArray = []
for (var i = 0; i < self.data.count; i++) {
let valueDict : NSDictionary = self.data[i] as! NSDictionary
let id = valueDict.objectForKey("id") as! String
friendIDs.addObject(id as String)
}
retFriendIDs = friendIDs
}
}
return retFriendIDs
}
As this is a completionHandler I understand that the return fires before completion of the block. But how do I achieve a return to use the list in this function for example?
findFacebookFriendsInBackend
func findFacebookFriendsInBackend() -> [AnyObject]{
println("findFacebookFriendsInBackend")
var retFriends:[AnyObject] = []
let fbFriends:NSMutableArray = getFacebookFriends()
var friendQuery = PFUser.query()
// look for friends in Parse
friendQuery!.whereKey("fbId", containedIn: fbFriends as [AnyObject])
friendQuery!.findObjectsInBackgroundWithBlock{
(friends, error) -> Void in
if error == nil {
retFriends = friends as [AnyObject]!
}
}
return retFriends
}
And then use it in a simple function like this:
iterateOverFriends
func iterateOverFriends(friends:[AnyObject]!){
for i in friends {
doSomething(i)
}
}
To call
iterateOverFriends(findFacebookFriendsInBackend())
Short answer: You don't. Not possible. With an async method, the result doesn't exist at the time when you return.
You have to adjust your thinking.
Rewrite this method:
func findFacebookFriendsInBackend() -> [AnyObject]{
As something like this instead:
func findFacebookFriendsInBackend(#completion:
(friendsArray: [AnyObject]) -> ())
In the new form it takes a block as a parameter. Write the function so that it invokes the block once it has built the array of friends.
The new call would look like this
findFacebookFriendsInBackend()
{
(friendsArray) -> () in
iterateOverFriends(friendsArray)
}
(When you have a function that takes a closure as the final parameter, you can call it with the closure outside the parenthesis.)
I wrote a long answer, including a working example project, in this thread.
Related
I'm trying to return a value for a function from inside of a nested function. I understand why it doesn't work, but I just need to know a workaround for my type of problem.
In my code, I'm checking whether or not a person has already been created inside of my Firebase Database. So inside of the usersRef function, you'll see that if the value return from firebase is "nil" then the user hasn't been created and vice versa.
When I say : return userBool it's giving me this error:
Unexpected non-void return value in void function
Been looking for a clean workaround, but the only thing I can think of is having to create this function over and over again for each of the various times I want to check it. Obviously, I'd rather not do that, haha, so if you could help me out by giving some suggestions on how to fix this and have a clean solution, let me know! :)
func checkIfUserExists(userID : String) -> Bool {
var userBool = Bool()
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with: {
snapshot in
if snapshot.value != nil {
userBool = true
return userBool
}
else {
userBool = false
return userBool
}
})
}
UPDATE :
Thanks to your suggestions, I added a completion, and it worked perfectly. :)
func checkIfUserExists(userID : String, completion: #escaping (_ success: Bool) -> ()){
var userBool = Bool()
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with:{
snapshot in
if snapshot.value != nil {
print("userBool!", userBool)
userBool = true
}
else {
print("userBool2!", userBool)
userBool = false
}
completion(userBool)
})
}
There is no way to return from the outer function from within the inner one. The only way I can think of is by using a completion handler, that you can then call from within the nested function like so:
func checkIfUserExists(userID : String, completion: #escaping (_ userExists: Bool) -> Void)
The problem is you're in a closure, that doesn't expect a return type.. How are you call this function?
Some ideas:
* Pass a closure to this function, which then gets called, instead of trying to return.
Keep a mapping of the results somewhere, update that from the closure and always check against that mapping.
Also, it looks like instead of
if snapshot.value != nil {
userBool = true
return userBool
}
else {
userBool = false
return userBool
}
You could just do this:
guard snapshot = snapshot else { return }
return snapshot.value
The only way to make it return what you want is possible if the observeSingleEvent(...) function executes the closure synchronously to its call.
If it's true, you can try the following:
func checkIfUserExists(userID : String) -> Bool {
var userBool = false
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with: {
userBool = $0.value != nil
})
return userBool
}
If the closure of observeSingleEvent(...) is called after a while, return a result of your method in a closure as Moritz suggested, not as a return value.
Another way is to create a delegate protocol and call it inside the closure
I am trying to only call a function only if I have retrieved a certain PFObjectfrom my Parse backend in a separate function. At the moment I am calling this second function after a set delay of 3.0 and it is working, but only if the first query function is called within 3.0, otherwise I have to pullToRefresh the tableView after the first function is eventually finished for it to populate with the Parse data. (I am using a PFQueryTableViewController by the way)
Here is my code at the moment (I am aware queryForTable is an override so is being called regardless, so is there a way to change this to a normal func?) :
override func viewDidLoad() {
// ideally want something like "if getX'IsComplete' then queryForTable() and self.loadObjects()"
retrieveFirstX()
let delay = 3.0 * Double(NSEC_PER_SEC) // retrieveFirstX must load within 3.0 for table to populate without needing to pullToRefresh
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
self.queryForTable()
self.loadObjects()
})
}
var X = PFObject(className: "X")
func retrieveFirstX() -> Bool {
let xQuery = PFQuery(className: "X")
xQuery.orderByDescending("createdAt")
xQuery.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
self.X = object!
}
}
return true
}
override func queryForTable() -> PFQuery {
let xRelation = X.relationForKey("xPosts") as PFRelation!
let relationQuery = xRelation.query()
let xPostsQuery = PFQuery(className: "Posts")
xPostsQuery.includeKey("postUser")
xPostsQuery.whereKey("objectId", matchesKey: "objectId", inQuery: relationQuery)
xPostsQuery.cachePolicy = .NetworkElseCache
xPostsQuery.orderByDescending("createdAt")
return xPostsQuery
}
Do I need to use completion handlers, and if so how do I do that as I have never used them before?
Thanks in advance!
A completion handler sounds right, something like:
override func viewDidLoad() {
let completionHandler = {
self.queryForTable()
self.loadObjects()
}
retrieveFirstX(completionHandler)
}
func retrieveFirstX(completion: ()->()) -> Bool {
let xQuery = PFQuery(className: "X")
xQuery.orderByDescending("createdAt")
xQuery.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error == nil {
self.X = object!
completion()
}
}
return true
}
I'm having an issue retrieving data from within an closure. I'm calling function called getWallImages which is supposed to return an array. I can print the contents of the array from within the closure, but outside of it the array is empty.
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]()
func getWallImages() -> [WallPost] {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
//this line prints [] ...empty array?
println(result)
return self.result
}
}
Question
How do I get values out of a closure?
That is because println(result) is executed BEFORE self.results = objects. The closure is executed asynchronously so it executes afterwards. Try making a function that uses results which can be called form the closure:
var result = [WallPost]()
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
self.useResults(self.result)
}
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
Another solution to your problem, so that you can return it from that function is to create your own closure:
var result = [WallPost]()
func getWallImages(completion: (wallPosts: [WallPost]?) -> ()) {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
completion(wallPosts: self.result)
} else {
completion(wallPosts: nil)
}
} else {
completion(wallPosts: nil)
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
What is happening is that the method is returning before the closure executes.
Fundamentally, you're running into a problem with they way you are managing asynchronous callbacks.
Asynchronous vs synchronous execution, what does it really mean?
You need to create a way of notifying your caller from within your closure. You can achieve this by: requiring your own closure as an input parameters; using a delegate pattern; using a notification.
https://codereview.stackexchange.com/questions/87016/swift-ios-call-back-functions
Each has their benefits/drawbacks, and it depends on your particular situation. The simplest way to get started with async data fetches, is to pass in your own closure. From there, you can jump to another pattern such as the delegate pattern if the need arises.
I think the latter of println(result) is called before because findObjectsInBackgroundWithBlock is executed on background as its name suggests.
So, you can confirm result in the following way,
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]() {
didSet {
println(result)
}
}
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
}
}
My exact question is here:
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058
I'm trying to understand how the proxy object which is returned from
mutableArrayValueForKey: works and I've hit a bit of a wall. I get
what the proxy is and why it exists. I have a test app which allows
me to do things to/with the collection it represents and it all works
fine. The problem is that when I try to implement some of the methods
mentioned in the developer docs under the "Key-Value Coding Accessor
Methods" section. There are some methods there which if implemented
in the hosting object (i.e. the original recipient of the
mutableArrayValueForKey: call) are to be called by the proxy when the
proxy is asked to do various things. The hitch is that in my test app
I can't get the -countOf<key> or -objectIn<key>AtIndex methods to be
invoked.
In reading through the docs, it seems that a number of methods need
to be implemented before any of these (dare I call them 'proxy
methods'?) are called. I implemented a whole slew of them - including
a number that shouldn't need to be - and ended up with this set: (The
test app is based on a Playlist->Songs->Song model where "songs" is
the NSMutableArray which lives in the Playlist class in which I'm
interested in getting a count of its member songs.)
- (unsigned int)countOfSongs;
- (Song *)objectInSongsAtIndex:(unsigned int)index;
- (NSArray *)songsAtIndexes:(NSIndexSet *)indexes;
- (void)getSongs:(Song **)buffer range:(NSRange)inRange;
- (void)insertObject:(Song *)newSong inSongsAtIndex:(unsigned int)idx;
- (void)removeObjectFromSongsAtIndex:(unsigned int)idx;
Even simple test code like this fails:
Playlist *myPlaylist = [[[Playlist alloc] init] autorelease];
id arrayProxy = [myPlaylist mutableArrayValueForKey:#"songs"];
[arrayProxy insertObject:[[[Song alloc] initWithName:#"test" andLength:
10] autorelease] atIndex:0];
unsigned int theCount = [arrayProxy count];
I've got all the properties defined, all the methods written, etc.
Yet when I call [arrayProxy count] my countOfSongs method in the
Playlist class isn't touched. The right answer is returned, but it's
apparently coming from the runtime going to the array directly and
getting the answer via NSArray's count method.
Oddly enough, when I do the insertObject call in line #3 the
insertObject:inSongsAtIndex: method IS called... so some of this stuff
works as I believe it is supposed to. Unfortunately it's the other
stuff that's driving me nuts. I've been working on this one for a
couple of days now and have tried everything I could come up with -
including some really silly, paranoid stuff - and have made no progress.
Can anybody help me with a suggestion as to what I might be doing
wrong or what I'm missing?
Thanks!
And the answer there--although it seemed to help the op--does nothing to shed any light on the problem for me.
Here is my playground code:
import Cocoa
"hello"
class Song {
dynamic var title = "Hello"
}
"hello"
class PlayList: NSObject {
/*dynamic*/ var songs = NSMutableArray()
//private var theSongs = NSMutableArray()
//var countOfSongs: Int = 100
func countOfSongs() -> Int {
println("count of songs")
return 100
//return theSongs.count + 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return songs[i]
//return theSongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
songs[index] = song as! Song
//theSongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
songs.removeObjectAtIndex(index)
//theSongs.removeObjectAtIndex(index)
}
}
"hello"
var playlist = PlayList()
"hello"
let arrayProxy = playlist.mutableArrayValueForKey("songs")
"hello"
arrayProxy.addObject(Song()) //successfully calls proxy method => outputs "insert"
arrayProxy.removeObjectAtIndex(0) //successfully calls proxy method => outputs "remove"
arrayProxy.count //=> 0 ???
What changes do I need to make to my code so that the countOfSongs property or method is called when I write:
arrayProxy.count
From the referenced article
http://www.cocoabuilder.com/archive/cocoa/236041-kvc-array-proxy-objects.html#236058:
The documentation about accessor search order shows that it will
prefer a method named -<key> over the corresponding -countOf<Key> and
- objectIn<Key>AtIndex: methods.
which means that
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
accesses the "songs" property of Playlist directly, if there is such
a property.
If you rename the property then it works as you expected:
class Song {
dynamic var title = "Hello"
}
class PlayList: NSObject {
var thesongs = NSMutableArray()
func countOfSongs() -> Int {
println("count of songs")
return 100
}
func objectInSongsAtIndex(i: Int) -> AnyObject? {
println("getter")
return thesongs[i]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("insert")
thesongs[index] = song as! Song
}
func removeObjectFromSongsAtIndex(index:Int) {
println("remove")
thesongs.removeObjectAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
let cnt = arrayProxy.count
println(cnt)
Output:
count of songs
100
For future searchers, here is a full example exercising all the methods in an OSX>Application>Command Line Tool (I checked Swift for the language):
import Foundation
class Song {
var title = "Hello"
}
class PlayList: NSObject {
private var array: [Song] = []
func countOfSongs() -> Int {
println("countOfSongs() called")
return array.count
}
/*
//This also works:
var countOfSongs: Int {
println("countOfSongs property was accessed")
return array.count
}
*/
func objectInSongsAtIndex(index: Int) -> AnyObject? {
println("getter called")
return array[index]
}
func insertObject(song:AnyObject, inSongsAtIndex index:Int) {
println("inserting at index \(index)")
array.insert(song as! Song, atIndex: index)
}
func removeObjectFromSongsAtIndex(index:Int) {
println("removing at index \(index)")
array.removeAtIndex(index)
}
}
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.objectAtIndex(0)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
var playlist = PlayList()
let arrayProxy = playlist.mutableArrayValueForKey("songs")
arrayProxy.addObject(Song())
println(arrayProxy.count)
arrayProxy.removeObjectAtIndex(0)
println(arrayProxy.count)
--output:--
countOfSongs() called
inserting at index 0
countOfSongs() called
1
getter called
removing at index 0
countOfSongs() called
0
No more playgrounds for me!
In my app I keep receiving, 'RLMException', reason: 'RLMArray is no longer valid' while attempting to delete an RLMObject that contains a one-to-many relationship with another RLMObject. For example: 'Task' is the RLMObject which contains an RLMArray 'records' with the type 'Record' RLMObjects within it. The code throwing the error is as follows:
public class func deleteTask(#taskName: String, retainRecords: Bool) {
let realm = Database.getRealm()
let currentTask = (Task.objectsWhere("name = '\(taskName)'").objectAtIndex(0) as Task)
let loopCount:UInt = currentTask.records.count
if (retainRecords) {
for var i:UInt = 0; i < loopCount; ++i {
Database.moveRecord(record: (currentTask.records.objectAtIndex(0) as Record), newTask: "Taskless Records")
}
} else {
for var i:UInt = 0; i < loopCount; ++i {
Database.deleteRecord(record: currentTask.records.objectAtIndex(0) as Record)
}
}
realm.beginWriteTransaction()
realm.deleteObject(currentTask)
realm.commitWriteTransaction()
}
The program throws the exception on the line,
realm.commitWriteTransaction()
And the loops are moving or deleting all of the child database objects under the Task before attempting to delete the task. Both moving and deleting the records works properly. Also one last important note, if the task doesn't contain records under it then there is no issue deleting it and no exception thrown.
I appreciate anyone's help, I've been banging my head on a brick wall for this issue.
--New Changes based on the reply from yoshyosh--
I have since modified my code to the following. I still receive the same error.
public class func deleteTask(#taskName: String, retainRecords: Bool) {
let realm = Database.getRealm()
let currentTask = (Task.objectsWhere("name = '\(taskName)'").firstObject() as Task)
let currentRecords = currentTask.records
let parent = currentTask.parent
if (retainRecords) {
let loopCount:UInt = currentTask.records.count
for var i:UInt = 0; i < loopCount; ++i {
Database.moveRecord(record: (currentTask.records.firstObject() as Record), newTaskName: "Taskless Records")
}
} else {
realm.transactionWithBlock { () -> Void in
realm.deleteObjects(currentRecords)
}
}
realm.transactionWithBlock { () -> Void in
realm.deleteObject(currentTask)
}
}
public class func moveRecord(#record: Record, newTaskName: String) {
let realm = Database.getRealm()
let oldTask = record.parent
let oldIndex = oldTask.records.indexOfObject(record)
let newTask = (Task.objectsWhere("name = '\(newTaskName)'")).firstObject() as Task
realm.transactionWithBlock { () -> Void in
oldTask.records.removeObjectAtIndex(oldIndex)
newTask.records.addObject(record)
}
}
public class func addTask(#name: String, memo: String, time: Double, categoryName: String) {
let realm = Database.getRealm()
let parentCategory = (Category.objectsWhere("name = '\(categoryName)'")).firstObject() as Category
realm.transactionWithBlock { () -> Void in
let newTask = Task()
newTask.parent = parentCategory
newTask.name = name
newTask.memo = memo
newTask.timeRemaining = time
parentCategory.tasks.addObject(newTask)
}
}
public class func addRecord(#taskName: String, note: String, timeSpent: Double, date: NSDate) {
let realm = Database.getRealm();
let parentTask = Task.objectsWhere("name = '\(taskName)'").firstObject() as Task
realm.transactionWithBlock { () -> Void in
let newRecord = Record()
newRecord.parent = parentTask
newRecord.note = note
newRecord.timeSpent = timeSpent
newRecord.date = date
parentTask.records.addObject(newRecord)
}
}
If needed I can also add the code used to add new Records and new Tasks to the Database. Yoshyosh I appreciate your comment as it did help me clean up my code, unfortunately the error is still being thrown when deleting a Task.
One strange thing I've notice while testing the app, is that when I try deleting the Task it crashes, but once I open the app again the Task and all Records are deleted.
-- Solution --
I found that I was looking in the completely wrong spot for the error. it was caused by a TableView View Controller that populated with records from a task that was passed into it. It used an RLMArray to retrieve the recordList from the task and used a notificationToken to reload tableView data.
var currentTask:Task!
var recordList:RLMArray!
var notificationToken: RLMNotificationToken?
override func viewDidLoad() {
super.viewDidLoad()
recordList = currentTask.records
notificationToken = RLMRealm.defaultRealm().addNotificationBlock { note, realm in
self.tableView.reloadData()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Int(self.recordList.count)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return Factory.prepareRecordCell(tableView: tableView, recordList: self.recordList, indexPath: indexPath)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
When I would delete a task from a separate view it would try to reload data using the 'recordsList' RLMArray of a task that was deleted.
After removing the notificationToken and manually reloading tableView data inside the viewWillAppear() function deleting tasks works flawlessly.
I'm not sure about your particular case, you would need to show more of your code where deleteRecord is happening. I've tried to reproduce your case as best as possible but everything has deleted fine using the code below:
let currentTask = Task.allObjects().firstObject() as Task
let currentRecords = currentTask.records
let loopCount = currentRecords.count
for var i:UInt = 0; i < loopCount; i++ {
realm.transactionWithBlock({ () -> Void in
var recordToDelete = currentTask.records.objectAtIndex(0) as Record
realm.deleteObject(recordToDelete)
})
}
realm.transactionWithBlock { () -> Void in
realm.deleteObject(currentTask)
}
I think an easier way to delete all relevant objects is like this:
let currentTask = Task.allObjects().firstObject() as Task
let currentRecords = currentTask.records
// Delete Task and records that are connected to it
realm.transactionWithBlock { () -> Void in
realm.deleteObjects(currentRecords)
realm.deleteObject(currentTask)
}
Here's a simple way to update your task with the other records. This code is missing the part where you update the parent of these records though
let currentTask = Task.objectsWhere("name = %#", "First task").firstObject() as Task
let currentRecords = currentTask.records
let newFetchedTask = Task.objectsWhere("name = %#", "New Task").firstObject() as Task
realm.transactionWithBlock { () -> Void in
newFetchedTask.records.addObjects(currentRecords)
currentTask.records.removeAllObjects()
}
Feel free to post more code to debug your particular case