I am trying to access whatever record was returned from my QBRequest and send it to a separate function (in this case, I'm trying to set whatever I got from the request, convert its contents to strings, and then display in cells). However, I am unable to do this by direct referencing outside of the QBRequest nor setting the object to outside variable.
var posts = [QBCOCustomObject]()
func queryForPost(city: String) -> [QBCOCustomObject] {
let requestParameters = NSMutableDictionary();
[requestParameters.setObject(city, forKey: "Location")];
QBRequest.objectsWithClassName("UserFeed", extendedRequest: requestParameters, successBlock: { (response: QBResponse, record, page) in
print(record![0].fields!.allValues[0])
print(record![1].fields!.allValues[0])
//the two above prints return the desired values
self.posts = record!
}, errorBlock: {(response: QBResponse) in
// Handle error here
NSLog("error QBRequest - objectsWithClassName")
})
print(self.posts.count, "POSTS.COUNT") //can't access posts or record - this returns 0, and THEN returns whatever print functions are inside the QBRequest.
print(self.posts, "POSTS")
return self.posts
}
"print(self.posts.count, "POSTS.COUNT")" returns 0, and THEN returns whatever print functions are inside the QBRequest. Seems as if the QBRequest is performing after going through the whole function. Very confused and not sure how to fix this...
Been struggling with this for a while now :( If anybody could provide some guidance I would very much appreciate it.
Thank you in advance!
Lance
Related
I am currently using Moya to structure my networking calls. Per their docs, I have configured it as the following:
enum SomeAPIService {
case endPoint1(with: Object)
case endPoint2(duration: Int)
}
When calling an endpoint (in this case, endPoint1), I do the following:
let provider = MoyaProvider<SomeAPIService>()
provider.request(.endPoint1(object: object)) { (result) in
switch result {
case let .success(moyaResponse):
finished(Result.success(value: moyaResponse.value))
case let .failure(error):
let backendError = BackendError(localizedTitle: "", localizedDescription: "Some error", code: moyaResponse.statusCode)
finished(Result.failure(error: backendError))
}
})
My goal is, upon the user performing an action, cancel all the networking requests that's happening.
Accordingly, Moya does allow one to cancel requests from the discussion here. From the most upvoted comment, someone mentioned let request_1 = MoyaRequestXXXXX and then ruest_1.cancel()
My problem is:
How would I keep pointer to the requests?
provider doesn't have a cancel() function - so how should I be calling it?
Any help is much appreciated.
Edit:
Per the helpful suggestion about using [Cancellable], I did the following:
(1) In my app's singleton instance called Operator, I added var requests = [Cancellable]()
(2) Every API call is added to the requests array as a Cancellable, like so:
let provider = MoyaProvider<SomeAPIService>()
Operator.shared.requests.append(provider as! Cancellable) //causing error
provider.request(.endPoint1(object: object)) { (result) in
//rest of the block omitted
I think I am not getting the syntax correct, and am adding the provider and not the request. However, since the request is itself a block, where would be the place to add the request?
The request method returns a Cancellable. From the documentation we can read:
The request() method returns a Cancellable, which has only one public function, cancel(), which you can use to cancel the request.
So according to this, I made a simple test and call:
var requests: [Cancellable] = []
#objc func doRequests() {
for i in 1...20 {
let request = provider.request(MyApi.someMethod) {
result in
print(result)
}
requests.append(request)
}
requests.forEach { cancellable in cancellable.cancel() } // here I go through the array and cancell each request.
requests.removeAll()
}
I set up a proxy using Charles and it seems to be working as expected. No request was sent - each request was cancelled.
So, the answer to your questions is:
You can keep it in [Cancellable] array.
Go through the array and cancel each request that you want to cancel.
EDIT
The main problem is that you adding the provider to the array and you try to map provider as Cancellable, so that cause the error.
You should add reqest to the array. Below you can see the implementation.
let provider = MoyaProvider<SomeAPIService>()
let request = provider.request(.endPoint1(object: object)) { // block body }
Operator.shared.requests.append(request)
//Then you can cancell your all requests.
I would just cancel the current provider session + tasks:
provider.manager.session.invalidateAndCancel()
Could anyone tell me why my startingPoints array is still at 0 elements? I know that I am getting objects returned during the query, because that print statement prints out each query result, however it seems like those objects are not getting appended to my local array. I've included the code snippet below...
func buildStartSpots() -> Void {
let queryStartingPoints = PFQuery(className: "CarpoolSpots")
queryStartingPoints.whereKey("spotCityIndex", equalTo: self.startingCity)
queryStartingPoints.findObjectsInBackgroundWithBlock{(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
print("starting point: \(object)")
self.startingPoints.append(object)
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
print("starting points")
dump(self.startingPoints)
}
While I have no experience in Parse, the block is asynchronously executed and likely non-blocking as dictated by the method name of the API call. Therefore, it is not guaranteed that the data would be available at the time you call dump, since the background thread might still be doing its work.
The only place that the data is guaranteed to be available at is the completion block you supplied to the API call. So you might need some ways to notify changes to others, e.g. post an NSNotification or use event stream constructs from third party libraries (e.g. ReactiveCocoa, RxSwift).
When you try to access the array, you need to use it within the closure:
func buildStartSpots() -> Void {
let queryStartingPoints = PFQuery(className: "CarpoolSpots")
queryStartingPoints.whereKey("spotCityIndex", equalTo: self.startingCity)
queryStartingPoints.findObjectsInBackgroundWithBlock{(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
print("starting point: \(object)")
**self.startingPoints.append(object)**
}
//use it here
startingPoints xxx
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
print("starting points")
dump(self.startingPoints)
}
I am able to get the application functioning as intended and will close this answer out.
It seems as though that the startingPoints array is not empty, and the values I need can be accessed from a different function within that same class.
The code snippet I am using to access my locally stored query results array is here:
for object in self.startingPoints {
let startingLat = object["spotLatitude"] as! Double
let startingLong = object["spotLongitude"] as! Double
let carpoolSpotAnnotation = CarpoolSpot(name: object.valueForKey("spotTitle") as! String, subTitle: object.valueForKey("spotSubtitle") as! String, coordinate: CLLocationCoordinate2D(latitude: startingLat, longitude: startingLong))
self.mapView.addAnnotation(carpoolSpotAnnotation)
The code snippet above is located within my didUpdateLocations implementation of the locationManager function, and with this code, I am able to access the query results I need.
I'm saving objects to core data from a JSON, which I get using a for loop (let's say I called this setup function.
Because the user might stop this loop, the objects saved in core data will be partial. The user can restart this setup function, restarting the parsing and the procedure to save object to core data.
Now, I'm getting duplicated objects in core data if I restart the setup().
The object has an attribute which is id.
I've thought I could fetch first objects that could eventually already exist in core data, save them to an array (a custom type one), and test for each new object to add to core data if already exist one with the same id.
The code used is the following:
if !existingCards.isEmpty {
for existingCard in existingCards {
if id == existingCard.id {
moc.deleteObject(existingCard)
println("DELETED \(existingCard.name)")
}
}
}
...
// "existingCards is the array of object fetched previously.
// Code to save the object to core data.
Actually, the app return
EXC_BAD_ACCESS(code=1, address Ox0)
Is there an easier way to achieve my purpose or what should I fix to make my code work? I'm quite new to swift and I can't figure other solution.
The main purpose is to delete duplicated core data, BTW.
Swift 4 code to delete duplicate object:
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Card")
var resultsArr:[Card] = []
do {
resultsArr = try (mainManagedObjectContext!.fetch(fetchRequest) as! [Card])
} catch {
let fetchError = error as NSError
print(fetchError)
}
if resultsArr.count > 0 {
for x in resultsArr {
if x.id == id {
print("already exist")
mainManagedObjectContext.deleteObject(x)
}
}
}
At the end, I managed to make it work.
I had to rewrite my code, because I realized moc.deleteObject() works with a fetch before, which in my previous code wasn't in the same function, but it was in viewDidLoad().
// DO: - Fetch existing cards
var error: NSError?
var fetchRequest = NSFetchRequest(entityName: "Card")
if let results = moc.executeFetchRequest(fetchRequest, error: &error) as? [Card] {
if !results.isEmpty {
for x in results {
if x.id == id {
println("already exist")
moc.deleteObject(x)
}
}
}
} else {
println(error)
}
No more existingCards, the result of the the fetch is now processed as soon as possible. Something isn't clear to me yet, but now my code works. If you have any improvements/better ways, they're welcome.
P.S.: I actually found Apple reference useful but hard to understand because I don't know Obj-C. Often I can figure what the code do, but in swift functions and properties are a bit different.
I've written a class called Movie whose initializer takes an integer "id" to retrieve data from the Rotten Tomatoes API:
init(id: Int) {
let movieURL = NSURL(string: "http://api.rottentomatoes.com/api/public/v1.0/movies/\(id).json?apikey=\(apiKey)")!
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: movieURL), queue: NSOperationQueue()) { (response, movieData, error) -> Void in
var movieJson = NSJSONSerialization.JSONObjectWithData(movieData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? [String: AnyObject]
self.id = self.idFromMovieJson(movieJson)
self.title = self.titleFromMovieJson(movieJson)
// ...
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate!.movieDidDownload(self)
})
}
}
If I instantiate a Movie object with a correct id, everything goes as expected. This is what I've written in another class:
var movie = Movie(id: 771351912)
movie.delegate = self
func movieDidDownload(movie: Movie) {
println(movie.title)
}
And this is the output:
Optional("Interstellar")
However, when I try to instantiate a Movie object inside a for-loop like this:
let ids = [771351912, 771380953, 771041011, 13863, 12490, 771311818, 771321699, 11691]
for id in ids {
var movie = Movie(id: id)
movie.delegate = self
}
The print results are not very encouraging:
Optional("Super 8")
Optional("Interstellar")
Optional("Pulp Fiction")
nil
Optional("The Nightmare Before Christmas")
nil
nil
Optional("Nightcrawler")
Worst of all, the println() output is different each time I build and run my code:
Optional("Interstellar")
Optional("Pulp Fiction")
Optional("Super 8")
Optional("Nightcrawler")
Optional("The Nightmare Before Christmas")
nil
nil
nil
I even tried to create different NSOperationQueues with different names to use in the NSURLConnection.sendAsynchronousRequest() method but that didn't work out too:
var queue = NSOperationQueue()
queue.name = "\(id)"
I guess that the problem is related to the fact I'm sending too many requests at the same time. I've placed sleep(1) in the ids for-loop and it actually prints the movie titles properly.
Does anybody know how to asynchronously make multiple requests inside a for-loop?
The swift println() function is asynchronous, and the order of delivery isn't guaranteed, so you may get all kinds of confusing results if you use it like this.
I'd suggest switching to using NSLog() instead, as that will give more consistent results.
Also, creating a new throwaway NSOperationQueue() for each request seems like a bad idea, as the queue may get released before the operation executes. Try using NSOperationQueue.mainQueue() instead.
As an added benefit, using a single queue instead of a different queue for each request should ensure that the movies get downloaded in the order you've requested them.
I am trying to mimic the app as in this youtube tutorial, but using Swift and I am facing a problem constructing the closure as shown in the code snippet below.
func twitterTimeline() {
let account = ACAccountStore()
let accountType = account.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierTwitter)
// take action
account.requestAccessToAccountsWithType(accountType, options: nil,
completion: { (granted, error) in
if (granted) {
// invoke twitter API
let arrayOfAccount: NSArray = account.accountsWithAccountType(accountType)
if (arrayOfAccount.count > 0) {
let twitterAccount = arrayOfAccount.lastObject as ACAccount
let requestAPI = NSURL.URLWithString("http://api.twitter.com/1.1/atuses/user_timeline.json")
var parameters = Dictionary<String, String>()
parameters["100"] = "count"
parameters["1"] = "include_entities"
let posts = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: SLRequestMethod.GET, URL: requestAPI, parameters: parameters)
posts.account = twitterAccount
// This is the Error Prone Area
let handler: SLRequestHandler = { (response, urlResponse, error) in
self.array = NSJSONSerialization.JSONObjectWithData(data: response, options: NSJSONReadingOptions.MutableLeaves, error: &error) as NSArray
}
posts.performRequestWithHandler(handler)
}
} else {
// do something
}
}
)
}
The error I get is
Cannot convert expression's type '($T1, $T2, $T3) -> $T0' to type '()'
I have tried checking and explicitly casting the types with no much help. I believe the error is somewhere else. Could anyone help me with what exactly is the trouble? I am sorry, if this turns out to be a näive question.
Thanks in advance,
Nikhil
This looks like an interesting case of error propagation in the compiler — I'd suggest filing a bug report with Apple.
The error message you're getting says that you can't assign a closure (which takes three parameters and returns one value) to something that takes no parameters. What's actually going wrong is that the handler closure you're defining takes its error input parameter and tries to pass it to JSONObjectWithData(_:options:error:). That's problematic from a language perspective because the error you're getting in is an immutable (optional) reference to one error, and the parameter you're passing it to expects a mutable pointer for it to (potentially) write another error into.
It's also incorrect API usage. The error you receive as a parameter in the closure is a report of an error that happened in whatever procedure calls your closure. You should log this error, present it to the user, or examine it so your app can gracefully fail. The error parameter you pass to JSONObjectWithData is a place for you to receive reports of additional errors that occur when decoding JSON from your data — you should be handling this error, too. These are two separate places to receive errors, so you shouldn't be passing one to the other.
If you fix that, you'll find a more helpful compiler message saying that the data: label on the first parameter to that function should be omitted. Also, you can use type inference for the options: parameter. So, your handler definition should look something more like this:
let handler: SLRequestHandler = { (response, urlResponse, error) in
// check for error and do something about it if need be, then...
var err: NSError?
if let jsonArray = NSJSONSerialization.JSONObjectWithData(response, options: NSJSONReadingOptions.MutableLeaves, error: &err) as? NSArray {
self.array = jsonArray
} else {
// do something about err
}
}
(You can also probably use a Swift typed array instead of an NSArray if you know what to expect from your JSON. But that's another subject for another question. Actually, several questions.)