I need to get ~50 items with their primary keys from dynamodb using ios sdk. i am able to get the items by AWSDynamoDB.defaultDynamoDB().batchGetItem but couldn't figure out if it is possible to use object mapper with the response. Unfortunately objectmapper class in ios doesn't have batchGet function. As far as i know i cant use query in this situation.
Is it possible to use object mapper? If not which one makes more sense: parsing the response to get the desired class instance or calling objectMapper.load on each item?
Currently, AWSDynamoDBObjectMapper does not support the batch get item. You need to load one item at a time if you want to use the object mapper.
I solved it by doing this,
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.defaultDynamoDBObjectMapper()
let task1 = dynamoDBObjectMapper.load(User.self, hashKey: "rtP1oQ5DJG", rangeKey: nil)
let task2 = dynamoDBObjectMapper.load(User.self, hashKey: "dbqb1zyUq1", rangeKey: nil)
AWSTask.init(forCompletionOfAllTasksWithResults: [task1, task2]).continueWithBlock { (task) -> AnyObject? in
if let users = task.result as? [User] {
print(users.count)
print(users[0].firstName)
print(users[1].firstName)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Related
I have some core that performs a Batch delete request:
extension NSManagedObject: Clearable {
/// Clears all objects of this type in coreData
static func clearAll() {
let context = AppDelegate.sharedInstance()?.coreDataHelper.objectContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing:self))
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
if let unwrappedContext = context {
unwrappedContext.shouldDeleteInaccessibleFaults = true
let result = try unwrappedContext.execute(batchDeleteRequest) as? NSBatchDeleteResult
DLog("result \(result.debugDescription)")
switch result!.resultType {
case .resultTypeCount:
DLog("resultTypeCount")
case .resultTypeObjectIDs:
DLog("resultTypeObjectIDs")
case .resultTypeStatusOnly:
DLog("resultTypeStatusOnly")
}
if let objectIDArray = result?.result as? [NSManagedObjectID] {
let changes = [NSDeletedObjectsKey : objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [unwrappedContext])
}
try context?.save()
}
} catch let error as NSError {
DLog("Error removing : \(error), \(error.localizedDescription)")
}
}
}
The code works fine, but the result of the batch delete is always .resultTypeStatusOnly
The documentation here https://developer.apple.com/library/content/featuredarticles/CoreData_Batch_Guide/BatchDeletes/BatchDeletes.html#//apple_ref/doc/uid/TP40016086-CH3-SW2 says under the title Updating Your Application After Execution that
it is important that you notify the application that the objects in
memory are stale and need to be refreshed.
And to do that the result type has to be .resultTypeObjectIDs, to be able to trigger the NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [unwrappedContext]) part. It's just not clear how you get that.
How does one set the type of the result?
You can set the resultType property on the batchDeleteRequest. So, also from https://developer.apple.com/library/content/featuredarticles/CoreData_Batch_Guide/BatchDeletes/BatchDeletes.html :
When the executeRequest completes successfully, a response is received. That response can take one of two forms. The form of the response is deteremined by setting the resultType property on the NSBatchDeleteRequest. The default value is NSStatusOnlyResultType, which returns nothing.
The other option is NSBatchDeleteObjectIDsResultType, which returns an array of NSManagedObjectID instances indicating which entities were deleted during the execution.
Hope this helps. Good luck!
Thanks in advance for any help. I am trying to get Batch items (Load multiple) items from one DynamoDb table using the AWS iOS SDK (Swift). I can load one item using the Block syntax, but I need to load 10 or more than that. I don't want to use 10 Block calls to load them individually. I tried to follow the attach stackoverflow Link (where the similar solution is given) but I am getting the following compiler error message. I come from Java background, hence could also be a syntax issue. Is it the right way to load multiple items? I don't want to use low level API. Any help, where I am going wrong. Thanks.
aws dynamodb how to use object mapper with batch get in ios
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask.init(forCompletionOfAllTasksWithResults: tasksList).continueWithBlock { (task) -> AnyObject? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Have a try with the following codes (Swift 4.1, Feb 9th, 2018):
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
var tasksList = Array<AWSTask<AnyObject>>()
for i in 1...10 {
tasksList.append(dynamoDBObjectMapper.load(AWSCards.self, hashKey: "SH_"+String(i), rangeKey: nil))
}
AWSTask<AnyObject>.init(forCompletionOfAllTasksWithResults: tasksList).continueWith { (task) -> Any? in
if let cards = task.result as? [AWSCards] {
print(cards.count)
}
else if let error = task.error {
print(error.localizedDescription)
}
return nil
}
Your question is "how to use the object mapper" but it might be more efficient for you to not use it.
However, there is a way to use it. See Niklas's answer here and here (he copy & pasted), but something about it strikes me as fishy. I want to make the assertion that it is not as fast as the built-in batch-get function, but I am unsure. I suspect that this does not complete the items in parallel, or at least not as efficiently as in BatchGetItem.
See the docs: "In order to minimize response latency, BatchGetItem retrieves items in parallel."
According to Yosuke, "Currently, AWSDynamoDBObjectMapper does not support the batch get item. You need to load one item at a time if you want to use the object mapper" as of 2016. This still seems to be the case. I am using a version a couple versions behind, but not too far behind. Someone check.
In conclusion, if you are loading one item at a time, you are likely missing out on the whole purpose of BatchGetItem (low latency).
Pulling from various sources, including John Davis's question here, I have tested and ran this BatchGetItem result. Here ya go.
import AWSDynamoDB
let primaryKeyToSortKeyDict : [String : String] = .... // Your stuff
var keys = [Any]()
for key in primaryKeyToSortKeyDict.keys {
let partitionKeyValue = AWSDynamoDBAttributeValue()
partitionKeyValue?.s = String(key)
let sortValue = AWSDynamoDBAttributeValue()
sortValue?.s = String(primaryKeyToSortKeyDict[key]!)
keys.append(["partitionKeyAttributeName": partitionKeyValue, "sortKeyAttributeName": sortValue])
}
let keysAndAttributesMap = AWSDynamoDBKeysAndAttributes()
keysAndAttributesMap?.keys = keys as? [[String : AWSDynamoDBAttributeValue]]
keysAndAttributesMap?.consistentRead = true
let tableMap = [table : keysAndAttributesMap]
let request = AWSDynamoDBBatchGetItemInput()
request?.requestItems = tableMap as? [String : AWSDynamoDBKeysAndAttributes]
request?.returnConsumedCapacity = AWSDynamoDBReturnConsumedCapacity.total
guard request != nil else {
print("Handle some error")
return
}
AWSDynamoDB.default().batchGetItem(request!) { (output, error) in
print("Here is the batchgetitem output")
if error == nil {
// do output stuff
} else {
// handle error
}
}
I have a model which is a swift object.
I retrieve data from the web and then I need to update my object but there are different cases to handle:
I create an object, fetch the data, update the properties, save it in realm
I create an object, save it in realm, fetch the data, update the properties, save it again
I create an object, save it in realm, start to fetch the data, delete it from realm, receive the data, do nothing.
And this is how I handle it:
If self.invalidated == false & self.realm == nil -> update the properties on self
If self.invalidated == false & self.realm != nil -> Fetch the object from Realm in a background thread, set the properties, Refresh Realm on main thread before completion
If self.invalidated == true -> Stop (object has been deleted so it's not needed anymore)
One solution to simplify this code is to save the object in realm, but I don't want to save an object that could be dirtier than a potential one in realm. Or I could fetch whatever I have in realm before fetching the data online, so that I'm sure I save something at least as dirty as one in realm (but performance is not as optimal as it could be)
Could you give me some insight about what is the cleanest way to handle such a case?
Here is my code at the moment:
func fetchDataOnline(completion:(success : Bool)->()){
let params = ["tmdb_id":self.tmdbId,"lang":kLang]
let tmdbId = self.tmdbId
let invoker = AWSLambdaInvoker.defaultLambdaInvoker()
invoker.invokeFunction("getMovie", JSONObject: params).continueWithBlock { (task) -> AnyObject? in
guard self.invalidated == false else{
DDLogWarn("Movie has been invalidated while fecthing data")
completion(success: false)
return nil
}
if let dic = task.result as? NSDictionary{
var objectToUpdate = self
if self.realm != nil{ //Use new realm instance
guard let newRealmInstance = try! Realm().objectForPrimaryKey(MovieNew.self, key: tmdbId) else{
DDLogError("self.realm not nil but can't find movie in realm")
completion(success: false)
return nil
}
objectToUpdate = newRealmInstance
}
try! Realm().write{
objectToUpdate.setProperties(dic: dic)
objectToUpdate.lastUpdate = NSDate()
}
}
else{ //No dictionary found from result
if let error = task.error{
DDLogError(error.description)
}
DDLogError("Error getting movie")
}
Async.main{
try! Realm().refresh()
completion(success : task.error == nil)
}
return nil
}
}
Given your specific use-case scenarios, I think this looks like the best way to go about doing it. While you could do state tracking within the Realm objects themselves, it's much better to simply track their status against their parent Realm objects and respond to that accordingly.
The main best practice for Realm is mainly to try and minimize the number of write transactions as much as possible., so the only thing I could potentially question here is if it's absolutely necessary to add an object to a Realm before performing the request, only to potentially delete it again before the download is complete. If that's necessary because you're using those objects as placeholders in your UI, then that's perfectly fine.
Either way, all of this is really a matter of opinion. This solution you've put forward is competent and fills all of your requirements, so I'm not sure if it's worth trying to find something better. :)
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.