Storing AnyObject in array? - ios

I've got an array that I'm trying to store timestamps into.
I'm testing stuff out with something like:
#IBAction func buttonPressed(sender:UIButton!) {
let post = ["MinutesLeft" : (FIRServerValue.timestamp())]
DataService.ds.REF_POSTS.childByAutoId().setValue(post)
}
Then I'm trying to call it back and fill an array with some of those timestamps:
DataService.ds.REF_POSTS.queryOrderedByChild("MinutesLeft").queryStartingAtValue(Int(cutoff)).observeEventType(.ChildAdded, withBlock: { (snapshot:FIRDataSnapshot) in
let post = snapshot.value!["MinutesLeft"]
print("POST \(post)" )
self.postsArray.append(post)
print(self.postsArray)
} else {
print("Didn't work")
}
})
Then I'm running a timer that's meant to clear out certain posts:
func removeExpiredItems() {
let cutoff = UInt64(1000 * floor(NSDate().timeIntervalSince1970) - 10*60*1000)
while (self.postsArray.count > 0 && self.postsArray[0] < cutoff) {
// remove first item, because it expired
self.postsArray.removeAtIndex(0)
print(postsArray.count)
}
}
My issues are :
I don't know what kind of array to have here. It's telling me that a timestamp is an AnyObject, but when I go to make an array of anyobjects I get a straight up Xcode internal error crash...so I guess that's a no-go.
I can translate it all to strings and just store it like that, but the issue comes then when I'm trying to compare the times vs my cutoff in my removeExpiredItems func.
I was trying to do something like:
Take the timestamps, change them to a string , then down when I'm going to make the comparison change the String to an Int but I get something like "This kind of conversion will always fail".
Any ideas here?

let post = snapshot.value!["MinutesLeft"]
The type of post will be AnyObject, (Optional<AnyObject>) in this case.
You have store it as the data type you want. For example in this case you can store it as NSTimeInterval
let post = snapshot.value!["MinutesLeft"] as! NSTimeInterval
And store it in an Array of NSTimeInterval as
var postsArray :[NSTimeInterval] = []

Related

Is it possible to force encode Date as Date type instead of string?

I am trying to mock Apollo Queries using its init. It pretty much is taking in a dictionary to build the object up.
public init(unsafeResultMap: [String: Any]) {
self.resultMap = unsafeResultMap
}
So, I have decided to create Mock objects that have the same properties of the query objects while being Encodable (So we get the free JSON conversion, which can be represented as a string version dictionary).
For example:
class MockAnimalObject: Encodable {
let teeth: MockTeethObject
init(teeth: MockTeethObject) {
self.teeth = teeth
}
}
class MockTeethObject: Encodable {
let numberOfTeeth: Int
let dateOfTeethCreation: Date
init (numberOfTeeth: Int, dateOfTeethCreation: Date) {
self.numberOfTeeth = numberOfTeeth
self.dateOfTeethCreation = dateOfTeethCreation
}
}
The problem is, the Apollo conversion checks the types during the result map, which in our case is a string of [String: Encodable].
And this is where the Date encodable becomes a problem.
/// This property is auto-generated and not feasible to be edited
/// Teeth date for these teeth
public var teethCreationDate: Date {
get {
// This is the problem. resultMap["teethCreationDate"] is never going to be a Date object since it is encoded.
return resultMap["teethCreationDate"]! as! Date
}
set {
resultMap.updateValue(newValue, forKey: "teethCreationDate")
}
}
So, I am wondering if it is possible to override the encoder to manually set the date value as a custom type.
var container = encoder.singleValueContainer()
try container.encode(date) as Date // Something where I force it to be a non-encodable object
JSON has nothing to do with this. JSON is not any kind of dictionary. It's a serialization format. But you don't want a serialization format. You want to convert types to an Apollo ResultMap, which is [String: Any?]. What you want is a "ResultMapEncoder," not a JSONEncoder.
That's definitely possible. It's just an obnoxious amount of code because Encoder is such a pain to conform to. My first pass is a bit over 600 lines. I could probably strip it down more and it's barely tested, so I don't know if this code works in all (or even most) cases, but it's a start and shows how you would attack this problem.
The starting point is the source code for JSONEncoder. Like sculpture, you start with a giant block of material, and keep removing everything that doesn't look like what you want. Again, this is very, very lightly tested. It basically does what you describe in your question, and not much else is tested.
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10,
dateOfTeethCreation: .now))
let result = try AnyEncoder().encode(animal)
print(result)
//["teeth": Optional(["dateOfTeethCreation": Optional(2022-08-12 18:35:27 +0000),
// "numberOfTeeth": Optional(10)])]
The key changes, and where you'd want to explore further to make this work the way you want, are:
Gets rid of all configuration and auto-conversions (like snake case)
Handles the "special cases" (Date, Decimal, [String: Encodable]) by just returning them. See wrapEncodable and wrapUntyped
If you want [String: Any] rather than [String: Any?] (which is what ResultMap is), then you can tweak the types a bit. The only tricky piece is you would need to store something like nil as Any? as Any in order to encode nil (or you could encode NSNull, or you could just not encode it at all if you wanted).
Note that this actually returns Any, since it can't know that the top level encodes an object. So you'll need to as? cast it to [String: Any?].
To your question about using Mirror, the good thing about Mirror is that the code is short. The bad thing is that mirror is very slow. So it depends on how important that is. Not everything has the mirror you expect, however. For your purposes, Date has a "struct-like" Mirror, so you have to special-case it. But it's not that hard to write the code. Something like this:
func resultMap(from object: Any) -> Any {
// First handle special cases that aren't what they seem
if object is Date || object is Decimal {
return object
}
let mirror = Mirror(reflecting: object)
switch mirror.displayStyle {
case .some(.struct), .some(.class), .some(.dictionary):
var keyValues: [String: Any] = [:]
for child in mirror.children {
if let label = child.label {
keyValues[label] = resultMap(from: child.value)
}
}
return keyValues
case .some(.collection):
var values: [Any] = []
for child in mirror.children {
values.append(resultMap(from: child.value))
}
return values
default:
return object
}
}
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10, dateOfTeethCreation: .now))
let result = resultMap(from: animal)
print(result)
// ["teeth": ["dateOfTeethCreation": 2022-08-12 21:08:11 +0000, "numberOfTeeth": 10]]
This time I didn't bother with Any?, but you could probably expand it that way if you needed. You'd need to decide what you'd want to do about enums, tuples, and anything else you'd want to handle specially, but it's pretty flexible. Just slow.
As pointed out in comments, JSON (Javascript object notation) is universal format and is not anyhow related to Date object in Swift after it is encoded. Therefore somewhere in the flow you need to make it String Double or some other object type that can be encoded to JSON. Anyway, if you want to make encoding Date to be easier you can take hold of some native JSONEncoder functions, such as folowing:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

Dictionary not appending values

I think I'm introducing some logic error and I might be missing something here.
Please consider the following code:
// Model
class MyModel: NSObject {
let month: Int
let destiny: String
init(month: Int, destiny: String) {
self.month = month
self.destiny = destiny
}
}
var datasource: [MyModel] = []
var dict: [Int : [MyModel]] = [:]
func fillDatasource() {
for _ in 0...20 {
let month = Int.random(in: 1...12)
let destiny = "Any"
let model = MyModel(month: month, destiny: destiny)
datasource.append(model)
}
}
func fillDict() {
datasource.forEach {
let month = $0.month
dict[month]?.append($0)
}
print(dict) // always empty
}
fillDatasource()
fillDict()
Inside my fillDict function the array is always nil.
I think this is because the key doesn't exist , so the value cannot be appended to that specific key.
My question is: if the key doesn't exist, calling the append function would insert the key as well?
Am I missing something here?
Thanks.
Your assumption is incorrect and there is no reason to think that this would insert a new array.
It might seem intuitive for this case but it may be very wrong for some cases. How about something like this:
garages[myName]?.parkCar(myCar)
Should this construct a new garage for my car? I think not. But even if so; what if default constructor is unavailable and this is actually defined as a protocol:
protocol Garage {
func parkCar(_ car: Car)
}
var garages[String: Garage]
there is no way for Swift to fill in this object automatically.
Technically there would be a possible solution for this work that Swift would automatically construct an object for you in dictionary if this object had a default constructor and possibly the object type is a struct or a final class... But this would most likely only introduce more confusion than it would solve.
The most straight forward solution to your example is what #Sh_Khan wrote (but later deleted) which is:
if dict[month] == nil {
dict[month] = [$0]
}
else {
dict[month]?.append($0)
}
Probably some more feasible approach would be
dict[month] = (dict[month] ?? []) + [$0]
but as described in a comment there is already a method that does exactly that for you:
dict[month, default: []].append($0)
I hope we can agree that this is a more general approach and it fixes all cases. For instance
garages[myName, default: PublicGarage(parkingSpot: myName)].parkCar(myCar)
You can update your fillDict method to the following:
func fillDict() {
datasource.forEach {
let month = $0.month
if dict.keys.contains(month) {
dict[month]?.append($0)
} else {
dict[month] = [$0]
}
}
print(dict)
}
Explanation:
We need to check if the month key already exits in the dictionary, than append in it's array else we are assigning a new array against a month in the dictionary
Dic is empty because dic[month] is nil, the value has never been altered.
To group array by a property of the array elem, I'd use the following:
dic = Dictionary(grouping: datasource) { (model) -> Int in
return model.month
}

Compare different rows and make them one at tableview - Firebase/Swift 3

I am new in Swift 3 and Firebase programming, but I am making good progress. But sometimes things come harder and here I am again because I could not find some text as specifically as I need, neither updated for Swift 3. I have tried many many ways.
The issue is that I am building an app for some teachers to make a kind of examination with their students. Each Student has the same examination by two different teachers at different times and the results are shown at a table view controller like the following image:
As you can see, I have two rows for each student with two different scores (partial). What I wish is to filter this Firebase data, search for those two “repeated” students and make an average score from them and show the data at a table view controller (exam1 + exam2 / 2) for the final average score. This is my currently Firebase structure (I will post an alternative later at this question):
Now, parts of the code:
1. Struct for variables and snapshot
import Foundation
import Firebase
struct Examination {
var nome: String?
var preceptor: String?
var dataHoje: String?
var nota: String?
var key: String?
init(nome: String, preceptor: String, nota: String, dataHoje: String, key: String) {
self.nome = nome
self.preceptor = preceptor
self.nota = nota
self.dataHoje = dataHoje
self.key = key
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as? [String: AnyObject]
nome = snapshotValue?["nome"] as? String
preceptor = snapshotValue?["preceptor"] as? String
nota = snapshotValue?["nota"] as? String
dataHoje = snapshotValue?["dataHoje"] as? String
notaAtitude = snapshotValue?["notaAtitude"] as? String
}
func toAnyObject() -> Any {
return [
"nome": nome,
"preceptor": preceptor,
"nota": nota,
"dataHoje": dataHoje,
"userKey": key,
]
}
}
Load method for main table view controller
import Foundation
import Firebase
var refAlunos: [Examination] = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
Load_HM1()
}
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
}
self.refAlunos = newTeste2
})
}
At last, the Firebase structure I have also tried, but I always receive nil as result, table view empty:
The code for this alternative way (method load), I could not implement:
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists") ... I CRASHED HERE..
// The rest of this method is the same shown before
So, resuming the issue, I need a way to compare rows with the same student name (always will be 2) and calculate the average of these two scores and publish a table view with this final score and name, almost as the first image, but only changing the score to the final average. I really don’t know if there is a way and I don’t know which way to go. I have searched a lot, but nothing found. I guess this second firebase structure could be more friendly, but my rows are empty. If need more information to understand this issue, ask me that I update the question. Thank you very much for any help.
I'm not aware of Firebase providing such functionalities out of the box, therefore I would recommend doing your calculations manually.
Before setting self.refAlunos at the end of the callback function you can extract the corresponding pairs of examinations, compute the average grade for each pair and create a new instance containing the computed average grade and the remaining attributes. Afterwards you can store an array of the generated examinations in self.refAlunos.
Additionally, you should take a look at Structure Your Database and Structuring your firebase data. Applying these guidelines will help to flatten your data and improve performance as your data grows.
Edit:
I'm currently not on my Mac, but I hope my suggestions can point you in the right direction, although they might not be 100% syntactically correct.
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
var examinations: [Examination] = []
var position = -1
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
for exam in 0..<examinations.count {
if (examinations[exam].nome == itemsAadicionar.nome) {
position = exam
break
}
}
if (position != -1) {
examinations[position].nota += itemsAadicionar.nota
examinations[position].nota /= 2
position = -1
}
}
self.refAlunos = examinations
})
}
I have edited your code and I hope you will get the gist. However, this approach is only suitable if you always have two exams per student. If you have more, you need to adapt it a little bit.

Complex Firebase Query with iOS Swift

My DB looks like this:
shows{
show1{
name: //Showname
start: //Timestamp start
end: //Timestamp end
rating: //Showrating INT
}
show2{
...
}
}
How can i query the shows, which are running now (start < now && end > now), ordered by the rating?
Is this even possible with this Database Structure or do i have to change it?
You should name shows' children nodes by their UID, not "show1", "show2", etc. Then you would query your database for the shows ordered by their rating, and use a conditional to test whether each result is within the desired time frame. I haven't actually tested this code, but something like this should work:
ref?.child("shows").child(getUid()).queryOrdered(byChild: "rating").observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children as? [String: AnyObject] {
// filter results
if (child["start"] <= currentTime && child["end"] >> currentTime ) {
// results
resultsArray.append(child)
}
}
However, I recommend reading about denormalizing data in Firebase first:
https://firebase.google.com/docs/database/ios/structure-data
https://stackoverflow.com/a/16651115/3502608
And read the docs over querying after you understand denormalization:
https://firebase.google.com/docs/database/ios/lists-of-data
First of all if you are using timestamps and you want to manipulate them in your front end or perform any algorithmic procedure over the timestamp (i.e > or <) then use NSDate not FIRServerValue.timestamp().
To query your show that are having the end : before the current timestamp try using this:-
let currentTimeStamp = Int(NSDate.timeIntervalSinceReferenceDate*1000)
FIRDatabase.database().reference().child("shows").queryOrdered(byChild: "end").queryStarting(atValue: currentTimeStamp).observeSingleEvent(of: .value, with: {(Snapshot) in
print(Snapshot)
})
This will give you all the shows who are running now. Also for this to work you have to store the value of start and end in similar fashion i.e Int(NSDate.timeIntervalSinceReferenceDate*1000)
To order them according to your show rating , you can only retrieve the values and store them in a struct.
struct show_Struct {
var name : String!
var rating : Int! //If it is int or float if it is of type float.
...
}
Before calling the reloadData() function on any of your tableView or collectionView, just call
let showFeed = [show_Struct]()
..
self.showFeed.sort(by: {$0.rating > $1.rating})
self.tableView.reloadData()

how to save and read dictionary of touples to NSUserDefaults?

I have an dictionary = String: ([(String)], [(Int)], NSDate, Bool, [(String)]) and I attempted to deconstruct it into seperate arrays when then app calls applicationWillTerminate
var codes = [(String)]()
var messages = [[String]]()
var senders = [[Int]]()
var dates = [(NSDate)]()
var bools = [(Bool)]()
var pairs = [[String]]()
for code in self.dictionary.keys {
codes.append(code)
messages.append(self.dictionary[code]!.0)
senders.append(self.dictionary[code]!.1)
dates.append(self.dictionary[code]!.2)
bools.append(self.dictionary[code]!.3)
pairs.append(self.dictionary[code]!.4)
}
self.userDefaultsMessages.setObject(codes, forKey: "userMessagesArrays")
self.userDefaultsSenders.setObject(messages, forKey: "userSentArrays")
self.userDefaultsDates.setObject(senders, forKey: "userDatesArray")
self.userDefaultsDeletedBool.setObject(dates, forKey: "userDeletedArrays")
self.userDefaultsPairs.setObject(bools, forKey: "userPairsArrays")
self.userDefaultsCodeKeys.setObject(pairs, forKey: "userCodesArrays")
self.userDefaultsCodeKeys.synchronize()
self.userDefaultsMessages.synchronize()
self.userDefaultsSenders.synchronize()
self.userDefaultsDates.synchronize()
self.userDefaultsDeletedBool.synchronize()
self.userDefaultsPairs.synchronize()
and then I attempt to pull it all back together when the app calls applicationDidBecomeActive
//read
if let savedCodesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userCodesArrays") {
self.userCodes = savedCodesArray! as! [String]
if let savedMessagesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userMessagesArrays") {
self.Usermessages = savedMessagesArray! as! [[String]]
if let savedSendersArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userSentArrays") {
self.Usersenders = savedSendersArray! as! [[Int]]
if let savedDatesArray : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userDatesArray") {
self.Userdates = savedDatesArray! as! [NSDate]
if let savedBools : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userDeletedArrays") {
self.Userbools = savedBools! as! [Bool]
if let savedPairs : AnyObject? = self.userDefaultsCodeKeys.objectForKey("userPairsArrays") {
self.Userpairs = savedPairs! as! [[String]]
var indexPath: Int = 0
for code in self.userCodes {
self.dictionary[code]! = [self.Usermessages[indexPath], self.Usermessages[indexPath], self.Userdates[indexPath], self.Userbools[indexPath], self.Userpairs[indexPath]]
}
}
}
}
}
}
}
I am fairly new to iOS development and could use help, how would i save the single dictionary = String: ([(String)], [(Int)], NSDate, Bool, [(String)]) to NSUserDefaults and then later read it.. the documentation was not very helpful since it only worked with simple dictionaries
the code looks incredibly cumbersome so I know I can't be doing it right. It should be a simple solution since I only have one variable to save to NSUserDefaults.
Short answer: You can't.
NSUserDefaults will only record "property list objects" (dictionaries, arrays, strings, numbers (integer and float), dates, binary data, and Boolean values).
You can't save any other types of data into NSUserDefaults, or into a property list. The only solution is to convert other data types into those types.
Tuples are not one of those types, so they can't be saved into user defaults.
First, your interaction with NSUserDefaults is completely wrong. You only need to interact with the shared NSUserDefaults.sharedUserDefaults() instance, not create separate instances, which seems to be what you're doing (i.e. userDefaultsMessages, userDefaultsSenders, etc.).
Second, you don't need to call synchronize() at all. There are very few conditions under which you need to call it manually, and this isn't one of them.
Third, the easiest way to store a particular tuple in NSUserDefaults is to convert it into an Array (or NSArray) and store the result. Of course, this assumes that the tuple contains only types that can be serialized, which your example seems to contain. Unfortunately there's no general solution to this, but creating an array from a tuple is straight forward, as you can just map the tuple indices to array indices.
Finally, such large tuples are usually the result of poor design somewhere along the line. Perhaps refactoring would help resolve your storage issue?

Resources