Passing constants out of a function - Swift - ios

I hope this doesn't sound dumb but I'm trying to put:
let lowercasedQuery = query.lowercased()
let usersNew = users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
into the DispatchQueue function but obviously, since they are constants declared in the function, the function is out of scope for the return line.
func filteredUsers(_ query: String) -> [User] {
let delay = 3.3
DispatchQueue.main.asyncAfter(deadline: .now() + delay)
{
}
let lowercasedQuery = query.lowercased()
let usersNew = users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
return usersNew
}
Does anyone know how to solve this?
Thanks!

You need a closure... more info here. Instead of return, call the completion handler closure.
func filteredUsers(_ query: String, completion: #escaping (([User]) -> Void)) {
let delay = 3.3
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
let lowercasedQuery = query.lowercased()
let usersNew = self.users.filter({ $0.fullname.lowercased().contains(lowercasedQuery) || $0.username.contains(lowercasedQuery) })
completion(usersNew)
}
}
Usage:
viewModel.filteredUsers(searchText) { users in
print(users) /// get your users here!
}
If you are trying to return users inside another function, it won't work. You also need to a add a closure to that function:
/// another closure here
func mainFunction(_ query: String, completion: #escaping (([User]) -> Void)) {
viewModel.filteredUsers(query) { users in
completion(users) /// equivalent to `return users`
}
}
mainFunction("searchText") { users in
print(users) /// get your users here!
}
/// NOT `let users = mainFunction("searchText")`

Related

Swift function async [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool {
let userDefault = UserDefaults.standard
let tokenPinCode = userDefault.string(forKey: "tokenPinCode")
let mailData = self.emailField.text
let dataStruct = mailData!+"|"+tokenPinCode!
print("1")
self.checkToken(code: dataStruct) { (response) in
if(response[0] == "OK"){
print("2")
self.alertPasswordChange(text: "Podaj nowe hasło", code: dataStruct)
}else{
self.standardAlert(title: "Znaleziono błędy", message: "Podany kod jest błedny", ok: "Rozumiem")
self.werifyButton.isEnabled = true
}
}
print("3")
return false
}
Function returns: Print -> 1 -> 3 -> 2
How to get the effect to work out: Print -> 1 -> 2 -> 3
Make your function void and pass completion handler which can handle bool value.
func passcodeViewController(_ controller: Controller, code: String, #escaping handler: (Bool) -> ()) {
// Your logic
asyncRequest(...) {
response in
let result = ... // find whether code ok
handler(result)
}
}
You can call this like:
passcodeViewController(controller, code: "$&36_$") {
(isOk: Bool) in
print(3)
print("code is ok: \(isOk)")
}
You can implement this using semaphore. Semaphore provide the functionality to wait until your function completed. Look at following code.
let serialQueue = DispatchQueue(label: "co.random.queue")
func funcA() {
print("Print A")
let semaphore = DispatchSemaphore(value: 1)
serialQueue.async {
print("Print B")
semaphore.signal()
}
semaphore.wait()
print("Print C")
}
funcA()
The above code is execute with async function, I have implemented in playground and it print following output;
Print A
Print B
Print C
I remove semaphore then print as:
Print A
Print C
Print B
I hope this will work for you.
Make your function void and pass completion handler with signature

Remove the observer using the handle in Firebase in Swift

I have the following case. The root controller is UITabViewController. There is a ProfileViewController, in it I make an observer that users started to be friends (and then the screen functions change). ProfileViewController can be opened with 4 tabs out of 5, and so the current user can open the screen with the same user in four places. In previous versions, when ProfileViewController opened in one place, I deleted the observer in deinit and did the deletion just by ref.removeAllObservers(), now when the user case is such, I started using handle and delete observer in viewDidDisappear. I would like to demonstrate the code to find out whether it can be improved and whether I'm doing it right in this situation.
I call this function in viewWillAppear
fileprivate func firObserve(_ isObserve: Bool) {
guard let _user = user else { return }
FIRFriendsDatabaseManager.shared.observeSpecificUserFriendshipStart(observer: self, isObserve: isObserve, userID: _user.id, success: { [weak self] (friendModel) in
}) { (error) in
}
}
This is in the FIRFriendsDatabaseManager
fileprivate var observeSpecificUserFriendshipStartDict = [AnyHashable : UInt]()
func observeSpecificUserFriendshipStart(observer: Any, isObserve: Bool, userID: String, success: ((_ friendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
DispatchQueue.global(qos: .background).async {
let specificUserFriendRef = Database.database().reference().child(MainGateways.friends.description).child(currentUserID).child(SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: userID)
if !isObserve {
guard let observerHashable = observer as? AnyHashable else { return }
if let handle = self.observeSpecificUserFriendshipStartDict[observerHashable] {
self.observeSpecificUserFriendshipStartDict[observerHashable] = nil
specificUserFriendRef.removeObserver(withHandle: handle)
debugPrint("removed handle", handle)
}
return
}
var handle: UInt = 0
handle = specificUserFriendRef.observe(.childAdded, with: { (snapshot) in
if snapshot.value is NSNull {
return
}
guard let dict = snapshot.value as? [String : Any] else { return }
guard let friendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
if friendModel.friendID == userID {
success?(friendModel)
}
}, withCancel: { (error) in
fail?(error)
})
guard let observerHashable = observer as? AnyHashable else { return }
self.observeSpecificUserFriendshipStartDict[observerHashable] = handle
}
}
Concerning your implementation of maintaining a reference to each viewController, I would consider moving the logic to an extension of the viewController itself.
And if you'd like to avoid calling ref.removeAllObservers() like you were previously, and assuming that there is just one of these listeners per viewController. I'd make the listener ref a variable on the view controller.
This way everything is contained to just the viewController. It also is potentially a good candidate for creating a protocol if other types of viewControllers will be doing similar types of management of listeners.

How to return a bool to the outside function when you have nested functions in Swift?

So I have a function I have that has an inner function that connects to a Firebase database. the function returns a bool but I need the to return true or false based on whats inside the Firebase database. So basically I need to return true or false inside of the Firebase function the problem is it thinks that I am trying to return to the Firebase function when I am actually trying to return to the outside function a simple example is this
func someOtherFunc(name: String, lastname: String){
}
func someFunc(name: String, lastname: String) -> bool {
someOtherFunc(name: name, lastname: lastname) {
if name == "George" {
return true
} else {
return false // will not work because some other func does not return a value
}
}
}
here is the code that I need fixed which is a little more complicated than the above function because the inner Firebase function runs asynchronously(in the background) and so all the code inside the function needs to run synchronously to return the correct value
Here is my function
func takeAwayMoney(_ howMuch: String) -> Bool{
if let notMuch = Int(howMuch) {
let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let money = value?["money"] as? String ?? ""
//convert money to int
if let conMoney = Int(money) {
var conMoreMoney = conMoney
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
return false
} else {
conMoreMoney -= notMuch
let values = ["money": String(conMoreMoney)]
//update the users money
self.datRef.child("User").child(userID!).updateChildValues(values)
return true //doesn't work because of example above
}
}
// ...
}) { (error) in
print(error.localizedDescription)
}
}
}
This code does not compile because there is no return values to the main function.
I know the really hard way to fix this would be to initialize values at the top of the function that would be the money of the user then check it after dispatching it for a couple of seconds then you could return the values, but I know there must be another way because this way would cause a lot of problems.
The root cause is that takeAwayMoney is synchronous, but it uses observeSingleEvent, which is asynchronous.
The "right" way to solve this is to make takeAwayMoney return Void, but take a completion function that will give you the bool asynchronously, like this:
func takeAwayMoney(_ howMuch: String, completion: #escaping (Bool)->()) -> Void {
/// When you want to "return" the bool, call the completion. e.g.:
// ...
if conMoreMoney < notMuch {
print(" sorry you don't have enough money")
completion(false)
return // if you want to stop processing
}
// ...
}
If you cannot do this, then takeMoney needs to wait for the completion to finish and you use semaphores to have them communicate with each other. You do not just wait a couple of seconds. See this for an example:
Semaphores to run asynchronous method synchronously

Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value

I'm trying to update my project to Swift 3.0 but I have some difficulties.
I'm getting next error: "Escaping closures can only capture inout parameters explicitly by value".
The problem is inside this function:
fileprivate func collectAllAvailable(_ storage: inout [T], nextUrl: String, completion: #escaping CollectAllAvailableCompletion) {
if let client = self.client {
let _ : T? = client.collectionItems(nextUrl) {
(resultCollection, error) -> Void in
guard error == nil else {
completion(nil, error)
return
}
guard let resultCollection = resultCollection, let results = resultCollection.results else {
completion(nil, NSError.unhandledError(ResultCollection.self))
return
}
storage += results // Error: Escaping closures can only capture inout parameters explicitly by value
if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {
self.collectAllAvailable(&storage, nextUrl: nextUrlItr, completion: completion)
// Error: Escaping closures can only capture inout parameters explicitly by value
} else {
completion(storage, nil)
// Error: Escaping closures can only capture inout parameters explicitly by value
}
}
} else {
completion(nil, NSError.unhandledError(ResultCollection.self))
}
}
Can someone help me to fix that?
Using an inout parameter exclusively for an asynchronous task is an abuse of inout – as when calling the function, the caller's value that is passed into the inout parameter will not be changed.
This is because inout isn't a pass-by-reference, it's just a mutable shadow copy of the parameter that's written back to the caller when the function exits – and because an asynchronous function exits immediately, no changes will be written back.
You can see this in the following Swift 2 example, where an inout parameter is allowed to be captured by an escaping closure:
func foo(inout val: String, completion: (String) -> Void) {
dispatch_async(dispatch_get_main_queue()) {
val += "foo"
completion(val)
}
}
var str = "bar"
foo(&str) {
print($0) // barfoo
print(str) // bar
}
print(str) // bar
Because the closure that is passed to dispatch_async escapes the lifetime of the function foo, any changes it makes to val aren't written back to the caller's str – the change is only observable from being passed into the completion function.
In Swift 3, inout parameters are no longer allowed to be captured by #escaping closures, which eliminates the confusion of expecting a pass-by-reference. Instead you have to capture the parameter by copying it, by adding it to the closure's capture list:
func foo(val: inout String, completion: #escaping (String) -> Void) {
DispatchQueue.main.async {[val] in // copies val
var val = val // mutable copy of val
val += "foo"
completion(val)
}
// mutate val here, otherwise there's no point in it being inout
}
(Edit: Since posting this answer, inout parameters can now be compiled as a pass-by-reference, which can be seen by looking at the SIL or IR emitted. However you are unable to treat them as such due to the fact that there's no guarantee whatsoever that the caller's value will remain valid after the function call.)
However, in your case there's simply no need for an inout. You just need to append the resultant array from your request to the current array of results that you pass to each request.
For example:
fileprivate func collectAllAvailable(_ storage: [T], nextUrl: String, completion: #escaping CollectAllAvailableCompletion) {
if let client = self.client {
let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in
guard error == nil else {
completion(nil, error)
return
}
guard let resultCollection = resultCollection, let results = resultCollection.results else {
completion(nil, NSError.unhandledError(ResultCollection.self))
return
}
let storage = storage + results // copy storage, with results appended onto it.
if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {
self.collectAllAvailable(storage, nextUrl: nextUrlItr, completion: completion)
} else {
completion(storage, nil)
}
}
} else {
completion(nil, NSError.unhandledError(ResultCollection.self))
}
}
If you want to modify a variable passed by reference in an escaping closure, you can use KeyPath. Here is an example:
class MyClass {
var num = 1
func asyncIncrement(_ keyPath: WritableKeyPath<MyClass, Int>) {
DispatchQueue.main.async {
// Use weak to avoid retain cycle
[weak self] in
self?[keyPath: keyPath] += 1
}
}
}
You can see the full example here.
If you are sure that your variable will be available the whole time just use a true Pointer (same what inout actually does)
func foo(val: UnsafeMutablePointer<NSDictionary>, completion: #escaping (NSDictionary) -> Void) {
val.pointee = NSDictionary()
DispatchQueue.main.async {
completion(val.pointee)
}
}

Running one function after another completes

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.

Resources