Async issue in call using ObjectiveDropboxOfficial SDK - swift

I'm experiencing an issue with a function that should return an encrypted file from dropbox, but is instead returning the empty dictionary I initialized to receive the data.
I'm almost certain it's a race condition issue since an async call has to be made to the Dropbox API, but so far I have been unable to resolve the issue using GCD. Any help would be most appreciated:
func loadDropboxAccounts() {
let retreiveDataGroup = dispatch_group_create()
var dictionary = [String:String]()
dispatch_group_enter(retreiveDataGroup)
if DropboxClientsManager.authorizedClient() == nil {
DropboxClientsManager.authorizeFromController(UIApplication.sharedApplication(), controller: self, openURL: {(url: NSURL) -> Void in
UIApplication.sharedApplication().openURL(url)
}, browserAuth: true)
}
if let client = DropboxClientsManager.authorizedClient() {
client.filesRoutes.downloadData("/example/example.txt").response({(result: AnyObject?, routeError: AnyObject?, error: DBError?, fileContents: NSData) -> Void in
if (fileContents.length != 0) {
let cipherTextData = fileContents
let plainTextByteArray = CryptoHelper.accountDecrypt(cipherTextData, fileName: "vault")
let plainTextString = plainTextByteArray.reduce("") { $0 + String(UnicodeScalar($1)) }
let plainTextData = dataFromByteArray(plainTextByteArray)
do {
try dictionary = NSJSONSerialization.JSONObjectWithData(plainTextData, options: []) as! [String:String]
for (key, value) in dictionary {
let t = dictionary[key]
print(t)
}
} catch let error as NSError{
print("loadAccountInfo:", error)
}
} else {
print("\(routeError)\n\(error)\n")
}
}).progress({(bytesDownloaded: Int64, totalBytesDownloaded: Int64, totalBytesExpectedToDownload: Int64) -> Void in
print("\(bytesDownloaded)\n\(totalBytesDownloaded)\n\(totalBytesExpectedToDownload)\n")
})
}
dispatch_group_notify(retreiveDataGroup, dispatch_get_main_queue()) {
return dictionary
}
}
Just for reference, this the pod that I am using in the project:
https://github.com/dropbox/dropbox-sdk-obj-c

Related

How do I get data out of a firebase snapshot in Swift using a function in a struct?

I'm getting data from Cloud Firestore to populate a ListView. I've managed to get the data into an array, but when I return the array, it shows up empty.
//Edit
I've implemented a completion handler, works perfectly for 'Test Code', but when called in 'func industryPosts' and passed into 'myArray', it returns nil. While 'Test Code' returns data. I'm new to completion handlers, and Swift in general. Kindly let me know what I'm missing. Thanks.
//Edit
I was not able to return the values, but calling industryPosts where I needed to use it worked!
import Foundation
import SwiftUI
class IndustryData {
var _snapshotArray : Array<Any>?
func getSnapshotArray(collectionRef: String, completionHandler: #escaping (Array<Any>?, NSError?) -> ()){
if let snapArray = self._snapshotArray {
completionHandler(snapArray, nil)
} else {
var snapArray : Array<Any> = []
db.collection(collectionRef).getDocuments { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error - > \(String(describing: error))")
return
}
for document in snapshot.documents {
let item = Industry(avatar: document.get("avatar") as! String, name:document.documentID, tags: document.get("tags") as! String)
snapArray.append(item)
}
self._snapshotArray = snapArray
completionHandler(snapArray, error as NSError?)
}
}
}
}
Then calling the below function where needed
func getposts()-> [Industry] {
let data = IndustryData()
data.getSnapshotArray(collectionRef: "industry") { (snapshotArray, error) in
if snapshotArray != nil {
self.myArray = snapshotArray!
}
}
return myArray as! [Industry]
}
myArray returned Industry Array!

do not know how to get the result of completion

I am having trouble to use the result of a completion handler.
I am getting this error "Cannot convert value of type '()' to expected argument type"
struct SearchCollectionViewModel {
let name: String
let previewURL: String?
var image:UIImage?
let dataController = DataController()
}
extension SearchCollectionViewModel {
init(with result: Result) {
self.name = result.trackName
self.previewURL = result.previewURL
if let url = result.previewURL {
let imgData = preview(with: url, completion: { data -> Data? in
guard let data = data as? Data else { return nil }
return data
})
self.image = UIImage(data: imgData)
}
}
private func preview(with url: String, completion: #escaping (Data) -> Data?) {
dataController.download(with: url) { data, error in
if error == nil {
guard let imageData = data else { return }
DispatchQueue.main.async {
_ = completion(imageData)
}
}
}
}
}
A couple of observations:
You cannot “return” a value that is retrieved asynchronously via escaping closure.
The closure definition (Data) -> Data? says that the closure not only will be passed the Data retrieved for the image, but that the closure will, itself, return something back to preview. But it’s obviously not doing that (hence the need for _, as in _ = completion(...)). I’d suggest you change that to (Data?) -> Void (or use the Result<T, U> pattern).
I’d suggest renaming your Result type as there’s a well-known generic called Result<Success, Failure> for returning .success(Success) or .failure(Failure). This is a pattern that we’ve used for a while, but is formally introduced in Swift 5, too. See SE-0235.
Your codebase can have its own Result type, but it’s an invitation for confusion later down the road if and when you start adopting this Result<T, U> convention.
You really shouldn’t be initiating asynchronous process from init, but instead invoke a method to do that.
Personally, I’d move the conversion to UIImage into the DataController, e.g.
extension DataController {
func downloadImage(with url: URL, completion: #escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, error in
let image = data.flatMap { UIImage(data: $0) }
completion(image, error)
}
task.resume()
}
}
So, I might suggest you end up with something like:
class SearchCollectionViewModel {
let name: String
let previewURL: String?
let dataController = DataController()
var image: UIImage?
init(with result: Result) {
self.name = result.trackName
self.previewURL = result.previewURL
}
}
extension SearchCollectionViewModel {
func preview(with url: String, completion: #escaping (UIImage?) -> Void) {
guard let urlString = previewURL, let url = URL(string: urlString) else {
completion(nil)
return
}
dataController.downloadImage(with: url) { [weak self] image, error in
DispatchQueue.main.async {
self?.image = image
completion(image)
}
}
}
}

Is there a way to use my array of type Music, in another scope?

I'm attempting to print/dump and array of type Music outside of a function it's created in. I can successfully dump the musicItems array inside of the getMusicData function but when I set the musicItems array outside of the scope, it won't print anything. What am I doing wrong with the scope here? I have a feeling it's super simple but I just can't figure it out. Thanks in advance for taking the time to read this.
edit: It's giving me "0 elements" in the console when I attempt to dump the musicItems array in the ViewController class. Well, the function is in the same class as well so I guess I don't know what to call the first array. The parent array?
struct MusicResults: Decodable {
let results: [Music]?
}
struct Music: Decodable {
let trackName: String?
let collectionName: String?
let artworkUrl30: String?
}
class ViewController: UITableViewController, UISearchBarDelegate {
var musicItems: [Music] = []
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData()
dump(musicItems)
}
Here is the function.
func getMusicData() {
var musicItems: [Music] = []
guard let searchTerm = searchString else {return}
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
// print(results.trackName!)
musicItems.append(results)
}
//dump(musicItems)
self.musicItems = musicItems
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
Fixed Code
#IBAction func musicButton(_ sender: UIBarButtonItem) {
getMusicData {
music in
dump(music)
}
function:
func getMusicData(completionHandler: #escaping (_ music: [Music]) -> ()) {
...
let music = try JSONDecoder().decode(MusicResults.self, from: data)
for results in music.results! {
musicItems.append(results)
}
completionHandler(musicItems)
...
Your 'getMusicData' function is asynchronous which means that when it executes, it queues data task in a background queue and proceeds the execution and since there are no more institutions it simply returns control to its calling site - 'musicButton()' action, which in its turn executes the next instruction - prints the 'musicItems' array which might (and most likely, is) still not populated as the network call haven’t yet completed. One of the options that you have here is to pass a completion block to your 'getMusicData' function, that runs it after data task gets the results.
Another option is to use Property Observers
var musicItems: [Music] = [] {
didSet {
dump(self.musicItems)
/// This is where I would do the...
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
and then
func getMusicData() {
guard let searchTerm = searchString else { print("no search"); return }
let newString = searchTerm.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)
let jsonUrlString = "https://itunes.apple.com/search?media=music&term=\(newString)"
guard let url = URL(string: jsonUrlString) else { print("url error"); return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { print(err ?? "unknown"); return }
do {
let music = try JSONDecoder().decode(MusicResults.self, from: data)
if let results = music.results {
self.musicItems.append(contentsOf: results)
}
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}

Facebook login using RxSwift

I'm trying to implement the following RxSwift example:
Login in with facebook in my application -> retrieve the user information -> retrieve user's profile photo.
I have these three functions and they must be executed in this order: requestAccess() -> fetchUserInformation() -> fetchUserPhoto()
func requestAccess() -> Observable<(ACAccount)> {
return create { observer -> Disposable in
let accountStore = ACAccountStore()
let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
let dictionary: [NSObject : AnyObject] = [ACFacebookAppIdKey:"***APPID***", ACFacebookPermissionsKey:["public_profile", "email", "user_friends"]]
accountStore.requestAccessToAccountsWithType(accountType, options: dictionary) { granted, error in
if granted == false || error != nil {
sendError(observer, error ?? UnknownError)
} else {
let accounts = accountStore.accountsWithAccountType(accountType)
let account = accounts.last as! ACAccount
sendNext(observer, account)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserInformation(account: ACAccount) -> Observable<User> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/me")
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: nil)
request.account = account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
let user = User()
user.updateInformationWithJSON(result! as! JSONObject)
sendNext(observer, user)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserPhoto(user: User) -> Observable<AnyObject> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/***myid***/picture")
let params = ["redirect":"false", "height":"200", "width":"200"]
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: params)
request.account = SocialController.account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
sendNext(observer, result!)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
I already tried to implement this flow but it doesn't feel right. What is the best way to solve this problem?
requestAccess()
>- subscribeNext { account in
fetchUserInformation(account)
>- map { user in return UserViewModel(model: user) }
>- subscribeNext { viewModel in self.viewModel = viewModel }
}
Have you tried using flatMap?
It's an equivalent to then in the JavaScript bluebird or Q world. The difference between map and flatMap is that flatMap must return an Observable<T> which will then be unwrapped in the following block/closure.
requestAccess()
>- flatMap{ account in
return fetchUserInformation(account)
}
>- map { user in
return UserViewModel(model:user)
}
>- subscribeNext { viewModel in
self.viewModel = viewModel
}
Tidbit #1: Consider using unowned self when referencing self to avoid a retain cycle.
Tidbit #2: These two are pretty much the same thing.
flatMap { return just("hello") }
>- subscribeNext{ greeting in println(greeting) }
map { return "hello" }
>- subscribeNext{ greeting in println(greeting) }
private
func requestFacebookAccess(_ viewController: UIViewController) -> Observable<LoginManagerLoginResult?> {
return Observable.create { observer -> Disposable in
let loginManager = LoginManager()
loginManager.logIn(permissions: ["public_profile", "email"], from: viewController, handler: { result, error in
observer.onNext(result)
observer.onCompleted()
})
return Disposables.create()
}
}

Can't pass value? (CoreData and API-call)

I have some trouble with part of my Swift Application. I am trying to make two requests to a REST API (Spark's API) and save the requested variables as CoreData. However, I can not get the values passed propably from one function to the other, even though it's all within the same class.
So here is how it should work.
The function startTimer calls a series of functions every 60 seconds.
var timer: dispatch_source_t!
//Calls the getData function twice every 60 seconds. First to get the Posturevalue and then to get the Time value.
func startTimer() {
let queue = dispatch_queue_create("que", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC, 1 * NSEC_PER_SEC)
dispatch_source_set_event_handler(timer) {
self.getData("Posturevalue")
let Posturevalue:Int = self.GSValHolder
self.getData("Time")
let Time:Int = self.GSValHolder
let CoreID:String = self.GSCoreID
self.saveCoreData(Time, PostureVal: Posturevalue, CoreID: CoreID)
}
dispatch_resume(timer)
}
First call is to the getData function, which sends an handles the JSON response. I know this is working, cause I can print the variables and call the set funtion.
//Sends a request to the URL to receive JSON data.
func getData(Variblename:String)
{
let dataurl:String = "https://api.spark.io/v1/devices/"+DeviceID+"/"+Variblename+"/?access_token="+Accesstoken
if let url = NSURL(string: dataurl) {
let request = NSURLRequest(URL: url)
initialiseTaskForGettingData(request, element: "results")
}
}
//Handles the JSON data. Get the DeviceID and Variblevalue (Posture Value and Time value)
func initialiseTaskForGettingData(request: NSURLRequest, element:String)
{
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError
{
}
else
{
var parsingError: NSError?
let parsedResult: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &parsingError)
if let coreData = parsedResult as? NSDictionary {
if let VariableVal = (coreData as NSDictionary)["result"] as? Int {
if let coreInfo = coreData["coreInfo"] as? NSDictionary {
if let deviceID = (coreInfo as NSDictionary)["deviceID"] as? String {
println(deviceID)
println(VariableVal)
self.GSValHolder = VariableVal
}
}
}
}
}
}
task.resume()
}
Then do I try to get the variable and assign it to a new variable. This part keep causing me trouble and won't work.
//Get/Set functions to pass AccessToken and CoreID from CoreInfoView to the Model.
var AccessToken:String!
var CoreID:String!
var GSAccessToken:String {
set (newAccessToken) {
self.AccessToken = newAccessToken
}
get {
return AccessToken
}
}
var GSCoreID:String {
set (newCoreID) {
self.CoreID = newCoreID
}
get {
return CoreID
}
}
var ValHolder:Int!
var GSValHolder:Int {
set (newVal) {
self.ValHolder = newVal
}
get {
return ValHolder
}
}
If it worked, the startTimer function would do the same over again in order to get the other variable. Finally I wan't to call the saveCoreData function and save the variables.
func saveCoreData(Time:Int, PostureVal:Int, CoreID:String) {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Coredata")
var newCore = NSEntityDescription.insertNewObjectForEntityForName("Coredata", inManagedObjectContext: context) as! NSManagedObject
newCore.setValue(Time, forKey: "time")
newCore.setValue(PostureVal, forKey: "posturevalue")
context.save(nil)
println(newCore)
}
Hope it all makes sense and you are able to help me out.
Regards Mads R. Svendsen,
________________________________UPDATE_________________________________________
So I did a bit of reading and watched some tutorials on completionHandling and asynchronous, which helped a lot. My code now looks like this and works.
var timer: dispatch_source_t!
//Calls the getData function twice every 60 seconds. First to get the Posturevalue and then to get the Time value.
func startTimer() {
let queue = dispatch_queue_create("que", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC, 1 * NSEC_PER_SEC)
dispatch_source_set_event_handler(timer) {
var Time:Int!
var Posturevalue:Int!
var CoreID:String!
self.getData("Posturevalue", completionHandler: { (result) -> Void in
if (result == true) {
Posturevalue = self.GSValHolder
self.getData("Time", completionHandler: { (result) -> Void in
if (result == true) {
Time = self.GSValHolder
CoreID = self.GSCoreID
self.saveCoreData(Time, PostureVal: Posturevalue, CoreID: CoreID)
}
})
}
})
}
dispatch_resume(timer)
}
// Temporary constants to compose the URL. Has to be replaced with the values passed on from the CoreInfoView.
let DeviceID:String = "51ff72065082554944270887"
let Accesstoken:String = "09f54420e11539de00c13284f9fec31d3fba410d"
let session = NSURLSession.sharedSession()
//Sends a request to the URL to receive JSON data.
func getData(Variblename:String, completionHandler: (result:Bool!) -> Void)
{
let dataurl:String = "https://api.spark.io/v1/devices/"+DeviceID+"/"+Variblename+"/?access_token="+Accesstoken
if let url = NSURL(string: dataurl) {
let request = NSURLRequest(URL: url)
initialiseTaskForGettingData(request, element: "results", completionHandler: { (result) -> Void in
if (result == true) {
completionHandler(result: true)
}
else {
completionHandler(result: false)
}
})
}
}
//Handles the JSON data. Get the DeviceID and Variblevalue (Posture Value and Time value)
func initialiseTaskForGettingData(request: NSURLRequest, element:String, completionHandler: (result:Bool!) -> Void)
{
let task = session.dataTaskWithRequest(request) {data, response, downloadError in
if let error = downloadError
{
completionHandler(result: false)
}
else
{
var parsingError: NSError?
let parsedResult: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &parsingError)
if let coreData = parsedResult as? NSDictionary {
if let VariableVal = (coreData as NSDictionary)["result"] as? Int {
if let coreInfo = coreData["coreInfo"] as? NSDictionary {
if let deviceID = (coreInfo as NSDictionary)["deviceID"] as? String {
println(deviceID)
println(VariableVal)
self.GSValHolder = VariableVal
completionHandler(result: true)
}
}
}
}
}
}
task.resume()
}
However, I figured out that the dataTaskWithRequest can be written with a completionHandler, but I could not make it work. I was wondering if that might make the code a bit more simple?