AWSKinesisRecorder get the records that are not streamed - ios

In AWSKinesisRecorder (here), how can we check if our records are submitted to the server / reached the AWS or check if we have records on disk that are not yet submitted?
kinesisRecorder.submitAllRecords()?.continueOnSuccessWith(block: { (task: AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
Logger.log(method: .error, "\(#function) \(#line) \(#file)", "Error: \(error)")
}
if let result = task.result {
Logger.log(method: .info, "\(#function) \(#line) \(#file)", "Result: \(result)")
}
print("FINISHED AWSTask kinesisRecorder", task, task.error, task.isCompleted, task.isFaulted, task.isCancelled)
return nil
})
The completion block never returns an error neither does the task.result is also nil, even if the internet is turned off on the device.

Not Possible
Seems like there is no public API available to fetch the records that are written onto the local mobile storage, neither you can read the sent records from Kinesis.
Its aim is to stream data in a unidirectional way.
I had to create another API to get the details of records received on the server end and had to rely on the Kinesis that each record is 100% written safely onto the local storage. So, far I have not seen any data loss.

Related

Unexpected behavior of FireStore listener

I am working on an iOS app using Firebase as backend. I am encountering a problem where a listener on a sub collection is behaving unexpectedly. Let me explain my data models first:
I have a top-level collection called "families". Within this collection, I have a sub-collection called "chores". It looks something like this:
Within my iOS app, I am adding a listener to this "chores" sub collection like this:
func readChoreCollection(_ familyId: String) {
if familyChoresListener == nil {
let choreCollection = database.collection("families").document(familyId).collection("chores")
familyChoresListener = choreCollection.order(by: "created")
.addSnapshotListener(includeMetadataChanges: false) { [weak self] querySnapshot, error in
print("\(#fileID) \(#function): \(choreCollection.path)")
guard let querySnapshot = querySnapshot else {
print("\(#fileID) \(#function): Error fetching documents: \(error!)")
return
}
let chores: [Chore] = querySnapshot.documents
.compactMap { document in
do {
return try document.data(as: Chore.self)
} catch {
print("\(#fileID) \(#function): error")
return nil
}
}
if chores.isEmpty {
print("\(#fileID) \(#function): received empty list, publishing nil...")
self?.familyChoresPublisher.send(nil)
} else {
print("\(#fileID) \(#function): received chores data, publishing ... \(querySnapshot.metadata.hasPendingWrites)")
self?.familyChoresPublisher.send(chores)
}
}
}
}
According to the Firestore doc:
The snapshot handler will receive a new query snapshot every time the query results change (that is, when a document is added, removed, or modified
So, when I add a new document to the "chores" sub-collection, the listener did trigger, that is expected. However, it is triggered twice, one from local change, and one from remote change. As shown in the log below:
ChoreReward/ChoreRepository.swift readChoreCollection(_:): received chores data, publishing ... true
ChoreReward/ChoreService.swift addSubscription(): received and cached a non-nil chore list
ChoreReward/ChoreRepository.swift readChoreCollection(_:): families/tgO0B4bjq8uwAzmBaOtL/chores
ChoreReward/ChoreRepository.swift readChoreCollection(_:): received chores data, publishing ... false
ChoreReward/ChoreService.swift addSubscription(): received and cached a non-nil chore list
You can see that the listener is called twice, one with hasPendingWrites = true and one with hasPendingWrites = false. So the documentation did mentioned that the local changes will fire-off the callback to listener first before sending data back to Firestore. So this behavior is kinda expected??? On my other listeners (document listeners) within the app, they are only getting called once by the remote changes, not twice. Maybe there is a different in behavior of document vs. collection/query listener? Can anybody verify this difference?

Swift Network framework with TCP

I'm making a simple test app using Swift Network framework. One server, one client (both iOS simulators), and successfully established tcp connection between them. I'm trying to send a series of short messages.
Server is sending strings made of natural numbers from 1 to 999. Each number is sent as separate Data, isComplete and contentContext default values are true and .defaultMessage correspondingly.
var count = 0
func send(data: Data) {
self.connection.send(content: data, completion: .contentProcessed( { error in
if let error = error {
self.connectionDidFail(error: error)
return
}
self.count += 1
let newData = "\(self.count)".data(using: .utf8)!
if self.count < 1000 {
self.send(data: newData)
}
print("connection \(self.id) did send, data: \(newData as NSData)")
}))
}
Client is receiving them...
private func setupReceive() {
nwConnection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (data, contentContext, isComplete, error) in
if let data = data, !data.isEmpty {
print("isComplete: \(isComplete)")
print("isFinal: \(contentContext.isFinal)")
let message = String(data: data, encoding: .utf8)
print("connection did receive, data: \(data as NSData) string: \(message ?? "-" )")
}
if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive()
}
}
}
... but there is something wrong. Some messages look like their bytes are stuck together (for example consecutive messages "2", "3", "4", "5" could be received like a single message "2345")
For all received messages isComplete equals false and contentContext property isFinal equals true, while .defaultMessage.isFinal should be equal to false.
For now, i'm stuck. Am i just using wrong parameters (I've tried various combinations, but none seems working to me)? Is NWConnection hideously changing messages while sending them?
How can one send a series of separate messages?
I am not familiar with this Network framework. But from reading documentation, it seems like you are directly using the transport layer to transmit messages.
Without an application layer protocol, there probably isn't a way for the client to distinguish between different messages. For example, using http as your application protocol has different parameters in the request to identify if its a complete message or not (Content-Length, Content-Encoding, Transfer-Encoding etc...)(Hope an expert can confirm on this)
You may define you own simple protocol so that decoding is possible on client end. For example, you can wrap each message with <Message>your-message</Message> and use it to identify different messages at the client (you will face some drawbacks of something so simple along the way)
There are many things to consider when developing a custom protocol. Better do some reading on this subject if you are serious on it.
Upon further reading, it seems that the following receive is provided:
final func receiveMessage(completion: #escaping (Data?, NWConnection.ContentContext?, Bool, NWError?) -> Void)
... which is able to read complete data. The Discussion section will provide some valuable insight on transport type implications and framing logic required.

CloudKit Error Differentiation

I need some help to learn how to properly handle errors when fetching records via CloudKit. Currently I have an app that saves numerous records in the cloud, and will load them at launch. I have been referencing the records using a CKReference, and anytime I save the reference I use the CKReferenceAction.DeleteSelf option. A problem I've encountered periodically is that when a referenced record is deleted, sometimes there can be a significant amount of time before the reference deletes itself. This has caused me to occasionally come across the situation where my app has fetched a CKReference for a record that no longer exists. I'm able to manually find out when this happens just by inserting print(error!) in my error handler. What I would like to know is how I can add some code to detect this specific error i.e. if error.localizedDescription == ??? {.
Here is the basic code I'm using for the fetch:
let fetch = CKFetchRecordsOperation(recordIDs: recordIDs)
fetch.perRecordCompletionBlock = { (record:CKRecord?, recordID:CKRecordID?, error: NSError?) in
if error != nil {
// Error Line A (See below)
print("ERROR! : \(error!.localizedDescription)")
// Error Line B (See below)
print("ERROR: \(error!)")
}
else if let record = record {
// Record was found
}
}
if let database = self.privateDatabase {
fetch.database = database
fetch.start()
}
And then when it tries to fetch the non-existent record, here is the error message that prints out in the compiler window:
a) ERROR! : Error fetching record <CKRecordID: 0x10025b290; dbbda7c3-adcc-4271-848f-6702160ea34f:(_defaultZone:__defaultOwner__)> from server: Record not found
b) ERROR: <CKError 0x125e82820: "Unknown Item" (11/2003); server message = "Record not found"; uuid = (removed); container ID = "(removed)">
Above in error line B, where it says CKError 0x125e82820:, can I use this to create an if statement to check for this specific error type? I really could use any help finding a way to resolve this issue properly when it happens. I have set up some loading structure for my app, and when it thinks there is a record it needs to find, but can't, it screws up my loading process. I would really appreciate any help I can get, I assume it's an easy solution, but apparently not one I've been able to find. Thank you!
UPDATE -
Thanks to #AaronBrager, I was able to find the correct solution. You can verify the error code to match it to any specific error, and the domain to make sure it's a CKError. Here is the solution that works for me:
let fetch = CKFetchRecordsOperation(recordIDs: recordIDs)
fetch.perRecordCompletionBlock = { (record:CKRecord?, recordID:CKRecordID?, error: NSError?) in
if error != nil {
if error!.code == CKErrorCode.UnknownItem.rawValue && error!.domain == CKErrorDomain {
// This works great!
}
}
else if let record = record {
// Record was found
}
}
if let database = self.publicDatabase {
fetch.database = database
fetch.start()
}
You should be able to uniquely identify an error's cause by inspecting its domain and code variables. Same domain and code, same problem. And unlike localizedDescription, it won't change between users.

How do I implement saving 1000s of records using CloudKit and Swift without exceeding 30 or 40 requests/second

Let me describe the basic flow that I am trying to implement:
User logs in
System retrieves list of user's connections using HTTP request to 3rd party API (could be in the 1000s). I'll call this list userConnections
System retrieves stored connections from my app's database (could be in the 100,000s). I'll call this list connections
System then checks to see if each userConnection exists in the connections list already and if not, saves it to database:
for userConnection in userConnections {
if connections.contains(userConnection) {
//do nothing
} else {
saveRecord(userConnection)
}
}
The problem with this is that when the first users log in, the app will try to make 1000 saveRecord calls in a second which the CloudKit server will not allow.
How can I implement this in a different way using CloudKit and Swift so that I keep it to an acceptable number of requests/second, like ~30 or 40?
For anyone wondering, this is how I ended up doing it. The comment by TroyT was correct that you can batch save your records. This answer includes bonus of queued batches:
let save1 = CKModifyRecordsOperation(recordsToSave: list1, recordIDsToDelete: nil)
let save2 = CKModifyRecordsOperation(recordsToSave: list2, recordIDsToDelete: nil)
save1.database = publicDB
save2.database = publicDB
save2.addDependency(save1)
let queue = NSOperationQueue()
queue.addOperations([save1, save2], waitUntilFinished: false)
save1.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if (error != nil){
//handle error
}else{
//data saved
}
}

Parse: Does not query saved objects in local datastore

I am currently developing a inventory app. My goal is to retrieve objects from Parse and then saving onto the local datastore. Querying objects from Parse and saving them works (because of the console message) but querying later on from the local datastore, does not retrieve anything! Here's my code:
let query = PFQuery(className: "Publication")
query.limit = 150
query.selectKeys(["publication_id","publication_Type","publication_Name"])
dispatch_async(dispatch_get_main_queue()) { () -> Void in
query.findObjectsInBackgroundWithBlock({ (pubObject, error) -> Void in
if error == nil {
print("Succesfully retrieved \(pubObject!.count)")
PFObject.saveAllInBackground(pubObject, block: { (success, error) -> Void in
print("Saved \(pubObject!.count) in local DataStore")
})
}
})
}
This message comes out from the XCode console:
"Succesfully retrieved 103
Saved 103 in local DataStore"
So far so good right?
This is my code when I am about to query from the local datastore:
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let bookQuery = PFQuery(className: "Publication")
.fromLocalDatastore()
bookQuery.whereKey("publication_Type", equalTo: "Book")
bookQuery.findObjectsInBackgroundWithBlock { (bookObject, error) -> Void in
if error == nil{
print("Books found: \(bookObject!.count)")
self.displayData(bookObject!)
}
}
}
And I get from the console: Books found: 0.
What gives? What am I doing wrong? I read and read and read. NOTHING. I thought the ".ignoreACL()" would work but it didn't. Can anyone help me please?
I don't see where you are pinning the PFObjects into the local datastore. Perhaps that is your problem.
Calling any of PFObjects save methods saves them back to your parse server, not a local datastore. Look up how to use pin to accomplish what you want.
Also, dispatching these asynchronous calls to the main queue makes no sense. They are already executing on a background queue. In most cases, you only need to dispatch back to the main queue if you want to do something to the UI and that should be done in the completion handler.

Resources