Core Data Memory Crash [closed] - ios

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am using Core Data for the first time in my project and I feel there is a serious problem in my approach. What I am doing is that I fetch data from server (data includes pngs as well). Save it in core data locally. Then at app launch, I load the entire data in an array. Then this array is used where ever I need it. I think I am following a very bad approach. Can anyone guide me what should be a better approach? Should I only query Core Data when data is needed instead of loading everything in memory at start?
When data is being populated to the array, I can see memory increasing in Xcode and after a certain value, it crashes.
Here is my code for saving data:
func saveDataLocally () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: "FoodPlace", in: moContext)
for foodPlaceData in self.downloadedData_ {
let foodPlace = NSManagedObject(entity: entity!, insertInto: moContext) as! FoodPlace
foodPlace.objectId = foodPlaceData.objectId_
foodPlace.name = foodPlaceData.name_
foodPlace.address = foodPlaceData.address_
foodPlace.keywords = foodPlaceData.keywords_
foodPlace.baseFavourites = Int64(foodPlaceData.baseFavourites_)
foodPlace.startingTime = foodPlaceData.startingTime_
foodPlace.endingTime = foodPlaceData.endingTime_
foodPlace.category = foodPlaceData.category_
foodPlace.basePrice = foodPlaceData.basePrice_
foodPlace.dealTitle = foodPlaceData.dealTitle_
foodPlace.versionNumber = Int64(foodPlaceData.versionNumber_)
foodPlace.menuItems = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.menuItems_)
foodPlace.location = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.location_)
foodPlace.deals = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.deals_)
foodPlace.foodPlacePhotos = NSKeyedArchiver.archivedData(withRootObject: foodPlaceData.foodPlacePhotos_)
moContext.insert(foodPlace)
}
do {
try moContext.save()
}
catch let error {
print("error saving = \(error.localizedDescription)")
}
}
where menuItems is a Dictionary which contains text as well as png images. Also, deals and foodPlacePhotos only contain png images.
Here is the code for fetching:
func loadDataLocally () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "FoodPlace")
do {
let results = try moContext.fetch(request)
let savedFoodPlaceData = results as! [FoodPlace]
downloadedData_ = []
for foodPlace in savedFoodPlaceData {
let objectId = foodPlace.objectId
let name = foodPlace.name
let address = foodPlace.address
let keywords = foodPlace.keywords
let baseFavourites = foodPlace.baseFavourites
let startingTime = foodPlace.startingTime
let endingTime = foodPlace.endingTime
let category = foodPlace.category
let menuItems = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.menuItems!) as? [Dictionary<String,AnyObject>]
let location = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.location!) as? Dictionary<String,Double>
let deals = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.deals!) as? [UIImage]
let basePrice = Float(foodPlace.basePrice)
let dealTitle = foodPlace.dealTitle
let versionNumber = foodPlace.versionNumber
let foodPlacePhotos = NSKeyedUnarchiver.unarchiveObject(with: foodPlace.foodPlacePhotos!) as? [UIImage]
let data = FoodPlaceData(objectId: objectId!, name: name!, address: address!, category: category!, keywords: keywords!, baseFavourites: Int(baseFavourites), startingTime: startingTime!, endingTime: endingTime!, menuItems: menuItems!, location: location!, deals: deals!,basePrice: basePrice,dealTitle: dealTitle!,versionNumber: Int(versionNumber),foodPlacePhotos: foodPlacePhotos!)
downloadedData_.insert(data, at: downloadedData_.count)
}
}
catch let error {
print("error fetching = \(error.localizedDescription)")
}
}
and here is the code for deleting data:
func deleteAllLocalData () {
let moContext = ((UIApplication.shared.delegate) as! AppDelegate).managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "FoodPlace")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try moContext.fetch(fetchRequest)
for managedObject in results {
let managedObjectData : NSManagedObject = managedObject as! NSManagedObject
moContext.delete(managedObjectData)
}
try moContext.save()
} catch let error {
print("Delete all data in FoodPlace error : \(error) \((error as NSError).userInfo)")
}
}

Difficult to be prescriptive without a lot more detail about your code. But a few thoughts:
rather than storing the PNG data in CoreData itself, consider storing it in the file system directly, and using CoreData to store only the filename for the PNG.
Alternatively, if you really want the PNGs in CoreData, consider adding a separate entity for the PNG, and adding a one-one relationship from your current entity to the new one.
Either of the above will avoid all the PNG data being loaded into memory when you load the array. You can then load/unload the PNGs (either from the file system or from the related entity) as needed.
In addition, consider using:
NSFetchedResultsController and/or
fetchBatchSize
These will help to avoid all the objects being loaded into memory.

Related

Handing a completion with CloudKit

I have an app that has species and photos. I am adding cloudKit to the app. I have a working solution, but now I need to add a completion handler as if the user downloads new species that include images, this takes some time (of course depending on how many images). However, the app allows the user to work during most of this process as it runs in the background.
The issue is if an image is not yet fully downloaded and the user select that species the app crashes, naturally.
I need to input a completion handler (or if someone has a better idea) that will allow me to use an activity indicator until the full process is completed. I found a few examples, but they don't take into account multiple download processes, like my images and thumbnails.
Here is my code. Note that I have removed some of the irrelevant code to reduce the amount shown.
func moveSpeciesFromCloud() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: RemoteRecords.speciesRecord, predicate: predicate)
CKDbase.share.privateDB.perform(query, inZoneWith: nil) {
records, error in
if error != nil {
print(error!.localizedDescription)
} else {
guard let records = records else { return }
for record in records {
DispatchQueue.main.async {
self.remoteVersion = record[RemoteSpecies.remoteSpeciesVersion] as! Int
self.remoteSpeciesID = record[RemoteSpecies.remoteSpeciesID] as! Int
self.speciesDetail = AppDelegate.getUserDatabase().getSpeciesDetails(self.remoteSpeciesID)
self.localVersion = self.speciesDetail.version
// being sure that remote version is newer than local version
if self.localVersion >= self.remoteVersion {
print("Species version not newer")
} else {
self.commonNameLabel = record[RemoteSpecies.remoteCommonName] as! String
self.speciesLabel = record[RemoteSpecies.remoteSpeciesName] as! String
self.genusLabel = record[RemoteSpecies.remoteGenusName] as! String
self.groupLabel = record[RemoteSpecies.remoteGroupName] as! String
self.subGroupLabel = record[RemoteSpecies.remoteSubGroupName] as! String
self.speciesDetailsLabel = record[RemoteSpecies.remoteSpeciesDetails] as! String
// Here I sync records to SQLite, but removed code as not relevant.
// now syncing Photos, Thumbs, Groups, SubGroups and Favorties
self.syncPhotosFromCloud(self.remoteSpeciesID)
self.syncThumbsFromCloud(self.remoteSpeciesID)
}
}
}
}
}
}
Here is the code for the Thumbnails (Images are same process)
func syncThumbsFromCloud(_ id: Int) {
let predicate = NSPredicate(format: "thumbSpeciesID = \(id)")
let query = CKQuery(recordType: RemoteRecords.thumbsRecord, predicate: predicate)
CKDbase.share.privateDB!.perform(query, inZoneWith: nil)
{
records, error in
if error != nil {
print(error!.localizedDescription)
} else {
guard let records = records else { return }
for record in records {
DispatchQueue.main.async {
self.thumbName = (record.object(forKey: RemoteThumbs.remoteThumbName) as? String)!
self.thumbID = (record.object(forKey: RemoteThumbs.remoteThumbID) as? Int)!
if let asset = record[RemoteThumbs.remoteThumbFile] as? CKAsset,
let data = try? Data(contentsOf: (asset.fileURL)),
let image = UIImage(data: data)
{
let filemgr = FileManager.default
let dirPaths = filemgr.urls(for: .documentDirectory,
in: .userDomainMask)
let fileURL = dirPaths[0].appendingPathComponent(self.thumbName)
if let renderedJPEGData = image.jpegData(compressionQuality: 1.0) {
try! renderedJPEGData.write(to: fileURL)
}
}
// syncing records to SQLite
AppDelegate.getUserDatabase().syncThumbsFromCloudToSQLite(id: self.thumbID, name: self.thumbName, speciesID: id)
}
}
}
}
}
I call it here on SyncVC:
#IBAction func syncCloudToDevice(_ sender: Any) {
let cloudKit = CloudKit()
cloudKit.moveSpeciesFromCloud()
cloudKit.moveFavoritessFromCloud()
}
If I missed a detail, please let me know.
Any assistance would be greatly appreciated.
I'm kind of concerned that both the previous answers don't help answer your question.. One is asking you to restructure your database and the other is asking you to become dependent on a third-party library.
My suggestion would be to make your perform(_:inZoneWith:) into a synchronous operation so that you can easily perform one after another. For example:
func performSynchronously(query: CKQuery) throws -> [CKRecord] {
var errorResult: Error?
var recordsResult: [CKRecord]?
let semaphore = DispatchSemaphore(value: 0)
CKDbase.share.privateDB!.perform(query, inZoneWith: nil) { records, error in
recordsResult = records
errorResult = error
semaphore.signal()
}
// Block this thread until `semaphore.signal()` occurs
semaphore.wait()
if let error = errorResult {
throw error
} else {
return recordsResult ?? []
}
}
Ensure that you call this from a background thread so as to not block your UI thread! For example:
// ... start your activity indicator
DispatchQueue(label: "background").async {
do {
let records1 = try performSynchronously(query: CKQuery...)
// parse records1
let records2 = try performSynchronously(query: CKQuery...)
// parse records2
DispatchQueue.main.async {
// stop your activity indicator
}
} catch let e {
// The error e occurred, handle it and stop the activity indicator
}
}
Of course, please just use this code as inspiration on how to use a semaphore to convert your asynchronous operations into synchronous ones. Here's a good article that discusses semaphores in depth.
Well, in general that sort of things are easy to do with RxSwift. You set activity indicator to on/off in .onSubscribe() and .onTerminated(), respectively, and you get the end result in subscriber/observer when it is ready. Specifically for CloudKit, you can use RxCloudKit library.
Is there a reason why you made the pictures a separate record type? I would just add the thumbnail and the full photo to the Species record type:
thumbnail = Bytes data type (1MB max)
photo = Asset data type (virtually limitless)
That way when you do your initial Species query, you will instantly have your thumbnail available, and then you can access the CKAsset like you are currently doing and it will download in the background. No second query needed which will make your code simpler.

Cannot read coredata from extension

I have an app that stores some information in coredata and reads them.
I'm writing a message extension of this application and I'd like to have this extension reading the same data but I always have empty response.
Here is the code I'm using in the main app:
context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
fetchImages(Date()){ (array, arrayData) in
for image in array{
imagesArray.insert(image, at:0)
}
}
I'm using exactly the same code in the extension but it does not read the data.
What I'm wondering about is that I'm not using the appGroupIdentifier anywhere in the code.
How can I do to achieve that?
Thanks.
Here is the code of fetchImages function:
func fetchImages(_ predicate:Date, completion:(_ array:[Image], _ arrayData:NSArray)->()){
var arrData = [NSManagedObject]()
var existingImages = [Image]()
let request :NSFetchrequest<NSFetchrequestResult> = NSFetchrequest(entityName: "Photo")
do {
let results = try context?.fetch(request)
var myImage = Image()
if ((results?.count) != nil) {
for result in results! {
myImage.imageUrl = (resultat as! NSManagedObject).value(forKey:"url") as! String
myImage.imageFileName = (resultat as! NSManagedObject).value(forKey:"imageFileName") as! String
existingImages.append(myImage)
arrData.append(result as! NSManagedObject)
}
} else{
print ("No photo.")
}
completion(existingImages, arrData as NSArray)
} catch{
print ("Error during CoreData request")
}
}
Turning on app groups is the first step, but now you need to tell Core Data to use the app group.
First you get the location of the shared group container, from FileManager. Use containerURL(forSecurityApplicationGroupIdentifier:) to get a file URL for the directory.
You can use that URL without changes if you want. It's probably a good idea to create a subdirectory in it to hold your Core Data files. If you do that, add a directory name to the URL with the appendingPathComponent() method on URL. Then use FileManager to create the new directory with the createDirectory(at:withIntermediateDirectories:attributes:) method.
Now that you have a shared directory to use, tell NSPersistentContainer to put its files there. You do that by using NSPersistentStoreDescription. The initializer can take a URL that tells it where to store its data.
Your code will be something approximating this:
let directory: URL = // URL for your shared directory as described above
let containerName: String = // Your persistent container name
let persistentContainer = NSPersistentContainer(name: containerName)
let persistentStoreDirectoryUrl = directory.appendingPathComponent(containerName)
guard let _ = try? FileManager.default.createDirectory(at: persistentStoreDirectoryUrl, withIntermediateDirectories: true, attributes: nil) else {
fatalError()
}
let persistentStoreUrl = persistentStoreDirectoryUrl.appendingPathComponent("\(containerName).sqlite")
let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreUrl)
persistentContainer.persistentStoreDescriptions = [ persistentStoreDescription ]
persistentContainer.loadPersistentStores {
...
}

Swift 3 - Saving images to Core Data

For some reason I can't figure out how to save images to core data and fetch them again. I have a feeling it's something about my types but have a look:
I get my data from an api call to my server. It returns a base64 string.
Here is where I get the data:
updateAccessTokenOnly(newAccessToken: aToken!)
saveImageToDB(brandName: imageBrandName, image: data! )
Here I save it to my DB:
func saveImageToDB(brandName: String, image: Data) {
dropImages(){tableDropped in
let managedContext = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CoffeeShopImage", in: managedContext)!
let CSI = NSManagedObject(entity: entity, insertInto: managedContext)
CSI.setValue(image, forKey: "image")
CSI.setValue(brandName, forKey: "brandName")
do {
try managedContext.save()
print("saved!")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
then to fetch it:
func getImageFromDB(callback: #escaping (_ image: UIImage)-> ()) {
let fetchRequest: NSFetchRequest<NSManagedObject> = NSFetchRequest(entityName: "CoffeeShopImage")
do {
let searchResults = try getContext().fetch(fetchRequest)
for images in searchResults {
print("vi når her ned i get image")
if (images.value(forKey: "brandName")! as! String == "Baresso"){
print(images.value(forKey: "brandName")! as! String)
let image: Data = images.value(forKey: "image")! as! Data
let decodedimage = UIImage(data: image)
callback(decodedimage!)
}
}
} catch {
print("Error with request: \(error)")
}
}
Full error log:
https://docs.google.com/document/d/1gSXE64Sxtzo81eBSjv4bnBjBnnmG4MX2tuvNtnuJDIM/edit?usp=sharing
Hope someone can help. Thanks in advance!
UPDATED
So I uninstalled the app and then the code above worked. However the pictures come out blue? (yes I've checked that the pictures sent from the database are correct).
Any solution?
replace
let image: Data = images.value(forKey: "image")! as! Data
let dataDecoded : Data = Data(base64Encoded: image, options: [])!
let decodedimage = UIImage(data: dataDecoded)
with
let image: Data = images.value(forKey: "image")! as! Data
let decodedimage = UIImage(data: image)
Base64 is a way to to convert data to a string. There is no reason to use it here. You already have the data from the database you just want to convert it to a UIImage.
also change
let image = data?.base64EncodedData()
saveImageToDB(brandName: imageBrandName, image: image!)
to
saveImageToDB(brandName: imageBrandName, image: data!)
base64EncodedData is turning the data from image data into a utf-8 encoded based64encoded string. There is no reason for that.
You should get the base64 encoded string from server, convert it to data and then you never need base64 again. Read and write data to your database, and after you read it convert it to a UIImage. Base64 is an encoding method to transfer data. If you are not talking to the server there is no reason to use base64.
After the suggested corrections from Jon Rose all I needed was to add
.withRenderingMode(.alwaysOriginal)
to where I was showing my picture and the code worked.
Saving Image:
guard let managedObjectContext = managedObjectContext else { return }
// Create User
let user = User(context: managedObjectContext)
// Configure User
user.name = "name"
user.about = "about"
user.address = "Address"
user.age = 30
if let img = UIImage(named: "dog.png") {
let data = img.pngData() as NSData?
user.image = data
}
Fetching Image:
// Create Fetch Request
let fetchRequest: NSFetchRequest<User> = User.fetchRequest()
// Configure Fetch Request
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
let users = try! managedContext.fetch(fetchRequest)
let user: User = users.first as! User
if let imageData = user?.image {
imgView.image = UIImage(data: imageData as Data)
}

Swift - Core Data Relationship - Album Style

I have been working on establishing a working Core Data relationship for days now. I have made a bit of progress, but nothing substantial, so I am hoping someone much smarter than I would be able to tackle this and help me out.
For a bit of context, I am essentially creating a photo album app. Inside the album, there would be photos, with a caption on each photo. I have structured an entity "ScannedItem" that has attributes "savedTitle", "savedText", and "savedImage".
savedTitle = The title of the image.
savedText = The caption of the image.
savedImage = The image itself.
I am not sure how I would go about adding a "ScannedItem" (the entity holding the previous attributes) to my "Album" entity.
For more information, a "ScannedItem", and all of its attributes, may only be in one album, so I believe this would be a 'To-One' relationship.
I have included images of how I have structured my entities and relationships here.
Below is my code for saving the "Scanned Item Entity".
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let saveData = NSEntityDescription.insertNewObjectForEntityForName("ScannedItem", inManagedObjectContext: context)
let imageRep = UIImagePNGRepresentation(image)!
saveData.setValue(textTitle, forKey: "savedTitle")
saveData.setValue(scannedText, forKey: "savedText")
saveData.setValue(imageRep, forKey: "savedImage")
do{
try context.save()
}catch{
print("Failed")
}
How would I go about saving that ScannedItem to an Album? I have been struggling with the code for days and it's nearly driven me insane. Any help would be greatly appreciated. It's time to ask the experts.
UPDATE
do{
let request = NSFetchRequest(entityName: "ScannedItem")
let results = try context.executeFetchRequest(request)
let saveAlbum = NSEntityDescription.insertNewObjectForEntityForName("Album", inManagedObjectContext: context)
saveAlbum.setValue(results, forKey: "album")
let albumRequest = NSFetchRequest(entityName: "Album")
let albumResults = try context.executeFetchRequest(albumRequest)
if albumResults.count > 0{
for item in albumResults as! [NSManagedObject]{
let albumPrint = item.valueForKey("album")
print("Album Test: " + (albumPrint! as! String))
}
}
if results.count > 0{
for item in results as! [NSManagedObject]{
let titleSaved = item.valueForKey("savedTitle")
let textSaved = item.valueForKey("savedText")
//let imageSaved = item.valueForKey("savedImage")
//let titleSaved = item.valueForKey("savedTitle")
print("Title" + (titleSaved! as! String))
print("Text" + (textSaved! as! String))
//print(imageSaved!)
}
}
}catch{
print("Failed")
}
Now I am receiving an error stating that "the entity Album is not key value coding-compliant for the key "album". I do not know what to do, I have been toying with it for hours.
On Album, change scanItem to scanItems and make it to-many because you can have many items in an album.
Change albumRelationship to album and set it to to-one.
You need to know or fetch the album you want to add to and then just set:
saveData.setValue(album, forKey: "album")
Core data will automatically set the other end of the relationship for you.

Swift 2.0: Could not cast value of type '__NSArrayM' (0x10a9348d8) to 'NSDictionary' (0x10a934d60)

I have been reading some of the responses to questions with similar problems but I just can't figure it out...
I have PostService that does a JSON POST request and fetches the data from a MySQL database. Everything was working before I did the conversion to Swift 2.0 and now it's giving me gears. (Code comes from Skip Wilson's Youtube series - Swift: Using External Databases and API's)
It gives the above error in the output and stops and highlights this line -
"let response = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary"
var settings:Settings!
init() {
self.settings = Settings()
}
let userLoginEmail = "admin#email.co.za";
let userLoginPassword = "1234";
func getPosts(callback:(NSDictionary) -> ()) {
request(settings.viewPosts, callback: callback)
}
func request(url:String, callback:(NSDictionary) -> ()) {
let myURL = NSURL(string: url)
let requested = NSMutableURLRequest(URL:myURL!);
requested.HTTPMethod = "POST";
let postString = "email=\(userLoginEmail)&password=\(userLoginPassword)";
print("email=\(userLoginEmail)&password=\(userLoginPassword)")
requested.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(requested) {
(data, response, error) in
let response = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
callback(response)
}
task.resume()
}
Here's my JSON post... With my limited knowledge and reading up on json.org, it looks like an object with an array inside it containing a bunch of objects (A dictionary?) The format of all of this did not change and my app would fetch the data from the database and display it correctly before the conversion..
{"posts":[{"Post":{"Id":"5","idno":"4","product":"Aspen Simvastatin","quantity":"30","due_date":"2015-04-11","last_repeat":"2015-04-10","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"2","totalrepeat":"6","active_ingredient":"Simvastatin","strength":"20mg","manufacturer":"Aspen Pharmacare","dosage_form":"Tabs","directions":"Take one tablet daily","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"6","idno":"4","product":"Mybulen","quantity":"45","due_date":"2015-04-11","last_repeat":"2015-04-10","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"3","totalrepeat":"6","active_ingredient":"Codeine Phosphate;Ibuprofen;Paracetamol","strength":"10mg;200mg;250mg","manufacturer":"Aspen Pharmacare","dosage_form":"Tabs","directions":"Take one or two tablets four times a day after meals","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"7","idno":"4","product":"Ecotrin XL","quantity":"30","due_date":"2015-04-11","last_repeat":"2015-03-11","doctor":"Dr. Jim Jones","store":"Central","currentrepeat":"4","totalrepeat":"6","active_ingredient":"Aspirin","strength":"81mg","manufacturer":"Litha Pharma","dosage_form":"Tabs","directions":"Take one tablet in the morning","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}},{"Post":{"Id":"8","idno":"4","product":"Lorien","quantity":"28","due_date":"2015-04-11","last_repeat":"2015-03-11","doctor":"Dr. J. Eckel","store":"Central","currentrepeat":"4","totalrepeat":"6","active_ingredient":"Fluoxetine HCl","strength":"20mg","manufacturer":"Aspen Pharmacare","dosage_form":"Caps","directions":"Take one capsule in the morning","repeatflag":"0","repeattimestamp":"2015-08-17 20:38:13"}}]}
I would be extremely grateful for any help on this.
In my masterViewController's viewDidLoad(), I have this code which process the information fetched...
service = PostService()
service.getPosts {
(response) in
self.loadPosts(response["posts"]! as! NSArray)
}
}
func loadPosts(posts:NSArray) {
for post in posts {
let post = post["Post"]! as! NSDictionary
let Id = Int((post["Id"]! as! String))!
let idno = Int((post["idno"]! as! String))!
let product = post["product"]! as! String
let quantity = Int((post["quantity"]! as! String))!
let doctor = post["doctor"]! as! String
let store = post["store"]! as! String
let currentrepeat = Int((post["currentrepeat"]! as! String))!
let totalrepeat = Int((post["totalrepeat"]! as! String))!
let active_ingredient = post["active_ingredient"]! as! String
let strength = post["strength"]! as! String
let manufacturer = post["manufacturer"]! as! String
let dosage_form = post["dosage_form"]! as! String
let directions = post["directions"]! as! String
let postObj = Post(Id: Id, idno: idno, product: product, quantity: quantity, doctor: doctor, store: store, currentrepeat: currentrepeat, totalrepeat: totalrepeat, active_ingredient: active_ingredient, strength: strength, manufacturer: manufacturer, dosage_form: dosage_form, directions: directions)
postsCollection.append(postObj)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
You are telling NSJSONSerialization that you are absolutely sure that the JSON can be parsed and that you want your app to crash if it doesn't. (That's the try! ). Well, there are plenty of situations where you are asking for JSON and you are getting html back, so your users won't be happy with that, let's say if they use your app in a hotel or at the nearest starbucks.
Next, you are telling NSJSONSerialization that you are absolutely sure that the JSON contains a dictionary, and that you want your app to crash if it doesn't (as! NSDictionary). Guess what, you were given an array. You better read the documentation for your API, and check what you are given here.
BTW. I don't care what you are posting what JSON you are supposedly getting - I know that you received an array. Don't believe it? First rule of debugging: What you know is wrong.

Resources