CoreData fetch - memory leaks - ios

When I try to catch data from CoreData I get a memory leak.
My function for fetching is:
func fetchTableBodyData<T: TableBody, C: NSManagedObject>(from year: Int?, coreDataObject: C.Type, returnType: T.Type) -> [T] {
guard let name = C.entity().name else { return [] }
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: name)
fetchRequest.returnsObjectsAsFaults = false
if let year = year {
fetchRequest.predicate = NSPredicate(format: "date >= %# AND date <= %#", Date().startOfYear(year: year) as CVarArg, Date().endOfYear(year: year) as CVarArg)
}
do {
let fetched = try context.fetch(fetchRequest) // Memory leak is here
guard let casted = fetched as? [C] else { return [] }
return T.parseFromCoreData(from: casted)
} catch let er {
print(er.localizedDescription)
return []
}
}
Context is define:
private var context: NSManagedObjectContext {
CoreDataStack.managedObjectContext
}
I have another memory leak in parsing from CoreDataObject to Struct:
static func getYearlyDataFrom(object: YearObject) -> YearlyData? {
guard let objects = object.tableObjects else { return nil } // Memory leak
var yearlyData: YearlyData = YearlyData()
yearlyData.year = Int(object.year)
var tableDatas: [TableData] = []
for tableObject in objects { // Memory leak
guard let tableObj = tableObject as? TableObject else { continue }
guard let id = tableObj.id, let nameOfList = tableObj.nameOfList, let range = tableObj.nameOfList, let lastUpdate = tableObj.lastUpdate else { continue } // Memory leak
tableDatas.append(TableData(id: id, nameOfList: nameOfList, range: range, lastUpdate: lastUpdate))
}
var playlists: [Playlist] = []
for playlistObject in object.playlistObjects ?? NSSet() { // Memory leak
guard let playlistObj = playlistObject as? PlaylistObject else { continue }
guard let playlistId = playlistObj.playlistId, let date = playlistObj.date, let name = playlistObj.name, let imageURL = playlistObj.imageURL else { continue } // Memory leak
var image: UIImage? = nil
if let imageData = playlistObj.image {
image = UIImage(data: imageData)
}
playlists.append(Playlist(playlistId: playlistId, name: name, position: Int(playlistObj.position), date: date, imageURL: imageURL, image: image))
}
yearlyData.playlists = playlists
yearlyData.tables = tableDatas
return yearlyData
}
Did anybody have the same problem?

The context object is defined as a Computed-Property it should be a State-Property instead and shouldn't be computed each time it's called.
private var context = CoreDataStack.managedObjectContext

Related

How can I guarantee that a Swift closure (referencing Firebase) fully executes before I move on?

I have multiple query snapshots with closures and some of them are using the data supplied by the query that came before it.
I have read up on GCD and I've tried to implement a DispatchGroup with .enter() and .leave() but I am apparently doing something wrong.
If somebody can help me by laying out exactly how to force one task to be performed before another, that would solve my problem.
If you can't tell, I am somewhat new to this so any help is greatly appreciated.
//MARK: Get all userActivities with distance(All Code)
static func getAllChallengesWithDistanceAllCode(activity:String, completion: #escaping ([Challenge]) -> Void) {
let db = Firestore.firestore()
let currUserID = Auth.auth().currentUser!.uid
var currUserName:String?
var distanceSetting:Int?
var senderAverage:Double?
var senderBestScore:Int?
var senderMatchesPlayed:Double?
var senderMatchesWon:Double?
var senderWinPercentage:Double?
var validUserActivities = [Challenge]()
db.collection("users").document(currUserID).getDocument { (snapshot, error) in
if error != nil || snapshot == nil {
return
}
currUserName = snapshot?.data()!["userName"] as? String
distanceSetting = snapshot?.data()!["distanceSetting"] as? Int
}
db.collection("userActivities").document(String(currUserID + activity)).getDocument { (snapshot, error) in
//check for error
//MARK: changed snapshot to shapshot!.data() below (possible debug tip)
if error != nil {
//is error or no data..??
return
}
if snapshot!.data() == nil {
return
}
//get profile from data proprty of snapshot
if let uActivity = snapshot!.data() {
senderBestScore = uActivity["bestScore"] as? Int
senderMatchesPlayed = uActivity["played"] as? Double
senderMatchesWon = uActivity["wins"] as? Double
senderAverage = uActivity["averageScore"] as? Double
senderWinPercentage = round((senderMatchesWon! / senderMatchesPlayed!) * 1000) / 10
}
}
if distanceSetting != nil {
db.collection("userActivities").whereField("activity", isEqualTo: activity).getDocuments { (snapshot, error) in
if error != nil {
print("something went wrong... \(String(describing: error?.localizedDescription))")
return
}
if snapshot == nil || snapshot?.documents.count == 0 {
print("something went wrong... \(String(describing: error?.localizedDescription))")
return
}
if snapshot != nil && error == nil {
let uActivitiesData = snapshot!.documents
for uActivity in uActivitiesData {
let userID = uActivity["userID"] as! String
UserService.determineDistance(otherUserID: userID) { (determinedDistance) in
if determinedDistance! <= distanceSetting! && userID != currUserID {
var x = Challenge()
//Sender
x.senderUserID = currUserID
x.senderUserName = currUserName
x.senderAverage = senderAverage
x.senderBestScore = senderBestScore
x.senderMatchesPlayed = senderMatchesPlayed
x.senderMatchesWon = senderMatchesWon
x.senderWinPercentage = senderWinPercentage
//Receiver
x.receiverUserID = userID
x.receiverUserName = uActivity["userName"] as? String
x.receiverAverage = uActivity["averageScore"] as? Double
x.receiverBestScore = uActivity["bestScore"] as? Int
if (uActivity["played"] as! Double) < 1 || (uActivity["played"] as? Double) == nil {
x.receiverMatchesPlayed = 0
x.receiverMatchesWon = 0
x.receiverWinPercentage = 0
} else {
x.receiverMatchesPlayed = uActivity["played"] as? Double
x.receiverMatchesWon = uActivity["wins"] as? Double
x.receiverWinPercentage = ((uActivity["wins"] as! Double) / (uActivity["played"] as! Double) * 1000).rounded() / 10
}
if uActivity["playStyle"] as? String == nil {
x.receiverPlayStyle = "~No PlayStyle~"
} else {
x.receiverPlayStyle = uActivity["playStyle"] as! String
}
x.activity = activity
//append to array
validUserActivities.append(x)
}
}
}
completion(validUserActivities)
}
}
}
}
try this
static func getAllChallengesWithDistanceAllCode(activity:String, completion: #escaping ([Challenge]) -> Void) {
let db = Firestore.firestore()
let currUserID = Auth.auth().currentUser!.uid
var currUserName:String?
var distanceSetting:Int?
var senderAverage:Double?
var senderBestScore:Int?
var senderMatchesPlayed:Double?
var senderMatchesWon:Double?
var senderWinPercentage:Double?
var validUserActivities = [Challenge]()
db.collection("users").document(currUserID).getDocument { (snapshot, error) in
if error != nil || snapshot == nil {
return
}
currUserName = snapshot?.data()!["userName"] as? String
distanceSetting = snapshot?.data()!["distanceSetting"] as? Int
db.collection("userActivities").document(String(currUserID + activity)).getDocument { (snapshot, error) in
//check for error
//MARK: changed snapshot to shapshot!.data() below (possible debug tip)
if error != nil {
//is error or no data..??
return
}
if snapshot!.data() == nil {
return
}
//get profile from data proprty of snapshot
if let uActivity = snapshot!.data() {
senderBestScore = uActivity["bestScore"] as? Int
senderMatchesPlayed = uActivity["played"] as? Double
senderMatchesWon = uActivity["wins"] as? Double
senderAverage = uActivity["averageScore"] as? Double
senderWinPercentage = round((senderMatchesWon! / senderMatchesPlayed!) * 1000) / 10
if snapshot != nil && error == nil {
let uActivitiesData = snapshot!.documents
for uActivity in uActivitiesData {
let userID = uActivity["userID"] as! String
UserService.determineDistance(otherUserID: userID) { (determinedDistance) in
if determinedDistance! <= distanceSetting! && userID != currUserID {
var x = Challenge()
//Sender
x.senderUserID = currUserID
x.senderUserName = currUserName
x.senderAverage = senderAverage
x.senderBestScore = senderBestScore
x.senderMatchesPlayed = senderMatchesPlayed
x.senderMatchesWon = senderMatchesWon
x.senderWinPercentage = senderWinPercentage
//Receiver
x.receiverUserID = userID
x.receiverUserName = uActivity["userName"] as? String
x.receiverAverage = uActivity["averageScore"] as? Double
x.receiverBestScore = uActivity["bestScore"] as? Int
if (uActivity["played"] as! Double) < 1 || (uActivity["played"] as? Double) == nil {
x.receiverMatchesPlayed = 0
x.receiverMatchesWon = 0
x.receiverWinPercentage = 0
} else {
x.receiverMatchesPlayed = uActivity["played"] as? Double
x.receiverMatchesWon = uActivity["wins"] as? Double
x.receiverWinPercentage = ((uActivity["wins"] as! Double) / (uActivity["played"] as! Double) * 1000).rounded() / 10
}
if uActivity["playStyle"] as? String == nil {
x.receiverPlayStyle = "~No PlayStyle~"
} else {
x.receiverPlayStyle = uActivity["playStyle"] as! String
}
x.activity = activity
//append to array
validUserActivities.append(x)
}
}
}
completion(validUserActivities)
}
}
}
}
}
}
}
I solved this by doing the following:
Add a constant before the for-in loop counting the total number of query results
let documentCount = snapshot?.documents.count
Add a counter before the for-in loop starting at 0
var runCounter = 0
Increment the counter with each iteration at the beginning of the for-in loop
runCounter += 1
Add code to the end of the for-in loop to call the completion handler to return the results
if runCounter == documentCount {
completion(validChallenges)
}

My weather data is not showing up in my view controller

I can't figure out why my view controller is not showing the data, even though I can see it in the output window.
Output:
Muḩāfaz̧at Al Jīzah
Clear
88.0
my code:
override func viewDidLoad() {
super.viewDidLoad()
loadCurrentWeather = currentWeatherData()
loadCurrentWeather.downloadWeatherData {
//setting uo UI to download data
self.updateTodayUI()
}
}
func updateTodayUI() {
locationLabel.text = loadCurrentWeather.cityName
weatherTypeLabel.text = loadCurrentWeather.weatherType
currentTempLabel.text = "\(loadCurrentWeather.currentTemp)"
weatherTypeImage.image = UIImage(named: loadCurrentWeather.weatherType)
}
My view controller in Xcode:
My view controller on iphone:
currentweatherData the code where I'm downloading the data form.
import UIKit
import Alamofire
class currentWeatherData {
var cityNameone: String!
var dateone: String!
var weatherTypeone: String!
var currentTempone: Double!
var cityName: String {
if cityNameone == nil {
cityNameone = ""
}
return cityNameone
}
var date: String {
if dateone == nil {
dateone = ""
}
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
let currentDate = dateFormatter.string(from: Date())
self.dateone = "Today, \(currentDate)"
return dateone
}
var weatherType: String{
if weatherTypeone == nil{
weatherTypeone = ""
}
return weatherTypeone
}
var currentTemp: Double {
if currentTempone == nil {
currentTempone = 0.0
}
return currentTempone
}
func downloadWeatherData(completed: DownloadComplete){
// to tell alamofire where to download the data
let weatherURL = URL (string: currentWeatherURL)!
Alamofire.request(weatherURL).responseJSON{ response in
let result = response.result
if let dictionary = result.value as? Dictionary<String, AnyObject>{
if let name = dictionary["name"] as? String {
self.cityNameone = name.capitalized
print(self.cityNameone ?? "No city name")
}
if let weather = dictionary["weather"] as? [Dictionary<String, AnyObject>]{
if let main = weather[0]["main"] as? String {
self.weatherTypeone = main.capitalized
print(self.weatherTypeone ?? "No weather type")
}
}
if let main = dictionary["main"] as? Dictionary<String, AnyObject> {
if let currentTemperature = main["temp"] as? Double {
let kelvintoFarenheit = (currentTemperature * (9/5) - 459.67)
let totalKelvinToFarenheit = Double(round(10 * kelvintoFarenheit/10))
self.currentTempone = totalKelvinToFarenheit
print(self.currentTempone ?? .nan)
}
}
}
}
completed()
}
}
Is problem with my code or my view controller? Is it something wrong with my constraints?
I can't seem to figure it out.
You are calling completed too early - before the JSON response arrives. You have to call it inside the closure of the responseJSON call instead:
Alamofire.request(weatherURL).responseJSON { response in
let result = response.result
// ...
completed()
}
I cannot see all of your code to troubleshoot, but you may have a concurrency issue. Try putting the call to updateTodayUI inside of viewDidLoad(_:) inside of an async block like this:
DispatchQueue.main.async {
updateTodayUI()
}
You can find more information on dispatch queues and concurrency in the documentation.

Taking so much time when i insert 10k data in coredata ?

In my project concept i need a insert 10k data when user open the application. I integrate core data for storing data but its take 1 to 5 minutes.
Here is my code ?
func inserChatMessage(_ message: String, chatId: String, onCompletion completionHandler:((_ message: ChatMessage) -> Void)?) {
var objMessage: ChatMessage? = nil
if let obj = ChatMessage.createEntity() {
objMessage = obj
}
objMessage?.messageId = ""
objMessage?.message = message
objMessage?.chatId = chatId
objMessage?.senderId = AIUser.current.userId
objMessage?.createAt = Date()
objMessage?.updateAt = Date()
let cManager = CoreDataManager.sharedManager
cManager.saveContext()
if let completionHandler = completionHandler, let objMessage = objMessage {
completionHandler(objMessage)
}
}
Coredata is not a threadsafe. And as per your requirement you need to save large amount of data on app launch. So If you will save those data using main thread, your app will get hanged. So Instead on saving large amount of data on main thread you can save those data on background thread. Coredata is supporting multi threading concept by providing parent child context concept.
I have done same in one of my project and its working fine. Here i have attached code.
func savePersonalMessagesOnBackGroundThread(arrMessages:NSArray,responseData:#escaping () -> Void)
{
print(arrMessages)
let temporaryChatContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
temporaryChatContext.parent = self.managedObjectContext
temporaryChatContext.perform({() -> Void in
for i in 0..<arrMessages.count
{
let msgDic = arrMessages[i] as! NSDictionary
_ = self.saveMessageInLocalDB(dictMessage: msgDic, managedObjectContext: temporaryChatContext, onBackground: true)
if i == arrMessages.count - 1 {
do {
try temporaryChatContext.save()
runOnMainThreadWithoutDeadlock {
DLog(message: "Thred \(Thread.isMainThread)")
if(self.managedObjectContext.hasChanges)
{
self.saveContext()
responseData()
}
}
}
catch {
print(error)
}
}
}
})
}
func saveMessageInLocalDB(dictMessage:NSDictionary, managedObjectContext:NSManagedObjectContext,onBackground:Bool) -> Chat
{
var chatObj : Chat! = Chat()
var receiveId: Int32!
var flag:Bool = false
print(dictMessage)
// let predicate = NSPredicate(format:"uniqueId == %# and senderId = %d and receiverId = %d","\(dictMessage.value(forKey:keyuniqueId)!)",Int32(dictMessage.value(forKey:keysenderId) as! Int64),Int32(dictMessage.value(forKey:keyreceiverId) as! Int64))
let predicate = NSPredicate(format:"uniqueId == %#","\(dictMessage.value(forKey:keyuniqueId)!)")
let objContext = managedObjectContext
let fetchRequest = NSFetchRequest<Chat>(entityName: ENTITY_CHAT)
let disentity: NSEntityDescription = NSEntityDescription.entity(forEntityName: ENTITY_CHAT, in: objContext)!
fetchRequest.predicate = predicate
fetchRequest.entity = disentity
do{
let results = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Chat]
if(results.count > 0)
{
chatObj = results[0]
chatObj.messageId = Int32(dictMessage.value(forKey:keymessageId) as! Int64)
chatObj.dateOnly = dictMessage.value(forKey:keydateOnly) as! String?
}
else{
//receiveId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
//self.createNewChatObject(dictMessage: dictMessage, receiverId: receiveId, managedObjectContext: managedObjectContext)
chatObj = NSEntityDescription.insertNewObject(forEntityName:ENTITY_CHAT,into: managedObjectContext) as? Chat
if dictMessage[keymessageId] != nil {
chatObj.messageId = dictMessage.value(forKey:keymessageId) as! Int32
}
if(chatObj.message?.length != 0)
{
chatObj.message = dictMessage.value(forKey:keychatMessage) as? String
}
chatObj.messageType = Int32(dictMessage.value(forKey:keymessageType) as! Int64)
chatObj.senderId = Int32(dictMessage.value(forKey:keysenderId) as! Int64)
if(chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!))
{
let contactObj = self.getContactByContactId(contactId: Int32(dictMessage.value(forKey:keysenderId) as! Int64))
if(contactObj == nil)
{
_ = self.saveUnknownUserASContact(msgDict: dictMessage as! Dictionary<String, Any>)
}
}
chatObj.receiverId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
chatObj.uniqueId = dictMessage.value(forKey:keyuniqueId) as? String
chatObj.mediaName = dictMessage.value(forKey:keymediaName) as? String
print(NSDate())
if dictMessage[keycreatedDate] != nil {
let utcDate : NSDate = DateFormater.getUTCDateFromUTCString(givenDate: dictMessage.value(forKey:keycreatedDate) as! String)
chatObj.createdDate = utcDate
chatObj.updatedDate = utcDate
}
else
{
chatObj.createdDate = NSDate()
chatObj.updatedDate = NSDate()
}
if(chatObj.senderId == Int32((APP_DELEGATE.loggedInUser?.id)!))
{
chatObj.chatUser = chatObj.receiverId
}
else
{
chatObj.chatUser = chatObj.senderId
}
if dictMessage[keystatus] != nil {
chatObj.status = Bool((dictMessage.value(forKey:keystatus) as! Int64) as NSNumber)
}
switch Int(chatObj.messageType)
{
case MSG_TYPE.MSG_Text.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_TEXT_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_TEXT_SENDER.rawValue))
case MSG_TYPE.MSG_Image.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_IMAGE_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_IMAGE_SENDER.rawValue))
self.saveMedia(chatObj: chatObj)
default :
// chatObj.cellType = Int32(CELL_TYPE.CELL_LOAD_MORE.rawValue)
break
}
}
// deviceMake = 1;
if(!onBackground)
{
self.saveContext()
}
}
catch
{
}
return chatObj
}
Using the basic example below I can insert 10k records very quickly. The main thing that has changed here compared to your code is that I loop through and create the entities and then call save() at the very end. So you are performing one write call to the db instead of 10k. You are writing more information in that one call but it is still much quicker.
import UIKit
import CoreData
class ViewController: UIViewController {
lazy var sharedContext: NSManagedObjectContext? = {
return (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
}()
override func viewDidLoad() {
super.viewDidLoad()
if let messages = getMessages(), messages.count > 0 {
printMessages(messages: messages)
} else {
loadChatMessages()
printMessages(messages: getMessages())
}
}
private func printMessages(messages: [Message]?) {
guard let messages = messages else { return }
for message in messages {
print(message.message)
}
}
private func getMessages() -> [Message]? {
let request = NSFetchRequest<Message>(entityName: "Message")
let messages = try? self.sharedContext?.fetch(request)
return messages ?? nil
}
private func loadChatMessages() {
var counter = 1
while counter <= 10000 {
let message = Message(entity: Message.entity(), insertInto: self.sharedContext)
message.message = "This is message number \(counter)"
message.read = false
message.timestamp = Date()
counter = counter + 1
}
do {
try self.sharedContext?.save()
} catch {
print(error)
}
}
}
As mentioned in my comment above, you can improve this further by doing it in the background (see Twinkle's answer for an example of how to switch to a background thread), you can also provide a pre-filled (pre-seeded) core data database that already contains the 10k records with your app. so it doesn't need to load this on initial load.
To do this you would fill the db locally on your dev machine and then copy it to the project bundle. On initial load you can check to see if your db filename exists in the documents folder or not. If it doesn't copy it over from the bundle and then use that DB for core data.

SQLite db file is only read at the startup of the app

I have an issue with my code that I hope maybe som of you can help me out with.
I have a "kindoff" dictionary app where the user can search for abbreviations.
The datamodel is build using an SQLite database. In my first version the data.db file was only stored locally, but since I have to make a lot of data updates I don't wanna update en whole app every time. So I have made a version that gets the data.db file from a server instead.
My problem is that the user have to quit og restart the app to get the updated version. Somehow, even if the new file is copied to the directory the sqldb class does'nt read the new file. Any idea how to make som code the gets the dbclass to read the new file, while the app is running?
Here is the code the copies the new file to the directory
func executeDownloadTask(){
if Reachability.isConnectedToNetwork() == true{
print("just Checking")
let backgroundSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("backgroundSession")
backgroundSession = NSURLSession(configuration: backgroundSessionConfiguration, delegate: self,delegateQueue: NSOperationQueue.mainQueue())
let the_path:String = UpdateDbFile.findPathInDocDir("data.db");// get the path for the data.db file
UpdateDbFile.deleteFilewithPath(the_path) // delete the existing file
startDownload() // download the updated dbfile
}
}
Heres the SQLiteDb class that I use.
//
// SQLiteDB.swift
// TasksGalore
//
// Created by Fahim Farook on 12/6/14.
// Copyright (c) 2014 RookSoft Pte. Ltd. All rights reserved.
//
import Foundation
#if os(iOS)
import UIKit
#else
import AppKit
#endif
let SQLITE_DATE = SQLITE_NULL + 1
internal let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
// MARK:- SQLColumn Class - Column Definition
class SQLColumn {
var value:AnyObject? = nil
var type:CInt = -1
init(value:AnyObject, type:CInt) {
// println("SQLiteDB - Initialize column with type: \(type), value: \(value)")
self.value = value
self.type = type
}
// New conversion functions
func asString()->String {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return "\(value!)"
case SQLITE_TEXT:
return value as! String
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str as String
} else {
return ""
}
case SQLITE_NULL:
return ""
case SQLITE_DATE:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.stringFromDate(value as! NSDate)
default:
return ""
}
}
func asInt()->Int {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return value as! Int
case SQLITE_TEXT:
let str = value as! NSString
return str.integerValue
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str.integerValue
} else {
return 0
}
case SQLITE_NULL:
return 0
case SQLITE_DATE:
return Int((value as! NSDate).timeIntervalSince1970)
default:
return 0
}
}
func asDouble()->Double {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
return value as! Double
case SQLITE_TEXT:
let str = value as! NSString
return str.doubleValue
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
return str.doubleValue
} else {
return 0.0
}
case SQLITE_NULL:
return 0.0
case SQLITE_DATE:
return (value as! NSDate).timeIntervalSince1970
default:
return 0.0
}
}
func asData()->NSData? {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
let str = "\(value)" as NSString
return str.dataUsingEncoding(NSUTF8StringEncoding)
case SQLITE_TEXT:
let str = value as! NSString
return str.dataUsingEncoding(NSUTF8StringEncoding)
case SQLITE_BLOB:
return value as? NSData
case SQLITE_NULL:
return nil
case SQLITE_DATE:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
let str = fmt.stringFromDate(value as! NSDate)
return str.dataUsingEncoding(NSUTF8StringEncoding)
default:
return nil
}
}
func asDate()->NSDate? {
switch (type) {
case SQLITE_INTEGER, SQLITE_FLOAT:
let tm = value as! Double
return NSDate(timeIntervalSince1970:tm)
case SQLITE_TEXT:
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.dateFromString(value as! String)
case SQLITE_BLOB:
if let str = NSString(data:value as! NSData, encoding:NSUTF8StringEncoding) {
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
return fmt.dateFromString(str as String)
} else {
return nil
}
case SQLITE_NULL:
return nil
case SQLITE_DATE:
return value as? NSDate
default:
return nil
}
}
func description()->String {
return "Type: \(type), Value: \(value)"
}
}
// MARK:- SQLRow Class - Row Definition
class SQLRow {
var data = Dictionary<String, SQLColumn>()
subscript(key: String) -> SQLColumn? {
get {
return data[key]
}
set(newVal) {
data[key] = newVal
}
}
func description()->String {
return data.description
}
}
// MARK:- SQLiteDB Class - Does all the work
class SQLiteDB{
let DB_NAME = "data.db"
let QUEUE_LABLE = "SQLiteDB"
private var db:COpaquePointer = nil
private var queue:dispatch_queue_t
private var fmt = NSDateFormatter()
private var GROUP = ""
var newFileName = " "
var recentFileName = "Bøsse "
struct Static {
static var instance:SQLiteDB? = nil
static var token:dispatch_once_t = 0
}
class func sharedInstance() -> SQLiteDB! {
dispatch_once(&Static.token) {
Static.instance = self.init(gid:"")
}
return Static.instance!
}
class func sharedInstance(gid:String) -> SQLiteDB! {
dispatch_once(&Static.token) {
Static.instance = self.init(gid:gid)
}
return Static.instance!
}
required init(gid:String) {
assert(Static.instance == nil, "Singleton already initialized!")
GROUP = gid
// Set queue
queue = dispatch_queue_create(QUEUE_LABLE, nil)
// Set up for file operations
let fm = NSFileManager.defaultManager()
let dbName:String = String.fromCString(DB_NAME)!
var docDir = ""
// Is this for an app group?
if GROUP.isEmpty {
// Get path to DB in Documents directory
docDir = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)[0] as String
} else {
// Get path to shared group folder
if let url = fm.containerURLForSecurityApplicationGroupIdentifier(GROUP) {
docDir = url.path!
} else {
assert(false, "Error getting container URL for group: \(GROUP)")
}
}
let path = docDir.NS.stringByAppendingPathComponent(dbName)
print(path)
// Check if copy of DB is there in Documents directory
if !(fm.fileExistsAtPath(path)) {
// The database does not exist, so copy to Documents directory
if let from = NSBundle.mainBundle().resourcePath?.NS.stringByAppendingPathComponent(dbName) {
var error:NSError?
do {
try fm.copyItemAtPath(from, toPath: path)
print("restart the app to get updated data")
} catch let error1 as NSError {
error = error1
print("SQLiteDB - failed to copy writable version of DB!")
print("Error - \(error!.localizedDescription)")
return
}
}
}
// Open the DB
let cpath = path.cStringUsingEncoding(NSUTF8StringEncoding)
let error = sqlite3_open(cpath!, &db)
if error != SQLITE_OK {
// Open failed, close DB and fail
print("SQLiteDB - failed to open DB!")
sqlite3_close(db)
}
fmt.dateFormat = "YYYY-MM-dd HH:mm:ss"
}
deinit {
closeDatabase()
}
private func closeDatabase() {
if db != nil {
// Get launch count value
let ud = NSUserDefaults.standardUserDefaults()
var launchCount = ud.integerForKey("LaunchCount")
launchCount--
print("SQLiteDB - Launch count \(launchCount)")
var clean = false
if launchCount < 0 {
clean = true
launchCount = 500
}
ud.setInteger(launchCount, forKey: "LaunchCount")
ud.synchronize()
// Do we clean DB?
if !clean {
sqlite3_close(db)
return
}
// Clean DB
print("SQLiteDB - Optimize DB")
let sql = "VACUUM; ANALYZE"
if execute(sql) != SQLITE_OK {
print("SQLiteDB - Error cleaning DB")
}
sqlite3_close(db)
}
}
// Execute SQL with parameters and return result code
func execute(sql:String, parameters:[AnyObject]?=nil)->CInt {
var result:CInt = 0
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
if stmt != nil {
result = self.execute(stmt, sql:sql)
}
}
return result
}
// Run SQL query with parameters
func query(sql:String, parameters:[AnyObject]?=nil)->[SQLRow] {
var rows = [SQLRow]()
dispatch_sync(queue) {
let stmt = self.prepare(sql, params:parameters)
if stmt != nil {
rows = self.query(stmt, sql:sql)
}
}
return rows
}
// Show alert with either supplied message or last error
func alert(msg:String) {
dispatch_async(dispatch_get_main_queue()) {
#if os(iOS)
let alert = UIAlertView(title: "SQLiteDB", message:msg, delegate: nil, cancelButtonTitle: "OK")
alert.show()
#else
let alert = NSAlert()
alert.addButtonWithTitle("OK")
alert.messageText = "SQLiteDB"
alert.informativeText = msg
alert.alertStyle = NSAlertStyle.WarningAlertStyle
alert.runModal()
#endif
}
}
// Private method which prepares the SQL
private func prepare(sql:String, params:[AnyObject]?)->COpaquePointer {
var stmt:COpaquePointer = nil
let cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding)
// Prepare
let result = sqlite3_prepare_v2(self.db, cSql!, -1, &stmt, nil)
if result != SQLITE_OK {
sqlite3_finalize(stmt)
if let error = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to prepare SQL: \(sql), Error: \(error)"
print(msg)
}
return nil
}
// Bind parameters, if any
if params != nil {
// Validate parameters
let cntParams = sqlite3_bind_parameter_count(stmt)
let cnt = CInt(params!.count)
if cntParams != cnt {
let msg = "SQLiteDB - failed to bind parameters, counts did not match. SQL: \(sql), Parameters: \(params)"
print(msg)
return nil
}
var flag:CInt = 0
// Text & BLOB values passed to a C-API do not work correctly if they are not marked as transient.
for ndx in 1...cnt {
// println("Binding: \(params![ndx-1]) at Index: \(ndx)")
// Check for data types
if let txt = params![ndx-1] as? String {
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
} else if let data = params![ndx-1] as? NSData {
flag = sqlite3_bind_blob(stmt, CInt(ndx), data.bytes, CInt(data.length), SQLITE_TRANSIENT)
} else if let date = params![ndx-1] as? NSDate {
let txt = fmt.stringFromDate(date)
flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
} else if let val = params![ndx-1] as? Double {
flag = sqlite3_bind_double(stmt, CInt(ndx), CDouble(val))
} else if let val = params![ndx-1] as? Int {
flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(val))
} else {
flag = sqlite3_bind_null(stmt, CInt(ndx))
}
// Check for errors
if flag != SQLITE_OK {
sqlite3_finalize(stmt)
if let error = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to bind for SQL: \(sql), Parameters: \(params), Index: \(ndx) Error: \(error)"
print(msg)
}
return nil
}
}
}
return stmt
}
// Private method which handles the actual execution of an SQL statement
private func execute(stmt:COpaquePointer, sql:String)->CInt {
// Step
var result = sqlite3_step(stmt)
if result != SQLITE_OK && result != SQLITE_DONE {
sqlite3_finalize(stmt)
if let err = String.fromCString(sqlite3_errmsg(self.db)) {
let msg = "SQLiteDB - failed to execute SQL: \(sql), Error: \(err)"
print(msg)
}
return 0
}
// Is this an insert
let upp = sql.uppercaseString
if upp.hasPrefix("INSERT ") {
// Known limitations: http://www.sqlite.org/c3ref/last_insert_rowid.html
let rid = sqlite3_last_insert_rowid(self.db)
result = CInt(rid)
} else if upp.hasPrefix("DELETE") || upp.hasPrefix("UPDATE") {
var cnt = sqlite3_changes(self.db)
if cnt == 0 {
cnt++
}
result = CInt(cnt)
} else {
result = 1
}
// Finalize
sqlite3_finalize(stmt)
return result
}
// Private method which handles the actual execution of an SQL query
private func query(stmt:COpaquePointer, sql:String)->[SQLRow] {
var rows = [SQLRow]()
var fetchColumnInfo = true
var columnCount:CInt = 0
var columnNames = [String]()
var columnTypes = [CInt]()
var result = sqlite3_step(stmt)
while result == SQLITE_ROW {
// Should we get column info?
if fetchColumnInfo {
columnCount = sqlite3_column_count(stmt)
for index in 0..<columnCount {
// Get column name
let name = sqlite3_column_name(stmt, index)
columnNames.append(String.fromCString(name)!)
// Get column type
columnTypes.append(self.getColumnType(index, stmt:stmt))
}
fetchColumnInfo = false
}
// Get row data for each column
let row = SQLRow()
for index in 0..<columnCount {
let key = columnNames[Int(index)]
let type = columnTypes[Int(index)]
if let val:AnyObject = self.getColumnValue(index, type:type, stmt:stmt) {
// println("Column type:\(type) with value:\(val)")
let col = SQLColumn(value: val, type: type)
row[key] = col
}
}
rows.append(row)
// Next row
result = sqlite3_step(stmt)
}
sqlite3_finalize(stmt)
return rows
}
// Get column type
private func getColumnType(index:CInt, stmt:COpaquePointer)->CInt {
var type:CInt = 0
// Column types - http://www.sqlite.org/datatype3.html (section 2.2 table column 1)
let blobTypes = ["BINARY", "BLOB", "VARBINARY"]
let charTypes = ["CHAR", "CHARACTER", "CLOB", "NATIONAL VARYING CHARACTER", "NATIVE CHARACTER", "NCHAR", "NVARCHAR", "TEXT", "VARCHAR", "VARIANT", "VARYING CHARACTER"]
let dateTypes = ["DATE", "DATETIME", "TIME", "TIMESTAMP"]
let intTypes = ["BIGINT", "BIT", "BOOL", "BOOLEAN", "INT", "INT2", "INT8", "INTEGER", "MEDIUMINT", "SMALLINT", "TINYINT"]
let nullTypes = ["NULL"]
let realTypes = ["DECIMAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "NUMERIC", "REAL"]
// Determine type of column - http://www.sqlite.org/c3ref/c_blob.html
let buf = sqlite3_column_decltype(stmt, index)
// println("SQLiteDB - Got column type: \(buf)")
if buf != nil {
var tmp = String.fromCString(buf)!.uppercaseString
// Remove brackets
let pos = tmp.positionOf("(")
if pos > 0 {
tmp = tmp.subStringTo(pos)
}
// Remove unsigned?
// Remove spaces
// Is the data type in any of the pre-set values?
// println("SQLiteDB - Cleaned up column type: \(tmp)")
if intTypes.contains(tmp) {
return SQLITE_INTEGER
}
if realTypes.contains(tmp) {
return SQLITE_FLOAT
}
if charTypes.contains(tmp) {
return SQLITE_TEXT
}
if blobTypes.contains(tmp) {
return SQLITE_BLOB
}
if nullTypes.contains(tmp) {
return SQLITE_NULL
}
if dateTypes.contains(tmp) {
return SQLITE_DATE
}
return SQLITE_TEXT
} else {
// For expressions and sub-queries
type = sqlite3_column_type(stmt, index)
}
return type
}
// Get column value
private func getColumnValue(index:CInt, type:CInt, stmt:COpaquePointer)->AnyObject? {
// Integer
if type == SQLITE_INTEGER {
let val = sqlite3_column_int(stmt, index)
return Int(val)
}
// Float
if type == SQLITE_FLOAT {
let val = sqlite3_column_double(stmt, index)
return Double(val)
}
// Text - handled by default handler at end
// Blob
if type == SQLITE_BLOB {
let data = sqlite3_column_blob(stmt, index)
let size = sqlite3_column_bytes(stmt, index)
let val = NSData(bytes:data, length: Int(size))
return val
}
// Null
if type == SQLITE_NULL {
return nil
}
// Date
if type == SQLITE_DATE {
// Is this a text date
let txt = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
if txt != nil {
if let buf = NSString(CString:txt, encoding:NSUTF8StringEncoding) {
let set = NSCharacterSet(charactersInString: "-:")
let range = buf.rangeOfCharacterFromSet(set)
if range.location != NSNotFound {
// Convert to time
var time:tm = tm(tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0, tm_zone:nil)
strptime(txt, "%Y-%m-%d %H:%M:%S", &time)
time.tm_isdst = -1
let diff = NSTimeZone.localTimeZone().secondsFromGMT
let t = mktime(&time) + diff
let ti = NSTimeInterval(t)
let val = NSDate(timeIntervalSince1970:ti)
return val
}
}
}
// If not a text date, then it's a time interval
let val = sqlite3_column_double(stmt, index)
let dt = NSDate(timeIntervalSince1970: val)
return dt
}
// If nothing works, return a string representation
let buf = UnsafePointer<Int8>(sqlite3_column_text(stmt, index))
let val = String.fromCString(buf)
// println("SQLiteDB - Got value: \(val)")
return val
}
}
public extension String {
var NS: NSString { return (self as NSString) }
}
Hope you can give me some advice. Thanks

Issue Saving/Displaying CKAssets

I'm pretty new to swift, so please try to bear with me.
I'm currently able to download CKRecords and insert them into an array, "birdFacts". Each record includes a few strings, an image (CKAsset), a date, and an int. When they initially download from iCloud, everything works fine.
Everything is saving as expected, except the image. When I reload the data, the asset doesn't show up.
Here is my code to load saved data:
if let savedFacts = loadFacts() {
birdFacts = savedFacts
print("successfully loaded saved bird facts")
}
func loadFacts() -> [CKRecord]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(BirdFact.ArchiveURL.path!) as? [CKRecord]
}
This is my code to save the array:
func saveFacts() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(birdFacts, toFile: BirdFact.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save bird facts")
}
}
This is within my custom class definition file:
import UIKit
import CloudKit
class BirdFact: NSObject, NSCoding {
//MARK: PROPERTIES
var birdName: String
var photo: CKAsset
var birdFact: String
var date: NSDate
var sortingDate: Int
//MARK: TYPES
struct PropertyKey {
static let namekey = "name"
static let photokey = "photo"
static let factkey = "fact"
static let datekey = "date"
static let sortingDatekey = "sortingDate"
}
//MARK: INITIALIZATION
init?(birdName: String, photo: CKAsset, birdFact: String, date: NSDate, sortingDate: Int){
//init stored props
self.birdName = birdName
self.photo = photo
self.birdFact = birdFact
self.date = date
self.sortingDate = sortingDate
super.init()
if birdName.isEmpty || birdFact.isEmpty {
return nil
}
}
//MARK: NSCODING
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(birdName, forKey: PropertyKey.namekey)
aCoder.encodeObject(photo, forKey: PropertyKey.photokey)
aCoder.encodeObject(birdFact, forKey: PropertyKey.factkey)
aCoder.encodeObject(date, forKey: PropertyKey.datekey)
aCoder.encodeObject(sortingDate, forKey: PropertyKey.sortingDatekey)
}
required convenience init?(coder aDecoder: NSCoder) {
let birdName = aDecoder.decodeObjectForKey(PropertyKey.namekey) as! String
let photo = aDecoder.decodeObjectForKey(PropertyKey.photokey) as! CKAsset
let birdFact = aDecoder.decodeObjectForKey(PropertyKey.factkey) as! String
let date = aDecoder.decodeObjectForKey(PropertyKey.datekey) as! NSDate
let sortingDate = aDecoder.decodeObjectForKey(PropertyKey.sortingDatekey) as! Int
self.init(birdName: birdName, photo: photo, birdFact: birdFact, date: date, sortingDate: sortingDate)
}
//MARK: ARCHIVING PATHS
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("BirdFacts")
}
I'm saving whenever I finish downloading and sorting the records. Any idea what is going wrong?
EDIT: Included CloudKit query. I should note that I never save anything to iCloud, only download existing records.
func allprevFacts(date: String, olddate: Int){
act.startAnimating()
let container = CKContainer.defaultContainer()
let publicDB = container.publicCloudDatabase
//let factPredicate = NSPredicate(format: "recordID = %#", CKRecordID(recordName: date))
let factPredicate = NSPredicate(format: "date <= %#", NSDate())
let query = CKQuery(recordType: "BirdFacts", predicate: factPredicate)
publicDB.performQuery(query, inZoneWithID: nil) { (results, error) -> Void in
if error != nil {
print(error)
}
else {
dispatch_async(dispatch_get_main_queue()){
//print(results)
var count = 0
var sortedresults = [Int]()
for result in results! {
var b = result.valueForKey("sortingDate") as! Int
sortedresults.append(b)
}
print(sortedresults)
while count < sortedresults.count {
if sortedresults[count] <= olddate {
sortedresults.removeAtIndex(count)
}
else {
count = count + 1
}
}
print(sortedresults)
while sortedresults.count > 0 {
var d: Int = 0
let a = sortedresults.maxElement()
print(a)
while d < sortedresults.count{
if sortedresults[d] == a {
sortedresults.removeAtIndex(d)
self.birdFacts.append(results![d])
self.tableFacts.reloadData()
self.tableFacts.hidden = false
}
d = d + 1
print(d)
}
}
self.saveFacts()
print("saving bird facts")
self.tableFacts.hidden = false
}
}
}
act.stopAnimating()

Resources