From 283d4741168609381e4e8c94aceec78faf00ab31 Mon Sep 17 00:00:00 2001 From: Sanjay Pandey Date: Mon, 27 Mar 2017 13:31:11 +0530 Subject: [PATCH 1/3] Fix crash on ios due to sqlite3_column_decltype --- src/ios/SwiftData.swift | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/ios/SwiftData.swift b/src/ios/SwiftData.swift index 36cfd290..0c9c25bf 100644 --- a/src/ios/SwiftData.swift +++ b/src/ios/SwiftData.swift @@ -1428,29 +1428,23 @@ public struct SwiftData { for i: Int32 in 0 ..< columnCount { //for var i: Int32 = 0; i < columnCount; ++i { let columnName = String.fromCString(sqlite3_column_name(pStmt, i))! - if let columnType = String.fromCString(sqlite3_column_decltype(pStmt, i))?.uppercaseString { - if let columnValue: AnyObject = getColumnValue(pStmt, index: i, type: columnType) { - row[columnName] = SDColumn(obj: columnValue) - } - } else { - var columnType = "" - switch sqlite3_column_type(pStmt, i) { - case 1: - columnType = "INTEGER" - case 2: - columnType = "FLOAT" - case 3: - columnType = "TEXT" - case 4: - columnType = "BLOB" - case 5: - columnType = "NULL" - default: - columnType = "NULL" - } - if let columnValue: AnyObject = getColumnValue(pStmt, index: i, type: columnType) { - row[columnName] = SDColumn(obj: columnValue) - } + var columnType = "" + switch sqlite3_column_type(pStmt, i) { + case 1: + columnType = "INTEGER" + case 2: + columnType = "FLOAT" + case 3: + columnType = "TEXT" + case 4: + columnType = "BLOB" + case 5: + columnType = "NULL" + default: + columnType = "NULL" + } + if let columnValue: AnyObject = getColumnValue(pStmt, index: i, type: columnType) { + row[columnName] = SDColumn(obj: columnValue) } } resultSet.append(row) From 80c09c9d5421c9e3700c3718a866d1151aa5f035 Mon Sep 17 00:00:00 2001 From: Sanjay Pandey Date: Thu, 6 Apr 2017 19:27:18 +0530 Subject: [PATCH 2/3] Swift 3 update tested and working --- src/ios/GeofencePlugin.swift | 172 ++-- src/ios/SwiftData.swift | 1784 +++++++++++++--------------------- src/ios/SwiftyJson.swift | 1097 +++++++++++---------- 3 files changed, 1319 insertions(+), 1734 deletions(-) diff --git a/src/ios/GeofencePlugin.swift b/src/ios/GeofencePlugin.swift index fec9b233..6a31089c 100644 --- a/src/ios/GeofencePlugin.swift +++ b/src/ios/GeofencePlugin.swift @@ -14,38 +14,40 @@ let TAG = "GeofencePlugin" let iOS8 = floor(NSFoundationVersionNumber) > floor(NSFoundationVersionNumber_iOS_7_1) let iOS7 = floor(NSFoundationVersionNumber) <= floor(NSFoundationVersionNumber_iOS_7_1) -func log(message: String){ + +func log(_ message: String){ NSLog("%@ - %@", TAG, message) } -func log(messages: [String]) { +func log(_ messages: [String]) { for message in messages { log(message); } } + @available(iOS 8.0, *) @objc(HWPGeofencePlugin) class GeofencePlugin : CDVPlugin { lazy var geoNotificationManager = GeoNotificationManager() - let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT + let priority = DispatchQoS.QoSClass.default override func pluginInitialize () { - NSNotificationCenter.defaultCenter().addObserver( + NotificationCenter.default.addObserver( self, selector: #selector(GeofencePlugin.didReceiveLocalNotification(_:)), - name: "CDVLocalNotification", + name: NSNotification.Name(rawValue: "CDVLocalNotification"), object: nil ) - NSNotificationCenter.defaultCenter().addObserver( + NotificationCenter.default.addObserver( self, selector: #selector(GeofencePlugin.didReceiveTransition(_:)), - name: "handleTransition", + name: NSNotification.Name(rawValue: "handleTransition"), object: nil ) } - func initialize(command: CDVInvokedUrlCommand) { + func initialize(_ command: CDVInvokedUrlCommand) { log("Plugin initialization") //let faker = GeofenceFaker(manager: geoNotificationManager) //faker.start() @@ -65,83 +67,83 @@ func log(messages: [String]) { let result: CDVPluginResult if ok { - result = CDVPluginResult(status: CDVCommandStatus_OK, messageAsString: warnings.joinWithSeparator("\n")) + result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: warnings.joined(separator: "\n")) } else { result = CDVPluginResult( status: CDVCommandStatus_ILLEGAL_ACCESS_EXCEPTION, - messageAsString: (errors + warnings).joinWithSeparator("\n") + messageAs: (errors + warnings).joined(separator: "\n") ) } - commandDelegate!.sendPluginResult(result, callbackId: command.callbackId) + commandDelegate!.send(result, callbackId: command.callbackId) } - func deviceReady(command: CDVInvokedUrlCommand) { + func deviceReady(_ command: CDVInvokedUrlCommand) { let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + commandDelegate!.send(pluginResult, callbackId: command.callbackId) } - func ping(command: CDVInvokedUrlCommand) { + func ping(_ command: CDVInvokedUrlCommand) { log("Ping") let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + commandDelegate!.send(pluginResult, callbackId: command.callbackId) } func promptForNotificationPermission() { - UIApplication.sharedApplication().registerUserNotificationSettings(UIUserNotificationSettings( - forTypes: [UIUserNotificationType.Sound, UIUserNotificationType.Alert, UIUserNotificationType.Badge], + UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings( + types: [UIUserNotificationType.sound, UIUserNotificationType.alert, UIUserNotificationType.badge], categories: nil ) ) } - func addOrUpdate(command: CDVInvokedUrlCommand) { - dispatch_async(dispatch_get_global_queue(priority, 0)) { + func addOrUpdate(_ command: CDVInvokedUrlCommand) { + DispatchQueue.global(qos: priority).async { // do some task for geo in command.arguments { self.geoNotificationManager.addOrUpdateGeoNotification(JSON(geo)) } - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - self.commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) } } } - func getWatched(command: CDVInvokedUrlCommand) { - dispatch_async(dispatch_get_global_queue(priority, 0)) { + func getWatched(_ command: CDVInvokedUrlCommand) { + DispatchQueue.global(qos: priority).async { let watched = self.geoNotificationManager.getWatchedGeoNotifications()! let watchedJsonString = watched.description - dispatch_async(dispatch_get_main_queue()) { - let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAsString: watchedJsonString) - self.commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + DispatchQueue.main.async { + let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: watchedJsonString) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) } } } - func remove(command: CDVInvokedUrlCommand) { - dispatch_async(dispatch_get_global_queue(priority, 0)) { + func remove(_ command: CDVInvokedUrlCommand) { + DispatchQueue.global(qos: priority).async { for id in command.arguments { self.geoNotificationManager.removeGeoNotification(id as! String) } - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - self.commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) } } } - func removeAll(command: CDVInvokedUrlCommand) { - dispatch_async(dispatch_get_global_queue(priority, 0)) { + func removeAll(_ command: CDVInvokedUrlCommand) { + DispatchQueue.global(qos: priority).async { self.geoNotificationManager.removeAllGeoNotifications() - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK) - self.commandDelegate!.sendPluginResult(pluginResult, callbackId: command.callbackId) + self.commandDelegate!.send(pluginResult, callbackId: command.callbackId) } } } - func didReceiveTransition (notification: NSNotification) { + func didReceiveTransition (_ notification: Notification) { log("didReceiveTransition") if let geoNotificationString = notification.object as? String { @@ -151,9 +153,9 @@ func log(messages: [String]) { } } - func didReceiveLocalNotification (notification: NSNotification) { + func didReceiveLocalNotification (_ notification: Notification) { log("didReceiveLocalNotification") - if UIApplication.sharedApplication().applicationState != UIApplicationState.Active { + if UIApplication.shared.applicationState != UIApplicationState.active { var data = "undefined" if let uiNotification = notification.object as? UILocalNotification { if let notificationData = uiNotification.userInfo?["geofence.notification.data"] as? String { @@ -166,10 +168,10 @@ func log(messages: [String]) { } } - func evaluateJs (script: String) { + func evaluateJs (_ script: String) { if let webView = webView { if let uiWebView = webView as? UIWebView { - uiWebView.stringByEvaluatingJavaScriptFromString(script) + uiWebView.stringByEvaluatingJavaScript(from: script) } else if let wkWebView = webView as? WKWebView { wkWebView.evaluateJavaScript(script, completionHandler: nil) } @@ -182,7 +184,7 @@ func log(messages: [String]) { // class for faking crossing geofences @available(iOS 8.0, *) class GeofenceFaker { - let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT + let priority = DispatchQoS.QoSClass.default let geoNotificationManager: GeoNotificationManager init(manager: GeoNotificationManager) { @@ -190,7 +192,7 @@ class GeofenceFaker { } func start() { - dispatch_async(dispatch_get_global_queue(priority, 0)) { + DispatchQueue.global(qos: priority).async { while (true) { log("FAKER") let notify = arc4random_uniform(4) @@ -202,7 +204,7 @@ class GeofenceFaker { let index = arc4random_uniform(UInt32(geos.count)) let geo = geos[Int(index)] let id = geo["id"].stringValue - dispatch_async(dispatch_get_main_queue()) { + DispatchQueue.main.async { if let region = self.geoNotificationManager.getMonitoredRegion(id) { log("FAKER Trigger didEnterRegion") self.geoNotificationManager.locationManager( @@ -213,7 +215,7 @@ class GeofenceFaker { } } } - NSThread.sleepForTimeInterval(3) + Thread.sleep(forTimeInterval: 3) } } } @@ -241,7 +243,7 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { } } - func addOrUpdateGeoNotification(geoNotification: JSON) { + func addOrUpdateGeoNotification(_ geoNotification: JSON) { log("GeoNotificationManager addOrUpdate") let (_, warnings, errors) = checkRequirements() @@ -268,14 +270,14 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { //store store.addOrUpdate(geoNotification) - locationManager.startMonitoringForRegion(region) + locationManager.startMonitoring(for: region) } func checkRequirements() -> (Bool, [String], [String]) { var errors = [String]() var warnings = [String]() - if (!CLLocationManager.isMonitoringAvailableForClass(CLRegion)) { + if (!CLLocationManager.isMonitoringAvailable(for: CLRegion.self)) { errors.append("Geofencing not available") } @@ -285,24 +287,24 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { let authStatus = CLLocationManager.authorizationStatus() - if (authStatus != CLAuthorizationStatus.AuthorizedAlways) { + if (authStatus != CLAuthorizationStatus.authorizedAlways) { errors.append("Warning: Location always permissions not granted") } if (iOS8) { - if let notificationSettings = UIApplication.sharedApplication().currentUserNotificationSettings() { - if notificationSettings.types == .None { + if let notificationSettings = UIApplication.shared.currentUserNotificationSettings { + if notificationSettings.types == UIUserNotificationType() { errors.append("Error: notification permission missing") } else { - if !notificationSettings.types.contains(.Sound) { + if !notificationSettings.types.contains(.sound) { warnings.append("Warning: notification settings - sound permission missing") } - if !notificationSettings.types.contains(.Alert) { + if !notificationSettings.types.contains(.alert) { warnings.append("Warning: notification settings - alert permission missing") } - if !notificationSettings.types.contains(.Badge) { + if !notificationSettings.types.contains(.badge) { warnings.append("Warning: notification settings - badge permission missing") } } @@ -320,7 +322,7 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { return store.getAll() } - func getMonitoredRegion(id: String) -> CLRegion? { + func getMonitoredRegion(_ id: String) -> CLRegion? { for object in locationManager.monitoredRegions { let region = object @@ -331,12 +333,12 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { return nil } - func removeGeoNotification(id: String) { + func removeGeoNotification(_ id: String) { store.remove(id) let region = getMonitoredRegion(id) if (region != nil) { log("Stoping monitoring region \(id)") - locationManager.stopMonitoringForRegion(region!) + locationManager.stopMonitoring(for: region!) } } @@ -345,33 +347,33 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { for object in locationManager.monitoredRegions { let region = object log("Stoping monitoring region \(region.identifier)") - locationManager.stopMonitoringForRegion(region) + locationManager.stopMonitoring(for: region) } } - func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { log("update location") } - func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { log("fail with error: \(error)") } - func locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError error: NSError?) { + func locationManager(_ manager: CLLocationManager, didFinishDeferredUpdatesWithError error: Error?) { log("deferred fail error: \(error)") } - func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) { + func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { log("Entering region \(region.identifier)") handleTransition(region, transitionType: 1) } - func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) { + func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { log("Exiting region \(region.identifier)") handleTransition(region, transitionType: 2) } - func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) { + func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) { if region is CLCircularRegion { let lat = (region as! CLCircularRegion).center.latitude let lng = (region as! CLCircularRegion).center.longitude @@ -381,38 +383,38 @@ class GeoNotificationManager : NSObject, CLLocationManagerDelegate { } } - func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) { + func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) { log("State for region " + region.identifier) } - func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) { - log("Monitoring region " + region!.identifier + " failed " + error.description) + func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) { + log("Monitoring region " + region!.identifier + " failed " + error.localizedDescription) } - func handleTransition(region: CLRegion!, transitionType: Int) { + func handleTransition(_ region: CLRegion!, transitionType: Int) { if var geoNotification = store.findById(region.identifier) { geoNotification["transitionType"].int = transitionType - - if geoNotification["notification"].isExists() { + + if geoNotification["notification"].exists() { notifyAbout(geoNotification) } - NSNotificationCenter.defaultCenter().postNotificationName("handleTransition", object: geoNotification.rawString(NSUTF8StringEncoding, options: [])) + NotificationCenter.default.post(name: NSNotification.Name(rawValue: "handleTransition"), object: geoNotification.rawString(String.Encoding.utf8, options: [])) } } - func notifyAbout(geo: JSON) { + func notifyAbout(_ geo: JSON) { log("Creating notification") let notification = UILocalNotification() - notification.timeZone = NSTimeZone.defaultTimeZone() - let dateTime = NSDate() + notification.timeZone = TimeZone.current + let dateTime = Date() notification.fireDate = dateTime notification.soundName = UILocalNotificationDefaultSoundName notification.alertBody = geo["notification"]["text"].stringValue if let json = geo["notification"]["data"] as JSON? { - notification.userInfo = ["geofence.notification.data": json.rawString(NSUTF8StringEncoding, options: [])!] + notification.userInfo = ["geofence.notification.data": json.rawString(String.Encoding.utf8, options: [])!] } - UIApplication.sharedApplication().scheduleLocalNotification(notification) + UIApplication.shared.scheduleLocalNotification(notification) if let vibrate = geo["notification"]["vibrate"].array { if (!vibrate.isEmpty && vibrate[0].intValue > 0) { @@ -446,7 +448,7 @@ class GeoNotificationStore { } } - func addOrUpdate(geoNotification: JSON) { + func addOrUpdate(_ geoNotification: JSON) { if (findById(geoNotification["id"].stringValue) != nil) { update(geoNotification) } @@ -455,28 +457,28 @@ class GeoNotificationStore { } } - func add(geoNotification: JSON) { + func add(_ geoNotification: JSON) { let id = geoNotification["id"].stringValue let err = SD.executeChange("INSERT INTO GeoNotifications (Id, Data) VALUES(?, ?)", - withArgs: [id, geoNotification.description]) + withArgs: [id as AnyObject, geoNotification.description as AnyObject]) if err != nil { log("Error while adding \(id) GeoNotification: \(err)") } } - func update(geoNotification: JSON) { + func update(_ geoNotification: JSON) { let id = geoNotification["id"].stringValue let err = SD.executeChange("UPDATE GeoNotifications SET Data = ? WHERE Id = ?", - withArgs: [geoNotification.description, id]) + withArgs: [geoNotification.description as AnyObject, id as AnyObject]) if err != nil { log("Error while adding \(id) GeoNotification: \(err)") } } - func findById(id: String) -> JSON? { - let (resultSet, err) = SD.executeQuery("SELECT * FROM GeoNotifications WHERE Id = ?", withArgs: [id]) + func findById(_ id: String) -> JSON? { + let (resultSet, err) = SD.executeQuery("SELECT * FROM GeoNotifications WHERE Id = ?", withArgs: [id as AnyObject]) if err != nil { //there was an error during the query, handle it here @@ -485,7 +487,7 @@ class GeoNotificationStore { } else { if (resultSet.count > 0) { let jsonString = resultSet[0]["Data"]!.asString()! - return JSON(data: jsonString.dataUsingEncoding(NSUTF8StringEncoding)!) + return JSON(data: jsonString.data(using: String.Encoding.utf8)!) } else { return nil @@ -504,15 +506,15 @@ class GeoNotificationStore { var results = [JSON]() for row in resultSet { if let data = row["Data"]?.asString() { - results.append(JSON(data: data.dataUsingEncoding(NSUTF8StringEncoding)!)) + results.append(JSON(data: data.data(using: String.Encoding.utf8)!)) } } return results } } - func remove(id: String) { - let err = SD.executeChange("DELETE FROM GeoNotifications WHERE Id = ?", withArgs: [id]) + func remove(_ id: String) { + let err = SD.executeChange("DELETE FROM GeoNotifications WHERE Id = ?", withArgs: [id as AnyObject]) if err != nil { log("Error while removing \(id) GeoNotification: \(err)") diff --git a/src/ios/SwiftData.swift b/src/ios/SwiftData.swift index 0c9c25bf..5d43323b 100644 --- a/src/ios/SwiftData.swift +++ b/src/ios/SwiftData.swift @@ -1,7 +1,7 @@ // // SwiftData.swift // -// Copyright (c) 2014 Ryan Fowler +// Copyright (c) 2015 Ryan Fowler // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,860 +21,536 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - import Foundation import UIKit // MARK: - SwiftData - public struct SwiftData { - - + + // MARK: - Public SwiftData Functions - - + + // MARK: - Execute Statements - + /** - Execute a non-query SQL statement (e.g. INSERT, UPDATE, DELETE, etc.) - - This function will execute the provided SQL and return an Int with the error code, or nil if there was no error. - It is recommended to always verify that the return value is nil to ensure that the operation was successful. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter sqlStr: The non-query string of SQL to be executed (INSERT, UPDATE, DELETE, etc.) - - - returns: An Int with the error code, or nil if there was no error - */ - public static func executeChange(sqlStr: String) -> Int? { - - //create error variable + Execute a non-query SQL statement (e.g. INSERT, UPDATE, DELETE, etc.) + + This function will execute the provided SQL and return an Int with the error code, or nil if there was no error. + It is recommended to always verify that the return value is nil to ensure that the operation was successful. + + Possible errors returned by this function are: + + - SQLite errors (0 - 101) + + :param: sqlStr The non-query string of SQL to be executed (INSERT, UPDATE, DELETE, etc.) + + :returns: An Int with the error code, or nil if there was no error + */ + public static func executeChange(_ sqlStr: String) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the change statement error = SQLiteDB.sharedInstance.executeChange(sqlStr) - - //close database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - + /** - Execute a non-query SQL statement (e.g. INSERT, UPDATE, DELETE, etc.) along with arguments to be bound to the characters "?" (for values) and "i?" (for identifiers e.g. table or column names). - - The objects in the provided array of arguments will be bound, in order, to the "i?" and "?" characters in the SQL string. - The quantity of "i?"s and "?"s in the SQL string must be equal to the quantity of arguments provided. - Objects that are to bind as an identifier ("i?") must be of type String. - Identifiers should be bound and escaped if provided by the user. - If "nil" is provided as an argument, the NULL value will be bound to the appropriate value in the SQL string. - For more information on how the objects will be escaped, refer to the functions "escapeValue()" and "escapeIdentifier()". - Note that the "escapeValue()" and "escapeIdentifier()" include the necessary quotations ' ' or " " to the arguments when being bound to the SQL. - - It is recommended to always verify that the return value is nil to ensure that the operation was successful. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - binding errors (201 - 203) - - - parameter sqlStr: The non-query string of SQL to be executed (INSERT, UPDATE, DELETE, etc.) - - parameter withArgs: An array of objects to bind to the "?" and "i?" characters in the sqlStr - - - returns: An Int with the error code, or nil if there was no error - */ - public static func executeChange(sqlStr: String, withArgs: [AnyObject]) -> Int? { - - //create success variable + Execute a non-query SQL statement (e.g. INSERT, UPDATE, DELETE, etc.) along with arguments to be bound to the characters "?" (for values) and "i?" (for identifiers e.g. table or column names). + + The objects in the provided array of arguments will be bound, in order, to the "i?" and "?" characters in the SQL string. + The quantity of "i?"s and "?"s in the SQL string must be equal to the quantity of arguments provided. + Objects that are to bind as an identifier ("i?") must be of type String. + Identifiers should be bound and escaped if provided by the user. + If "nil" is provided as an argument, the NULL value will be bound to the appropriate value in the SQL string. + For more information on how the objects will be escaped, refer to the functions "escapeValue()" and "escapeIdentifier()". + Note that the "escapeValue()" and "escapeIdentifier()" include the necessary quotations ' ' or " " to the arguments when being bound to the SQL. + It is recommended to always verify that the return value is nil to ensure that the operation was successful. + + Possible errors returned by this function are: + + - SQLite errors (0 - 101) + - binding errors (201 - 203) + + :param: sqlStr The non-query string of SQL to be executed (INSERT, UPDATE, DELETE, etc.) + :param: withArgs An array of objects to bind to the "?" and "i?" characters in the sqlStr + + :returns: An Int with the error code, or nil if there was no error + */ + public static func executeChange(_ sqlStr: String, withArgs: [AnyObject]) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the change statement error = SQLiteDB.sharedInstance.executeChange(sqlStr, withArgs: withArgs) - - //close database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - + /** - Execute multiple SQL statements (non-queries e.g. INSERT, UPDATE, DELETE, etc.) - - This function will execute each SQL statment in the provided array, in order, and return an Int with the error code, or nil if there was no error. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter sqlArr: An array of non-query strings of SQL to be executed (INSERT, UPDATE, DELETE, etc.) - - - returns: An Int with the error code, or nil if there was no error - */ - public static func executeMultipleChanges(sqlArr: [String]) -> Int? { - - //create error variable + Execute multiple SQL statements (non-queries e.g. INSERT, UPDATE, DELETE, etc.) + This function will execute each SQL statment in the provided array, in order, and return an Int with the error code, or nil if there was no error. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :param: sqlArr An array of non-query strings of SQL to be executed (INSERT, UPDATE, DELETE, etc.) + + :returns: An Int with the error code, or nil if there was no error + */ + public static func executeMultipleChanges(_ sqlArr: [String]) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the change statements for sqlStr in sqlArr { if let err = SQLiteDB.sharedInstance.executeChange(sqlStr) { SQLiteDB.sharedInstance.close() - if let index = sqlArr.indexOf(sqlStr) { + if let index = sqlArr.index(of: sqlStr) {//find(sqlArr, sqlStr) { print("Error occurred on array item: \(index) -> \"\(sqlStr)\"") } error = err return } } - - //close database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - + /** - Execute a SQLite query statement (e.g. SELECT) - - This function will execute the provided SQL and return a tuple of: - - an Array of SDRow objects - - an Int with the error code, or nil if there was no error - - The value for each column in an SDRow can be obtained using the column name in the subscript format similar to a Dictionary, along with the function to obtain the value in the appropriate type (.asString(), .asDate(), .asData(), .asInt(), .asDouble(), and .asBool()). - Without the function call to return a specific type, the SDRow will return an object with type AnyObject. - Note: NULL values in the SQLite database will be returned as 'nil'. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter sqlStr: The query String of SQL to be executed (e.g. SELECT) - - - returns: A tuple containing an Array of "SDRow"s, and an Int with the error code or nil if there was no error - */ - public static func executeQuery(sqlStr: String) -> (result: [SDRow], error: Int?) { - - //create result and error variables + Execute a SQLite query statement (e.g. SELECT) + This function will execute the provided SQL and return a tuple of: + - an Array of SDRow objects + - an Int with the error code, or nil if there was no error + + The value for each column in an SDRow can be obtained using the column name in the subscript format similar to a Dictionary, along with the function to obtain the value in the appropriate type (.asString(), .asDate(), .asData(), .asInt(), .asDouble(), and .asBool()). + Without the function call to return a specific type, the SDRow will return an object with type AnyObject. + Note: NULL values in the SQLite database will be returned as 'nil'. + + Possible errors returned by this function are: + + - SQLite errors (0 - 101) + + :param: sqlStr The query String of SQL to be executed (e.g. SELECT) + + :returns: A tuple containing an Array of "SDRow"s, and an Int with the error code or nil if there was no error + */ + public static func executeQuery(_ sqlStr: String) -> (result: [SDRow], error: Int?) { + var result = [SDRow] () var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the query statement (result, error) = SQLiteDB.sharedInstance.executeQuery(sqlStr) - - //close database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return (result, error) + } - + /** - Execute a SQL query statement (e.g. SELECT) with arguments to be bound to the characters "?" (for values) and "i?" (for identifiers e.g. table or column names). - - See the "executeChange(sqlStr: String, withArgs: [AnyObject?])" function for more information on the arguments provided and binding. - - See the "executeQuery(sqlStr: String)" function for more information on the return value. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - binding errors (201 - 203) - - - parameter sqlStr: The query String of SQL to be executed (e.g. SELECT) - - parameter withArgs: An array of objects that will be bound, in order, to the characters "?" (for values) and "i?" (for identifiers, e.g. table or column names) in the sqlStr. - - - returns: A tuple containing an Array of "SDRow"s, and an Int with the error code or nil if there was no error - */ - public static func executeQuery(sqlStr: String, withArgs: [AnyObject]) -> (result: [SDRow], error: Int?) { - - //create result and error variables + Execute a SQL query statement (e.g. SELECT) with arguments to be bound to the characters "?" (for values) and "i?" (for identifiers e.g. table or column names). + + See the "executeChange(sqlStr: String, withArgs: [AnyObject?])" function for more information on the arguments provided and binding. + See the "executeQuery(sqlStr: String)" function for more information on the return value. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - binding errors (201 - 203) + :param: sqlStr The query String of SQL to be executed (e.g. SELECT) + :param: withArgs An array of objects that will be bound, in order, to the characters "?" (for values) and "i?" (for identifiers, e.g. table or column names) in the sqlStr. + + :returns: A tuple containing an Array of "SDRow"s, and an Int with the error code or nil if there was no error + */ + public static func executeQuery(_ sqlStr: String, withArgs: [AnyObject]) -> (result: [SDRow], error: Int?) { + var result = [SDRow] () var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the query statement (result, error) = SQLiteDB.sharedInstance.executeQuery(sqlStr, withArgs: withArgs) - - //close database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return (result, error) + } - + /** - Execute functions in a closure on a single custom connection - - - Note: This function cannot be nested within itself, or inside a transaction/savepoint. - - Possible errors returned by this function are: - - - custom connection errors (301 - 306) - - - parameter flags: The custom flag associated with the connection. Can be either: - - .ReadOnly - - .ReadWrite - - .ReadWriteCreate - - - parameter closure: A closure containing functions that will be executed on the custom connection - - - returns: An Int with the error code, or nil if there was no error - */ - public static func executeWithConnection(flags: SD.Flags, closure: ()->Void) -> Int? { - - //create error variable + Execute functions in a closure on a single custom connection + + + Note: This function cannot be nested within itself, or inside a transaction/savepoint. + Possible errors returned by this function are: + - custom connection errors (301 - 306) + :param: flags The custom flag associated with the connection. Can be either: + - .ReadOnly + - .ReadWrite + - .ReadWriteCreate + :param: closure A closure containing functions that will be executed on the custom connection + :returns: An Int with the error code, or nil if there was no error + */ + public static func executeWithConnection(_ flags: SD.Flags, closure: @escaping ()->Void) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open the custom connection if let err = SQLiteDB.sharedInstance.openWithFlags(flags.toSQL()) { error = err return } - - //execute the closure closure() - - //close the custom connection if let err = SQLiteDB.sharedInstance.closeCustomConnection() { error = err return } - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - - + + // MARK: - Escaping Objects - + /** - Escape an object to be inserted into a SQLite statement as a value - - NOTE: Supported object types are: String, Int, Double, Bool, NSData, NSDate, and nil. All other data types will return the String value "NULL", and a warning message will be printed. - - - parameter obj: The value to be escaped - - - returns: The escaped value as a String, ready to be inserted into a SQL statement. Note: Single quotes (') will be placed around the entire value, if necessary. - */ - public static func escapeValue(obj: AnyObject?) -> String { - + Escape an object to be inserted into a SQLite statement as a value + + NOTE: Supported object types are: String, Int, Double, Bool, NSData, NSDate, and nil. All other data types will return the String value "NULL", and a warning message will be printed. + + :param: obj The value to be escaped + + :returns: The escaped value as a String, ready to be inserted into a SQL statement. Note: Single quotes (') will be placed around the entire value, if necessary. + */ + public static func escapeValue(_ obj: AnyObject?) -> String { return SQLiteDB.sharedInstance.escapeValue(obj) } - + /** - Escape a string to be inserted into a SQLite statement as an indentifier (e.g. table or column name) - - - parameter obj: The identifier to be escaped. NOTE: This object must be of type String. - - - returns: The escaped identifier as a String, ready to be inserted into a SQL statement. Note: Double quotes (") will be placed around the entire identifier. - */ - public static func escapeIdentifier(obj: String) -> String { - + Escape a string to be inserted into a SQLite statement as an indentifier (e.g. table or column name) + + :param: obj The identifier to be escaped. NOTE: This object must be of type String. + + :returns: The escaped identifier as a String, ready to be inserted into a SQL statement. Note: Double quotes (") will be placed around the entire identifier. + */ + public static func escapeIdentifier(_ obj: String) -> String { return SQLiteDB.sharedInstance.escapeIdentifier(obj) } - - + + // MARK: - Tables - + /** - Create A Table With The Provided Column Names and Types - - Note: The ID field is created automatically as "INTEGER PRIMARY KEY AUTOINCREMENT" - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter table: The table name to be created - - parameter columnNamesAndTypes: A dictionary where the key = column name, and the value = data type - - - returns: An Int with the error code, or nil if there was no error - */ - public static func createTable(table: String, withColumnNamesAndTypes values: [String: SwiftData.DataType]) -> Int? { - - //create the error variable + Create A Table With The Provided Column Names and Types + Note: The ID field is created automatically as "INTEGER PRIMARY KEY AUTOINCREMENT" + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :param: table The table name to be created + :param: columnNamesAndTypes A dictionary where the key = column name, and the value = data type + + :returns: An Int with the error code, or nil if there was no error + */ + public static func createTable(_ table: String, withColumnNamesAndTypes values: [String: SwiftData.DataType]) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the statement error = SQLiteDB.sharedInstance.createSQLTable(table, withColumnsAndTypes: values) - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - + /** - Delete a SQLite table by name - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter table: The table name to be deleted - - - returns: An Int with the error code, or nil if there was no error - */ - public static func deleteTable(table: String) -> Int? { - - //create the error variable + Delete a SQLite table by name + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :param: table The table name to be deleted + + :returns: An Int with the error code, or nil if there was no error + */ + public static func deleteTable(_ table: String) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the statement error = SQLiteDB.sharedInstance.deleteSQLTable(table) - - //close the database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return error + } - + /** - Obtain a list of the existing SQLite table names - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - Table query error (403) - - - returns: A tuple containing an Array of all existing SQLite table names, and an Int with the error code or nil if there was no error - */ + Obtain a list of the existing SQLite table names + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - Table query error (403) + + :returns: A tuple containing an Array of all existing SQLite table names, and an Int with the error code or nil if there was no error + */ public static func existingTables() -> (result: [String], error: Int?) { - - //create result and error variables + var result = [String] () var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the statement (result, error) = SQLiteDB.sharedInstance.existingTables() - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return (result, error) + } - - + + // MARK: - Misc - - + + /** - Obtain the error message relating to the provided error code - - - parameter code: The error code provided - - - returns: The error message relating to the provided error code - */ - public static func errorMessageForCode(code: Int) -> String { - + Obtain the error message relating to the provided error code + :param: code The error code provided + :returns: The error message relating to the provided error code + */ + public static func errorMessageForCode(_ code: Int) -> String { return SwiftData.SDError.errorMessageFromCode(code) } - + /** - Obtain the database path - - - returns: The path to the SwiftData database - */ + Obtain the database path + + :returns: The path to the SwiftData database + */ public static func databasePath() -> String { - return SQLiteDB.sharedInstance.dbPath } - + /** - Obtain the last inserted row id - - Note: Care should be taken when the database is being accessed from multiple threads. The value could possibly return the last inserted row ID for another operation if another thread executes after your intended operation but before this function call. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - returns: A tuple of he ID of the last successfully inserted row's, and an Int of the error code or nil if there was no error - */ + Obtain the last inserted row id + Note: Care should be taken when the database is being accessed from multiple threads. The value could possibly return the last inserted row ID for another operation if another thread executes after your intended operation but before this function call. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :returns: A tuple of he ID of the last successfully inserted row's, and an Int of the error code or nil if there was no error + */ public static func lastInsertedRowID() -> (rowID: Int, error: Int?) { - - //create result and error variables + var result = 0 var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //find the last inserted row id result = SQLiteDB.sharedInstance.lastInsertedRowID() - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return (result, error) + } - + /** - Obtain the number of rows modified by the most recently completed SQLite statement (INSERT, UPDATE, or DELETE) - - Note: Care should be taken when the database is being accessed from multiple threads. The value could possibly return the number of rows modified for another operation if another thread executes after your intended operation but before this function call. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - returns: A tuple of the number of rows modified by the most recently completed SQLite statement, and an Int with the error code or nil if there was no error - */ + Obtain the number of rows modified by the most recently completed SQLite statement (INSERT, UPDATE, or DELETE) + Note: Care should be taken when the database is being accessed from multiple threads. The value could possibly return the number of rows modified for another operation if another thread executes after your intended operation but before this function call. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :returns: A tuple of the number of rows modified by the most recently completed SQLite statement, and an Int with the error code or nil if there was no error + */ public static func numberOfRowsModified() -> (rowID: Int, error: Int?) { - - //create result and error variables + var result = 0 var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //find the number of rows modified result = SQLiteDB.sharedInstance.numberOfRowsModified() - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return (result, error) + } - - + + // MARK: - Indexes - + /** - Create a SQLite index on the specified table and column(s) - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - Index error (401) - - - parameter name: The index name that is being created - - parameter onColumns: An array of column names that the index will be applied to (must be one column or greater) - - parameter inTable: The table name where the index is being created - - parameter isUnique: True if the index should be unique, false if it should not be unique (defaults to false) - - - returns: An Int with the error code, or nil if there was no error - */ - public static func createIndex(name name: String, onColumns: [String], inTable: String, isUnique: Bool = false) -> Int? { - - //create the error variable + Create a SQLite index on the specified table and column(s) + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - Index error (401) + + :param: name The index name that is being created + :param: onColumns An array of column names that the index will be applied to (must be one column or greater) + :param: inTable The table name where the index is being created + :param: isUnique True if the index should be unique, false if it should not be unique (defaults to false) + + :returns: An Int with the error code, or nil if there was no error + */ + public static func createIndex(name: String, onColumns: [String], inTable: String, isUnique: Bool = false) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //create the index error = SQLiteDB.sharedInstance.createIndex(name, columns: onColumns, table: inTable, unique: isUnique) - - //close the database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return error + } - + /** - Remove a SQLite index by its name - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter indexName: The name of the index to be removed - - - returns: An Int with the error code, or nil if there was no error - */ - public static func removeIndex(indexName: String) -> Int? { - - //create the error variable + Remove a SQLite index by its name + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :param: indexName The name of the index to be removed + + :returns: An Int with the error code, or nil if there was no error + */ + public static func removeIndex(_ indexName: String) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //remove the index error = SQLiteDB.sharedInstance.removeIndex(indexName) - - //close the database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return error + } - + /** - Obtain a list of all existing indexes - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - Index error (402) - - - returns: A tuple containing an Array of all existing index names on the SQLite database, and an Int with the error code or nil if there was no error - */ + Obtain a list of all existing indexes + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - Index error (402) + + :returns: A tuple containing an Array of all existing index names on the SQLite database, and an Int with the error code or nil if there was no error + */ public static func existingIndexes() -> (result: [String], error: Int?) { - - //create the result and error variables + var result = [String] () var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the statement (result, error) = SQLiteDB.sharedInstance.existingIndexes() - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return (result, error) + } - + /** - Obtain a list of all existing indexes on a specific table - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - Index error (402) - - - parameter table: The name of the table that is being queried for indexes - - - returns: A tuple containing an Array of all existing index names in the table, and an Int with the error code or nil if there was no error - */ - public static func existingIndexesForTable(table: String) -> (result: [String], error: Int?) { - - //create the result and error variables + Obtain a list of all existing indexes on a specific table + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - Index error (402) + + :param: table The name of the table that is being queried for indexes + + :returns: A tuple containing an Array of all existing index names in the table, and an Int with the error code or nil if there was no error + */ + public static func existingIndexesForTable(_ table: String) -> (result: [String], error: Int?) { + var result = [String] () var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //execute the statement (result, error) = SQLiteDB.sharedInstance.existingIndexesForTable(table) - - //close the database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return (result, error) + } - - + + // MARK: - Transactions and Savepoints - + /** - Execute commands within a single exclusive transaction - - A connection to the database is opened and is not closed until the end of the transaction. A transaction cannot be embedded into another transaction or savepoint. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - Transaction errors (501 - 502) - - - parameter transactionClosure: A closure containing commands that will execute as part of a single transaction. If the transactionClosure returns true, the changes made within the closure will be committed. If false, the changes will be rolled back and will not be saved. - - - returns: An Int with the error code, or nil if there was no error committing or rolling back the transaction - */ - public static func transaction(transactionClosure: ()->Bool) -> Int? { - - //create the error variable + Execute commands within a single exclusive transaction + + A connection to the database is opened and is not closed until the end of the transaction. A transaction cannot be embedded into another transaction or savepoint. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + - Transaction errors (501 - 502) + + :param: transactionClosure A closure containing commands that will execute as part of a single transaction. If the transactionClosure returns true, the changes made within the closure will be committed. If false, the changes will be rolled back and will not be saved. + + :returns: An Int with the error code, or nil if there was no error committing or rolling back the transaction + */ + public static func transaction(_ transactionClosure: @escaping ()->Bool) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //begin the transaction if let err = SQLiteDB.sharedInstance.beginTransaction() { SQLiteDB.sharedInstance.close() error = err return } - - //execute the transaction closure if transactionClosure() { if let err = SQLiteDB.sharedInstance.commitTransaction() { error = err @@ -884,61 +560,39 @@ public struct SwiftData { error = err } } - - //close the database connection SQLiteDB.sharedInstance.close() - - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } } - + putOnThread(task) return error + } - + /** - Execute commands within a single savepoint - - A connection to the database is opened and is not closed until the end of the savepoint (or the end of the last savepoint, if embedded). - - NOTE: Unlike transactions, savepoints may be embedded into other savepoints or transactions. - - Possible errors returned by this function are: - - - SQLite errors (0 - 101) - - - parameter savepointClosure: A closure containing commands that will execute as part of a single savepoint. If the savepointClosure returns true, the changes made within the closure will be released. If false, the changes will be rolled back and will not be saved. - - - returns: An Int with the error code, or nil if there was no error releasing or rolling back the savepoint - */ - public static func savepoint(savepointClosure: ()->Bool) -> Int? { - - //create the error variable + Execute commands within a single savepoint + + A connection to the database is opened and is not closed until the end of the savepoint (or the end of the last savepoint, if embedded). + + NOTE: Unlike transactions, savepoints may be embedded into other savepoints or transactions. + Possible errors returned by this function are: + - SQLite errors (0 - 101) + + :param: savepointClosure A closure containing commands that will execute as part of a single savepoint. If the savepointClosure returns true, the changes made within the closure will be released. If false, the changes will be rolled back and will not be saved. + + :returns: An Int with the error code, or nil if there was no error releasing or rolling back the savepoint + */ + public static func savepoint(_ savepointClosure: @escaping ()->Bool) -> Int? { + var error: Int? = nil - - //create task closure let task: ()->Void = { - - //open database connection if let err = SQLiteDB.sharedInstance.open() { error = err return } - - //begin the savepoint if let err = SQLiteDB.sharedInstance.beginSavepoint() { SQLiteDB.sharedInstance.close() error = err return } - - //execute the savepoint closure if savepointClosure() { if let err = SQLiteDB.sharedInstance.releaseSavepoint() { error = err @@ -955,388 +609,348 @@ public struct SwiftData { error = err } } - - //close the database connection SQLiteDB.sharedInstance.close() - } - - //execute the task on the current thread if in a transaction, savepoint, or closure. Otherwise, add the task to the database queue - if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { - task() - } else { - dispatch_sync(SQLiteDB.sharedInstance.queue) { - task() - } - } - + putOnThread(task) return error + } - + /** - Convenience function to save a UIImage to disk and return the ID - - - parameter image: The UIImage to be saved - - - returns: The ID of the saved image as a String, or nil if there was an error saving the image to disk - */ - public static func saveUIImage(image: UIImage) -> String? { - - let docsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] - let imageDirPath = docsPath.stringByAppendingPathComponent("SwiftDataImages") - - if !NSFileManager.defaultManager().fileExistsAtPath(imageDirPath) { - do { - try NSFileManager.defaultManager().createDirectoryAtPath(imageDirPath, withIntermediateDirectories: false, attributes: nil) - } catch _ { + Convenience function to save a UIImage to disk and return the ID + :param: image The UIImage to be saved + :returns: The ID of the saved image as a String, or nil if there was an error saving the image to disk + */ + public static func saveUIImage(_ image: UIImage) -> String? { + + let docsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]; + let imageDirPath = docsPath.appendingFormat("/%@", "SwiftDataImages"); //docsPath.stringByAppendingPathComponent("SwiftDataImages") + if !FileManager.default.fileExists(atPath: imageDirPath) { + do + { + try FileManager.default.createDirectory(atPath: imageDirPath, withIntermediateDirectories: false, attributes: nil); + }catch _ as NSError + { print("Error creating SwiftData image folder") return nil + + } + + // if !NSFileManager.defaultManager().createDirectoryAtPath(imageDirPath, withIntermediateDirectories: false, attributes: nil, error: nil) { + // print("Error creating SwiftData image folder") + // return nil + // } + } + let imageID = UUID().uuidString + let imagePath = imageDirPath.appendingFormat("/%@", imageID); //imageDirPath.stringByAppendingPathComponent(imageID) + let imageAsData = UIImagePNGRepresentation(image) + if let _ = imageAsData + { + if !((try? imageAsData!.write(to: URL(fileURLWithPath: imagePath), options: [.atomic])) != nil) { + print("Error saving image") + return nil } } - - let imageID = NSUUID().UUIDString - - let imagePath = imageDirPath.stringByAppendingPathComponent(imageID) - - let imageAsData = UIImagePNGRepresentation(image) - if !imageAsData!.writeToFile(imagePath, atomically: true) { - print("Error saving image") - return nil + else + { + return nil; } - + return imageID - + } - + /** - Convenience function to delete a UIImage with the specified ID - - - parameter id: The id of the UIImage - - - returns: True if the image was successfully deleted, or false if there was an error during the deletion - */ - public static func deleteUIImageWithID(id: String) -> Bool { - - let docsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] - let imageDirPath = docsPath.stringByAppendingPathComponent("SwiftDataImages") - let fullPath = imageDirPath.stringByAppendingPathComponent(id) - - do { - try NSFileManager.defaultManager().removeItemAtPath(fullPath) - return true - } catch _ { - return false - } - + Convenience function to delete a UIImage with the specified ID + + :param: id The id of the UIImage + + :returns: True if the image was successfully deleted, or false if there was an error during the deletion + */ + public static func deleteUIImageWithID(_ id: String) -> Bool { + + let docsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]; + let imageDirPath = docsPath.appendingFormat("/%@", "SwiftDataImages"); //stringByAppendingPathComponent("SwiftDataImages") + let fullPath = imageDirPath.appendingFormat("/%@", id); // stringByAppendingPathComponent(id) + do + { + try FileManager.default.removeItem(atPath: fullPath); + return true; + } + catch _ as NSError + { + return false; + } + //return NSFileManager.defaultManager().removeItemAtPath(fullPath, error: nil) + } - - + + // MARK: - SQLiteDB Class - - private class SQLiteDB { - - //create a single instance of SQLiteDB + + fileprivate class SQLiteDB { + class var sharedInstance: SQLiteDB { struct Singleton { static let instance = SQLiteDB() } return Singleton.instance } - - //declare SQLiteDB properties - var sqliteDB: COpaquePointer = nil + var sqliteDB: OpaquePointer? = nil var dbPath = SQLiteDB.createPath() var inTransaction = false var isConnected = false var openWithFlags = false var savepointsOpen = 0 - let queue = dispatch_queue_create("SwiftData.DatabaseQueue", DISPATCH_QUEUE_SERIAL) - - + let queue = DispatchQueue(label: "SwiftData.DatabaseQueue", attributes: []) + + // MARK: - Database Handling Functions - + //open a connection to the sqlite3 database func open() -> Int? { - - //check if in a transaction + if inTransaction || openWithFlags || savepointsOpen > 0 { return nil } - - //check if connection is already open if sqliteDB != nil || isConnected { return nil } - - //open connection - let status = sqlite3_open(dbPath.cStringUsingEncoding(NSUTF8StringEncoding)!, &sqliteDB) + let status = sqlite3_open(dbPath.cString(using: String.Encoding.utf8)!, &sqliteDB) if status != SQLITE_OK { print("SwiftData Error -> During: Opening Database") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } return Int(status) } - isConnected = true - return nil + } - + //open a connection to the sqlite3 database with flags - func openWithFlags(flags: Int32) -> Int? { - - //check if in transaction + func openWithFlags(_ flags: Int32) -> Int? { + if inTransaction { print("SwiftData Error -> During: Opening Database with Flags") print(" -> Code: 302 - Cannot open a custom connection inside a transaction") return 302 } - - //check if already openWithFlags if openWithFlags { print("SwiftData Error -> During: Opening Database with Flags") print(" -> Code: 301 - A custom connection is already open") return 301 } - - //check if in savepoint if savepointsOpen > 0 { print("SwiftData Error -> During: Opening Database with Flags") print(" -> Code: 303 - Cannot open a custom connection inside a savepoint") return 303 } - - //check if already opened if isConnected { print("SwiftData Error -> During: Opening Database with Flags") print(" -> Code: 301 - A custom connection is already open") return 301 } - - //open the connection - let status = sqlite3_open_v2(dbPath.cStringUsingEncoding(NSUTF8StringEncoding)!, &sqliteDB, flags, nil) + let status = sqlite3_open_v2(dbPath.cString(using: String.Encoding.utf8)!, &sqliteDB, flags, nil) if status != SQLITE_OK { print("SwiftData Error -> During: Opening Database with Flags") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } return Int(status) } - isConnected = true openWithFlags = true - return nil + } - + //close the connection to to the sqlite3 database func close() { - - //check if in transaction + if inTransaction || openWithFlags || savepointsOpen > 0 { return } - - //check if connection is already closed if sqliteDB == nil || !isConnected { return } - - //close connection let status = sqlite3_close(sqliteDB) if status != SQLITE_OK { print("SwiftData Error -> During: Closing Database") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } } - sqliteDB = nil isConnected = false + } - + //close a custom connection to the sqlite3 database func closeCustomConnection() -> Int? { - - //check if in trasaction + if inTransaction { print("SwiftData Error -> During: Closing Database with Flags") print(" -> Code: 305 - Cannot close a custom connection inside a transaction") return 305 } - - //check if in savepoint if savepointsOpen > 0 { print("SwiftData Error -> During: Closing Database with Flags") print(" -> Code: 306 - Cannot close a custom connection inside a savepoint") return 306 } - - //check if no custom connectino open if !openWithFlags { print("SwiftData Error -> During: Closing Database with Flags") print(" -> Code: 304 - A custom connection is not currently open") return 304 } - - //close connection let status = sqlite3_close(sqliteDB) - sqliteDB = nil isConnected = false openWithFlags = false - if status != SQLITE_OK { print("SwiftData Error -> During: Closing Database with Flags") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } return Int(status) } - return nil + } - + //create the database path class func createPath() -> String { - - let docsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] + + let docsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] let databaseStr = "SwiftData.sqlite" - let dbPath = docsPath.stringByAppendingPathComponent(databaseStr) - + let dbPath = docsPath.appendingFormat("/%@", databaseStr); //stringByAppendingPathComponent(databaseStr) return dbPath + } - + //begin a transaction func beginTransaction() -> Int? { - + if savepointsOpen > 0 { print("SwiftData Error -> During: Beginning Transaction") print(" -> Code: 501 - Cannot begin a transaction within a savepoint") return 501 } - if inTransaction { print("SwiftData Error -> During: Beginning Transaction") print(" -> Code: 502 - Cannot begin a transaction within another transaction") return 502 } - if let error = executeChange("BEGIN EXCLUSIVE") { return error } - inTransaction = true - return nil - + } - + //rollback a transaction func rollbackTransaction() -> Int? { - + let error = executeChange("ROLLBACK") - inTransaction = false - return error + } - + //commit a transaction func commitTransaction() -> Int? { - + let error = executeChange("COMMIT") - inTransaction = false - if let err = error { rollbackTransaction() return err } - return nil + } - + //begin a savepoint func beginSavepoint() -> Int? { - + if let error = executeChange("SAVEPOINT 'savepoint\(savepointsOpen + 1)'") { return error } - savepointsOpen += 1 - return nil + } - + //rollback a savepoint func rollbackSavepoint() -> Int? { - return executeChange("ROLLBACK TO 'savepoint\(savepointsOpen)'") } - + //release a savepoint func releaseSavepoint() -> Int? { - + let error = executeChange("RELEASE 'savepoint\(savepointsOpen)'") - savepointsOpen -= 1 - return error + } - + //get last inserted row id func lastInsertedRowID() -> Int { let id = sqlite3_last_insert_rowid(sqliteDB) return Int(id) } - + //number of rows changed by last update func numberOfRowsModified() -> Int { - return Int(sqlite3_changes(sqliteDB)) } - + //return value of column - func getColumnValue(statement: COpaquePointer, index: Int32, type: String) -> AnyObject? { - + func getColumnValue(_ statement: OpaquePointer, index: Int32, type: String) -> AnyObject? { + switch type { case "INT", "INTEGER", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT", "UNSIGNED BIG INT", "INT2", "INT8": if sqlite3_column_type(statement, index) == SQLITE_NULL { return nil } - return Int(sqlite3_column_int(statement, index)) + return Int(sqlite3_column_int(statement, index)) as AnyObject? case "CHARACTER(20)", "VARCHAR(255)", "VARYING CHARACTER(255)", "NCHAR(55)", "NATIVE CHARACTER", "NVARCHAR(100)", "TEXT", "CLOB": - let text = UnsafePointer(sqlite3_column_text(statement, index)) - return String.fromCString(text) + let text = sqlite3_column_text(statement, index) + return String(cString: text!) as AnyObject? case "BLOB", "NONE": let blob = sqlite3_column_blob(statement, index) if blob != nil { let size = sqlite3_column_bytes(statement, index) - return NSData(bytes: blob, length: Int(size)) + + return Data(bytes: blob!, count: Int(size)) as AnyObject? + } return nil case "REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT", "NUMERIC", "DECIMAL(10,5)": if sqlite3_column_type(statement, index) == SQLITE_NULL { return nil } - return Double(sqlite3_column_double(statement, index)) + return Double(sqlite3_column_double(statement, index)) as AnyObject? case "BOOLEAN": if sqlite3_column_type(statement, index) == SQLITE_NULL { return nil } - return sqlite3_column_int(statement, index) != 0 + let val = sqlite3_column_int(statement, index) + return NSNumber(value: val) case "DATE", "DATETIME": - let dateFormatter = NSDateFormatter() + let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - let text = UnsafePointer(sqlite3_column_text(statement, index)) - if let string = String.fromCString(text) { - return dateFormatter.dateFromString(string) + let text = sqlite3_column_text(statement, index) as AnyObject + if let string = String(validatingUTF8: text as! UnsafePointer) { + return dateFormatter.date(from: string) as AnyObject? } print("SwiftData Warning -> The text date at column: \(index) could not be cast as a String, returning nil") return nil @@ -1344,15 +958,20 @@ public struct SwiftData { print("SwiftData Warning -> Column: \(index) is of an unrecognized type, returning nil") return nil } - + } - - + + // func getColumnValue(_ statement: OpaquePointer, index: Int32, type: String) -> AnyObject? { + // let val = sqlite3_column_int(statement, index) + // return NSNumber(value: val) + // } + + // MARK: SQLite Execution Functions - + //execute a SQLite update from a SQL String - func executeChange(sqlStr: String, withArgs: [AnyObject]? = nil) -> Int? { - + func executeChange(_ sqlStr: String, withArgs: [AnyObject]? = nil) -> Int? { + var sql = sqlStr if let args = withArgs { let result = bind(args, toSQL: sql) @@ -1362,40 +981,36 @@ public struct SwiftData { sql = result.string } } - - var pStmt: COpaquePointer = nil + var pStmt: OpaquePointer? = nil var status = sqlite3_prepare_v2(SQLiteDB.sharedInstance.sqliteDB, sql, -1, &pStmt, nil) if status != SQLITE_OK { print("SwiftData Error -> During: SQL Prepare") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } sqlite3_finalize(pStmt) return Int(status) } - status = sqlite3_step(pStmt) if status != SQLITE_DONE && status != SQLITE_OK { print("SwiftData Error -> During: SQL Step") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } sqlite3_finalize(pStmt) return Int(status) } - sqlite3_finalize(pStmt) - return nil + } - + //execute a SQLite query from a SQL String - func executeQuery(sqlStr: String, withArgs: [AnyObject]? = nil) -> (result: [SDRow], error: Int?) { - + func executeQuery(_ sqlStr: String, withArgs: [AnyObject]? = nil) -> (result: [SDRow], error: Int?) { + var resultSet = [SDRow]() - var sql = sqlStr if let args = withArgs { let result = bind(args, toSQL: sql) @@ -1405,19 +1020,17 @@ public struct SwiftData { sql = result.string } } - - var pStmt: COpaquePointer = nil + var pStmt: OpaquePointer? = nil var status = sqlite3_prepare_v2(SQLiteDB.sharedInstance.sqliteDB, sql, -1, &pStmt, nil) if status != SQLITE_OK { print("SwiftData Error -> During: SQL Prepare") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } sqlite3_finalize(pStmt) return (resultSet, Int(status)) } - var columnCount: Int32 = 0 var next = true while next { @@ -1426,24 +1039,25 @@ public struct SwiftData { columnCount = sqlite3_column_count(pStmt) var row = SDRow() for i: Int32 in 0 ..< columnCount { - //for var i: Int32 = 0; i < columnCount; ++i { - let columnName = String.fromCString(sqlite3_column_name(pStmt, i))! + let columnName = String(cString: sqlite3_column_name(pStmt, i)) var columnType = "" switch sqlite3_column_type(pStmt, i) { - case 1: + case SQLITE_INTEGER: columnType = "INTEGER" - case 2: + case SQLITE_FLOAT: columnType = "FLOAT" - case 3: + case SQLITE_TEXT: + columnType = "TEXT" + case SQLITE3_TEXT: columnType = "TEXT" - case 4: + case SQLITE_BLOB: columnType = "BLOB" - case 5: + case SQLITE_NULL: columnType = "NULL" default: columnType = "NULL" } - if let columnValue: AnyObject = getColumnValue(pStmt, index: i, type: columnType) { + if let columnValue: AnyObject = getColumnValue(pStmt!, index: i, type: columnType) { row[columnName] = SDColumn(obj: columnValue) } } @@ -1453,31 +1067,26 @@ public struct SwiftData { } else { print("SwiftData Error -> During: SQL Step") print(" -> Code: \(status) - " + SDError.errorMessageFromCode(Int(status))) - if let errMsg = String.fromCString(sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { + if let errMsg = String(validatingUTF8: sqlite3_errmsg(SQLiteDB.sharedInstance.sqliteDB)) { print(" -> Details: \(errMsg)") } sqlite3_finalize(pStmt) return (resultSet, Int(status)) } - } - sqlite3_finalize(pStmt) - return (resultSet, nil) + } - + } - - + + // MARK: - SDRow - + public struct SDRow { - - //declare properties + var values = [String: SDColumn]() - - //subscript public subscript(key: String) -> SDColumn? { get { return values[key] @@ -1486,128 +1095,132 @@ public struct SwiftData { values[key] = newValue } } - + } - - + + // MARK: - SDColumn - + public struct SDColumn { - - //declare property + var value: AnyObject - - //initialization init(obj: AnyObject) { value = obj } - + //return value by type - + /** - Return the column value as a String - - - returns: An Optional String corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a String, or the value is NULL - */ + Return the column value as a String + :returns: An Optional String corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a String, or the value is NULL + */ public func asString() -> String? { return value as? String } - + /** - Return the column value as an Int - - - returns: An Optional Int corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Int, or the value is NULL - */ + Return the column value as an Int + :returns: An Optional Int corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Int, or the value is NULL + */ public func asInt() -> Int? { return value as? Int } - + /** - Return the column value as a Double - - - returns: An Optional Double corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Double, or the value is NULL - */ + Return the column value as a Double + :returns: An Optional Double corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Double, or the value is NULL + */ public func asDouble() -> Double? { return value as? Double } - + /** - Return the column value as a Bool - - - returns: An Optional Bool corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Bool, or the value is NULL - */ + Return the column value as a Bool + :returns: An Optional Bool corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as a Bool, or the value is NULL + */ public func asBool() -> Bool? { return value as? Bool } - + /** - Return the column value as NSData - - - returns: An Optional NSData object corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as NSData, or the value is NULL - */ - public func asData() -> NSData? { - return value as? NSData + Return the column value as NSData + :returns: An Optional NSData object corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as NSData, or the value is NULL + */ + public func asData() -> Data? { + return value as? Data } - + /** - Return the column value as an NSDate - - - returns: An Optional NSDate corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as an NSDate, or the value is NULL - */ - public func asDate() -> NSDate? { - return value as? NSDate + Return the column value as an NSDate + :returns: An Optional NSDate corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as an NSDate, or the value is NULL + */ + public func asDate() -> Date? { + return value as? Date } - + /** - Return the column value as an AnyObject - - - returns: An Optional AnyObject corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as an AnyObject, or the value is NULL - */ + Return the column value as an AnyObject + :returns: An Optional AnyObject corresponding to the apprioriate column value. Will be nil if: the column name does not exist, the value cannot be cast as an AnyObject, or the value is NULL + */ public func asAnyObject() -> AnyObject? { return value } - + /** - Return the column value path as a UIImage - - - returns: An Optional UIImage corresponding to the path of the apprioriate column value. Will be nil if: the column name does not exist, the value of the specified path cannot be cast as a UIImage, or the value is NULL - */ + Return the column value path as a UIImage + :returns: An Optional UIImage corresponding to the path of the apprioriate column value. Will be nil if: the column name does not exist, the value of the specified path cannot be cast as a UIImage, or the value is NULL + */ public func asUIImage() -> UIImage? { + if let path = value as? String{ - let docsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0] - let imageDirPath = docsPath.stringByAppendingPathComponent("SwiftDataImages") - let fullPath = imageDirPath.stringByAppendingPathComponent(path) - if !NSFileManager.defaultManager().fileExistsAtPath(fullPath) { + let docsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] + let imageDirPath = docsPath.appendingFormat("/%@", "SwiftDataImages");//stringByAppendingPathComponent("SwiftDataImages") + let fullPath = imageDirPath.appendingFormat("/%@", path);//stringByAppendingPathComponent(path) + if !FileManager.default.fileExists(atPath: fullPath) { print("SwiftData Error -> Invalid image ID provided") return nil } - let imageAsData = NSData(contentsOfFile: fullPath) - if let imageAsData = imageAsData { + if let imageAsData = try? Data(contentsOf: URL(fileURLWithPath: fullPath)) { return UIImage(data: imageAsData) } } return nil + } - + } - - + + // MARK: - Error Handling + + fileprivate struct SDError { + + } + +} - private struct SDError { +// MARK: - Threading +extension SwiftData { + + fileprivate static func putOnThread(_ task: ()->Void) { + if SQLiteDB.sharedInstance.inTransaction || SQLiteDB.sharedInstance.savepointsOpen > 0 || SQLiteDB.sharedInstance.openWithFlags { + task() + } else { + SQLiteDB.sharedInstance.queue.sync { + task() + } + } } - + } // MARK: - Escaping And Binding Functions - extension SwiftData.SQLiteDB { - - //bind object - func bind(objects: [AnyObject], toSQL sql: String) -> (string: String, error: Int?) { - + + func bind(_ objects: [AnyObject], toSQL sql: String) -> (string: String, error: Int?) { + var newSql = "" var bindIndex = 0 var i = false @@ -1627,7 +1240,7 @@ extension SwiftData.SQLiteDB { print(" -> Code: 203 - Object to bind as identifier must be a String at array location: \(bindIndex)") return ("", 203) } - newSql = newSql.substringToIndex(newSql.endIndex.predecessor()) + newSql = newSql.substring(to: newSql.characters.index(before: newSql.endIndex)) } else { obj = escapeValue(objects[bindIndex]) } @@ -1642,29 +1255,25 @@ extension SwiftData.SQLiteDB { i = false } } - if bindIndex != objects.count { print("SwiftData Error -> During: Object Binding") print(" -> Code: 202 - Too many objects to bind provided") return ("", 202) } - return (newSql, nil) + } - + //return escaped String value of AnyObject - func escapeValue(obj: AnyObject?) -> String { - + func escapeValue(_ obj: AnyObject?) -> String { + if let obj: AnyObject = obj { - if obj is String { return "'\(escapeStringValue(obj as! String))'" } - if obj is Double || obj is Int { return "\(obj)" } - if obj is Bool { if obj as! Bool { return "1" @@ -1672,8 +1281,7 @@ extension SwiftData.SQLiteDB { return "0" } } - - if obj is NSData { + if obj is Data { let str = "\(obj)" var newStr = "" for char in str.characters { @@ -1681,16 +1289,13 @@ extension SwiftData.SQLiteDB { newStr.append(char) } } - return "X'\(newStr)'" } - - if obj is NSDate { - let dateFormatter = NSDateFormatter() + if obj is Date { + let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - return "\(escapeValue(dateFormatter.stringFromDate(obj as! NSDate)))" + return "\(escapeValue(dateFormatter.string(from: obj as! Date) as AnyObject?))" } - if obj is UIImage { if let imageID = SD.saveUIImage(obj as! UIImage) { return "'\(escapeStringValue(imageID))'" @@ -1698,26 +1303,22 @@ extension SwiftData.SQLiteDB { print("SwiftData Warning -> Cannot save image, NULL will be inserted into the database") return "NULL" } - print("SwiftData Warning -> Object \"\(obj)\" is not a supported type and will be inserted into the database as NULL") return "NULL" - } else { return "NULL" } - + } - + //return escaped String identifier - func escapeIdentifier(obj: String) -> String { - + func escapeIdentifier(_ obj: String) -> String { return "\"\(escapeStringIdentifier(obj))\"" - } - - + + //escape string - func escapeStringValue(str: String) -> String { + func escapeStringValue(_ str: String) -> String { var escapedStr = "" for char in str.characters { if char == "'" { @@ -1725,12 +1326,11 @@ extension SwiftData.SQLiteDB { } escapedStr.append(char) } - return escapedStr } - + //escape string - func escapeStringIdentifier(str: String) -> String { + func escapeStringIdentifier(_ str: String) -> String { var escapedStr = "" for char in str.characters { if char == "\"" { @@ -1738,96 +1338,92 @@ extension SwiftData.SQLiteDB { } escapedStr.append(char) } - return escapedStr } - + } // MARK: - SQL Creation Functions - extension SwiftData { - + /** - Column Data Types - - - parameter StringVal: A column with type String, corresponds to SQLite type "TEXT" - - parameter IntVal: A column with type Int, corresponds to SQLite type "INTEGER" - - parameter DoubleVal: A column with type Double, corresponds to SQLite type "DOUBLE" - - parameter BoolVal: A column with type Bool, corresponds to SQLite type "BOOLEAN" - - parameter DataVal: A column with type NSdata, corresponds to SQLite type "BLOB" - - parameter DateVal: A column with type NSDate, corresponds to SQLite type "DATE" - - parameter UIImageVal: A column with type String (the path value of saved UIImage), corresponds to SQLite type "TEXT" - */ + Column Data Types + + :param: StringVal A column with type String, corresponds to SQLite type "TEXT" + :param: IntVal A column with type Int, corresponds to SQLite type "INTEGER" + :param: DoubleVal A column with type Double, corresponds to SQLite type "DOUBLE" + :param: BoolVal A column with type Bool, corresponds to SQLite type "BOOLEAN" + :param: DataVal A column with type NSdata, corresponds to SQLite type "BLOB" + :param: DateVal A column with type NSDate, corresponds to SQLite type "DATE" + :param: UIImageVal A column with type String (the path value of saved UIImage), corresponds to SQLite type "TEXT" + */ public enum DataType { - - case StringVal - case IntVal - case DoubleVal - case BoolVal - case DataVal - case DateVal - case UIImageVal - - private func toSQL() -> String { - + + case stringVal + case intVal + case doubleVal + case boolVal + case dataVal + case dateVal + case uiImageVal + + fileprivate func toSQL() -> String { + switch self { - - case .StringVal, .UIImageVal: + case .stringVal, .uiImageVal: return "TEXT" - case .IntVal: + case .intVal: return "INTEGER" - case .DoubleVal: + case .doubleVal: return "DOUBLE" - case .BoolVal: + case .boolVal: return "BOOLEAN" - case .DataVal: + case .dataVal: return "BLOB" - case .DateVal: + case .dateVal: return "DATE" } } - + } - + /** - Flags for custom connection to the SQLite database - - - parameter ReadOnly: Opens the SQLite database with the flag "SQLITE_OPEN_READONLY" - - parameter ReadWrite: Opens the SQLite database with the flag "SQLITE_OPEN_READWRITE" - - parameter ReadWriteCreate: Opens the SQLite database with the flag "SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE" - */ + Flags for custom connection to the SQLite database + + :param: ReadOnly Opens the SQLite database with the flag "SQLITE_OPEN_READONLY" + :param: ReadWrite Opens the SQLite database with the flag "SQLITE_OPEN_READWRITE" + :param: ReadWriteCreate Opens the SQLite database with the flag "SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE" + */ public enum Flags { - - case ReadOnly - case ReadWrite - case ReadWriteCreate - - private func toSQL() -> Int32 { - + + case readOnly + case readWrite + case readWriteCreate + + fileprivate func toSQL() -> Int32 { + switch self { - case .ReadOnly: + case .readOnly: return SQLITE_OPEN_READONLY - case .ReadWrite: + case .readWrite: return SQLITE_OPEN_READWRITE - case .ReadWriteCreate: + case .readWriteCreate: return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE } - + } - + } - + } extension SwiftData.SQLiteDB { - + //create a table - func createSQLTable(table: String, withColumnsAndTypes values: [String: SwiftData.DataType]) -> Int? { - - //form the SQLite string for creation of the actual table + func createSQLTable(_ table: String, withColumnsAndTypes values: [String: SwiftData.DataType]) -> Int? { + var sqlStr = "CREATE TABLE \(table) (ID INTEGER PRIMARY KEY AUTOINCREMENT, " var firstRun = true for value in values { @@ -1839,21 +1435,16 @@ extension SwiftData.SQLiteDB { } } sqlStr += ")" - - //execute update to create new table return executeChange(sqlStr) + } - + //delete a table - func deleteSQLTable(table: String) -> Int? { - - //form the SQLite string for deletion of the table + func deleteSQLTable(_ table: String) -> Int? { let sqlStr = "DROP TABLE \(table)" - - //execute update to delete table return executeChange(sqlStr) } - + //get existing table names func existingTables() -> (result: [String], error: Int?) { let sqlStr = "SELECT name FROM sqlite_master WHERE type = 'table'" @@ -1871,19 +1462,17 @@ extension SwiftData.SQLiteDB { return (tableArr, 403) } } - return (tableArr, nil) } - + //create an index - func createIndex(name: String, columns: [String], table: String, unique: Bool) -> Int? { - + func createIndex(_ name: String, columns: [String], table: String, unique: Bool) -> Int? { + if columns.count < 1 { print("SwiftData Error -> During: Creating Index") print(" -> Code: 401 - At least one column name must be provided") return 401 } - var sqlStr = "" if unique { sqlStr = "CREATE UNIQUE INDEX \(name) ON \(table) (" @@ -1900,23 +1489,20 @@ extension SwiftData.SQLiteDB { } } sqlStr += ")" - return executeChange(sqlStr) + } - + //remove an index - func removeIndex(name: String) -> Int? { - + func removeIndex(_ name: String) -> Int? { let sqlStr = "DROP INDEX \(name)" - return executeChange(sqlStr) } - + //obtain list of existing indexes func existingIndexes() -> (result: [String], error: Int?) { - + let sqlStr = "SELECT name FROM sqlite_master WHERE type = 'index'" - var indexArr = [String]() let results = executeQuery(sqlStr) if let err = results.error { @@ -1933,13 +1519,13 @@ extension SwiftData.SQLiteDB { } } return (indexArr, nil) + } - + //obtain list of existing indexes for a specific table - func existingIndexesForTable(table: String) -> (result: [String], error: Int?) { - + func existingIndexesForTable(_ table: String) -> (result: [String], error: Int?) { + let sqlStr = "SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = '\(table)'" - var indexArr = [String]() let results = executeQuery(sqlStr) if let err = results.error { @@ -1955,25 +1541,25 @@ extension SwiftData.SQLiteDB { } } return (indexArr, nil) + } - + } // MARK: - SDError Functions - extension SwiftData.SDError { - + //get the error message from the error code - private static func errorMessageFromCode(errorCode: Int) -> String { - + fileprivate static func errorMessageFromCode(_ errorCode: Int) -> String { + switch errorCode { - - //no error - + + //no error + case -1: return "No error" - + //SQLite error codes and descriptions as per: http://www.sqlite.org/c3ref/c_abort.html case 0: return "Successful result" @@ -2037,20 +1623,20 @@ extension SwiftData.SDError { return "sqlite3_step() has another row ready" case 101: return "sqlite3_step() has finished executing" - - //custom SwiftData errors - - //->binding errors - + + //custom SwiftData errors + + //->binding errors + case 201: return "Not enough objects to bind provided" case 202: return "Too many objects to bind provided" case 203: return "Object to bind as identifier must be a String" - - //->custom connection errors - + + //->custom connection errors + case 301: return "A custom connection is already open" case 302: @@ -2063,84 +1649,32 @@ extension SwiftData.SDError { return "Cannot close a custom connection inside a transaction" case 306: return "Cannot close a custom connection inside a savepoint" - - //->index and table errors - + + //->index and table errors + case 401: return "At least one column name must be provided" case 402: return "Error extracting index names from sqlite_master" case 403: return "Error extracting table names from sqlite_master" - - //->transaction and savepoint errors - + + //->transaction and savepoint errors + case 501: return "Cannot begin a transaction within a savepoint" case 502: return "Cannot begin a transaction within another transaction" - - //unknown error - + + //unknown error + default: //what the fuck happened?!? return "Unknown error" } - + } - + } public typealias SD = SwiftData - -extension String { - - var lastPathComponent: String { - - get { - return (self as NSString).lastPathComponent - } - } - var pathExtension: String { - - get { - - return (self as NSString).pathExtension - } - } - var stringByDeletingLastPathComponent: String { - - get { - - return (self as NSString).stringByDeletingLastPathComponent - } - } - var stringByDeletingPathExtension: String { - - get { - - return (self as NSString).stringByDeletingPathExtension - } - } - var pathComponents: [String] { - - get { - - return (self as NSString).pathComponents - } - } - - func stringByAppendingPathComponent(path: String) -> String { - - let nsSt = self as NSString - - return nsSt.stringByAppendingPathComponent(path) - } - - func stringByAppendingPathExtension(ext: String) -> String? { - - let nsSt = self as NSString - - return nsSt.stringByAppendingPathExtension(ext) - } -} diff --git a/src/ios/SwiftyJson.swift b/src/ios/SwiftyJson.swift index f0397a45..a6e9020e 100644 --- a/src/ios/SwiftyJson.swift +++ b/src/ios/SwiftyJson.swift @@ -1,6 +1,6 @@ // SwiftyJSON.swift // -// Copyright (c) 2014 Ruoyu Fu, Pinglin Tang +// Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -25,378 +25,325 @@ import Foundation // MARK: - Error ///Error domain -public let ErrorDomain: String! = "SwiftyJSONErrorDomain" +public let ErrorDomain: String = "SwiftyJSONErrorDomain" ///Error code -public let ErrorUnsupportedType: Int! = 999 -public let ErrorIndexOutOfBounds: Int! = 900 -public let ErrorWrongType: Int! = 901 -public let ErrorNotExist: Int! = 500 -public let ErrorInvalidJSON: Int! = 490 +public let ErrorUnsupportedType: Int = 999 +public let ErrorIndexOutOfBounds: Int = 900 +public let ErrorWrongType: Int = 901 +public let ErrorNotExist: Int = 500 +public let ErrorInvalidJSON: Int = 490 // MARK: - JSON Type /** -JSON's type definitions. - -See http://tools.ietf.org/html/rfc7231#section-4.3 -*/ -public enum Type :Int{ - - case Number - case String - case Bool - case Array - case Dictionary - case Null - case Unknown + JSON's type definitions. + + See http://www.json.org + */ +public enum Type: Int { + + case number + case string + case bool + case array + case dictionary + case null + case unknown } // MARK: - JSON Base - public struct JSON { /** - Creates a JSON using the data. + Creates a JSON using the data. - - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary - - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. - - parameter error: error The NSErrorPointer used to return the error. `nil` by default. + - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary + - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. + - parameter error: The NSErrorPointer used to return the error. `nil` by default. - - returns: The created JSON - */ - public init(data:NSData, options opt: NSJSONReadingOptions = .AllowFragments, error: NSErrorPointer = nil) { + - returns: The created JSON + */ + public init(data: Data, options opt: JSONSerialization.ReadingOptions = .allowFragments, error: NSErrorPointer = nil) { do { - let object: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: opt) - self.init(object) + let object: Any = try JSONSerialization.jsonObject(with: data, options: opt) + self.init(jsonObject: object) } catch let aError as NSError { if error != nil { - error.memory = aError + error?.pointee = aError } - self.init(NSNull()) + self.init(jsonObject: NSNull()) } } /** - Creates a JSON using the object. + Creates a JSON object + - parameter object: the object + - note: this does not parse a `String` into JSON, instead use `init(parseJSON: String)` + - returns: the created JSON object + */ + public init(_ object: Any) { + switch object { + case let object as Data: + self.init(data: object) + default: + self.init(jsonObject: object) + } + } - - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. + /** + Parses the JSON string into a JSON object + - parameter json: the JSON string + - returns: the created JSON object + */ + public init(parseJSON jsonString: String) { + if let data = jsonString.data(using: .utf8) { + self.init(data) + } else { + self.init(NSNull()) + } + } - - returns: The created JSON - */ - public init(_ object: AnyObject) { - self.object = object + /** + Creates a JSON from JSON string + - parameter string: Normal json string like '{"a":"b"}' + + - returns: The created JSON + */ + @available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`") + public static func parse(_ json: String) -> JSON { + return json.data(using: String.Encoding.utf8) + .flatMap { JSON(data: $0) } ?? JSON(NSNull()) } /** - Creates a JSON from a [JSON] + Creates a JSON using the object. - - parameter jsonArray: A Swift array of JSON objects + - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. - - returns: The created JSON - */ - public init(_ jsonArray:[JSON]) { - self.init(jsonArray.map { $0.object }) + - returns: The created JSON + */ + fileprivate init(jsonObject: Any) { + self.object = jsonObject } /** - Creates a JSON from a [String: JSON] + Merges another JSON into this JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONs getting merged the same way. - - parameter jsonDictionary: A Swift dictionary of JSON objects + - parameter other: The JSON which gets merged into this JSON + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + */ + public mutating func merge(with other: JSON) throws { + try self.merge(with: other, typecheck: true) + } - - returns: The created JSON - */ - public init(_ jsonDictionary:[String: JSON]) { - var dictionary = [String: AnyObject]() - for (key, json) in jsonDictionary { - dictionary[key] = json.object + /** + Merges another JSON into this JSON and returns a new JSON, whereas primitive values which are not present in this JSON are getting added, + present values getting overwritten, array values getting appended and nested JSONS getting merged the same way. + + - parameter other: The JSON which gets merged into this JSON + - returns: New merged JSON + - throws `ErrorWrongType` if the other JSONs differs in type on the top level. + */ + public func merged(with other: JSON) throws -> JSON { + var merged = self + try merged.merge(with: other, typecheck: true) + return merged + } + + // Private woker function which does the actual merging + // Typecheck is set to true for the first recursion level to prevent total override of the source JSON + fileprivate mutating func merge(with other: JSON, typecheck: Bool) throws { + if self.type == other.type { + switch self.type { + case .dictionary: + for (key, _) in other { + try self[key].merge(with: other[key], typecheck: false) + } + case .array: + self = JSON(self.arrayValue + other.arrayValue) + default: + self = other + } + } else { + if typecheck { + throw NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Couldn't merge, because the JSONs differ in type on top level."]) + } else { + self = other + } } - self.init(dictionary) } /// Private object - private var rawArray: [AnyObject] = [] - private var rawDictionary: [String : AnyObject] = [:] - private var rawString: String = "" - private var rawNumber: NSNumber = 0 - private var rawNull: NSNull = NSNull() + fileprivate var rawArray: [Any] = [] + fileprivate var rawDictionary: [String : Any] = [:] + fileprivate var rawString: String = "" + fileprivate var rawNumber: NSNumber = 0 + fileprivate var rawNull: NSNull = NSNull() + fileprivate var rawBool: Bool = false /// Private type - private var _type: Type = .Null + fileprivate var _type: Type = .null /// prviate error - private var _error: NSError? = nil + fileprivate var _error: NSError? /// Object in JSON - public var object: AnyObject { + public var object: Any { get { switch self.type { - case .Array: + case .array: return self.rawArray - case .Dictionary: + case .dictionary: return self.rawDictionary - case .String: + case .string: return self.rawString - case .Number: - return self.rawNumber - case .Bool: + case .number: return self.rawNumber + case .bool: + return self.rawBool default: return self.rawNull } } set { _error = nil - switch newValue { + switch unwrap(newValue) { case let number as NSNumber: if number.isBool { - _type = .Bool + _type = .bool + self.rawBool = number.boolValue } else { - _type = .Number + _type = .number + self.rawNumber = number } - self.rawNumber = number - case let string as String: - _type = .String + case let string as String: + _type = .string self.rawString = string - case _ as NSNull: - _type = .Null - case let array as [AnyObject]: - _type = .Array + case _ as NSNull: + _type = .null + case nil: + _type = .null + case let array as [Any]: + _type = .array self.rawArray = array - case let dictionary as [String : AnyObject]: - _type = .Dictionary + case let dictionary as [String : Any]: + _type = .dictionary self.rawDictionary = dictionary default: - _type = .Unknown + _type = .unknown _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"]) } } } - /// json type - public var type: Type { get { return _type } } + /// JSON type + public var type: Type { return _type } /// Error in JSON - public var error: NSError? { get { return self._error } } + public var error: NSError? { return self._error } - /// The static null json - @available(*, unavailable, renamed="null") - public static var nullJSON: JSON { get { return null } } - public static var null: JSON { get { return JSON(NSNull()) } } + /// The static null JSON + @available(*, unavailable, renamed:"null") + public static var nullJSON: JSON { return null } + public static var null: JSON { return JSON(NSNull()) } } -// MARK: - CollectionType, SequenceType, Indexable -extension JSON : Swift.CollectionType, Swift.SequenceType, Swift.Indexable { - - public typealias Generator = JSONGenerator - - public typealias Index = JSONIndex - - public var startIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.startIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.startIndex) - default: - return JSONIndex() - } - } - - public var endIndex: JSON.Index { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.rawArray.endIndex) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.rawDictionary.endIndex) - default: - return JSONIndex() - } - } - - public subscript (position: JSON.Index) -> JSON.Generator.Element { - switch self.type { - case .Array: - return (String(position.arrayIndex), JSON(self.rawArray[position.arrayIndex!])) - case .Dictionary: - let (key, value) = self.rawDictionary[position.dictionaryIndex!] - return (key, JSON(value)) - default: - return ("", JSON.null) - } - } - - /// If `type` is `.Array` or `.Dictionary`, return `array.empty` or `dictonary.empty` otherwise return `true`. - public var isEmpty: Bool { - get { - switch self.type { - case .Array: - return self.rawArray.isEmpty - case .Dictionary: - return self.rawDictionary.isEmpty - default: - return true - } - } +// unwrap nested JSON +private func unwrap(_ object: Any) -> Any { + switch object { + case let json as JSON: + return unwrap(json.object) + case let array as [Any]: + return array.map(unwrap) + case let dictionary as [String : Any]: + var unwrappedDic = dictionary + for (k, v) in dictionary { + unwrappedDic[k] = unwrap(v) + } + return unwrappedDic + default: + return object } +} - /// If `type` is `.Array` or `.Dictionary`, return `array.count` or `dictonary.count` otherwise return `0`. - public var count: Int { - switch self.type { - case .Array: - return self.rawArray.count - case .Dictionary: - return self.rawDictionary.count +public enum Index: Comparable { + case array(Int) + case dictionary(DictionaryIndex) + case null + + static public func == (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): + return left == right + case (.dictionary(let left), .dictionary(let right)): + return left == right + case (.null, .null): return true default: - return 0 + return false } } - public func underestimateCount() -> Int { - switch self.type { - case .Array: - return self.rawArray.underestimateCount() - case .Dictionary: - return self.rawDictionary.underestimateCount() + static public func < (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): + return left < right + case (.dictionary(let left), .dictionary(let right)): + return left < right default: - return 0 + return false } } - - /** - If `type` is `.Array` or `.Dictionary`, return a generator over the elements like `Array` or `Dictionary`, otherwise return a generator over empty. - - - returns: Return a *generator* over the elements of JSON. - */ - public func generate() -> JSON.Generator { - return JSON.Generator(self) - } } -public struct JSONIndex: ForwardIndexType, _Incrementable, Equatable, Comparable { - - let arrayIndex: Int? - let dictionaryIndex: DictionaryIndex? +public typealias JSONIndex = Index +public typealias JSONRawIndex = Index - let type: Type +extension JSON: Swift.Collection { - init(){ - self.arrayIndex = nil - self.dictionaryIndex = nil - self.type = .Unknown - } - - init(arrayIndex: Int) { - self.arrayIndex = arrayIndex - self.dictionaryIndex = nil - self.type = .Array - } + public typealias Index = JSONRawIndex - init(dictionaryIndex: DictionaryIndex) { - self.arrayIndex = nil - self.dictionaryIndex = dictionaryIndex - self.type = .Dictionary - } - - public func successor() -> JSONIndex { - switch self.type { - case .Array: - return JSONIndex(arrayIndex: self.arrayIndex!.successor()) - case .Dictionary: - return JSONIndex(dictionaryIndex: self.dictionaryIndex!.successor()) + public var startIndex: Index { + switch type { + case .array: + return .array(rawArray.startIndex) + case .dictionary: + return .dictionary(rawDictionary.startIndex) default: - return JSONIndex() + return .null } } -} - -public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex == rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex == rhs.dictionaryIndex - default: - return false - } -} - -public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex < rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex < rhs.dictionaryIndex - default: - return false - } -} - -public func <=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex <= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex <= rhs.dictionaryIndex - default: - return false - } -} -public func >=(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex >= rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex >= rhs.dictionaryIndex - default: - return false - } -} - -public func >(lhs: JSONIndex, rhs: JSONIndex) -> Bool { - switch (lhs.type, rhs.type) { - case (.Array, .Array): - return lhs.arrayIndex > rhs.arrayIndex - case (.Dictionary, .Dictionary): - return lhs.dictionaryIndex > rhs.dictionaryIndex - default: - return false + public var endIndex: Index { + switch type { + case .array: + return .array(rawArray.endIndex) + case .dictionary: + return .dictionary(rawDictionary.endIndex) + default: + return .null + } } -} - -public struct JSONGenerator : GeneratorType { - - public typealias Element = (String, JSON) - - private let type: Type - private var dictionayGenerate: DictionaryGenerator? - private var arrayGenerate: IndexingGenerator<[AnyObject]>? - private var arrayIndex: Int = 0 - init(_ json: JSON) { - self.type = json.type - if type == .Array { - self.arrayGenerate = json.rawArray.generate() - }else { - self.dictionayGenerate = json.rawDictionary.generate() + public func index(after i: Index) -> Index { + switch i { + case .array(let idx): + return .array(rawArray.index(after: idx)) + case .dictionary(let idx): + return .dictionary(rawDictionary.index(after: idx)) + default: + return .null } } - public mutating func next() -> JSONGenerator.Element? { - switch self.type { - case .Array: - if let o = self.arrayGenerate!.next() { - return (String(self.arrayIndex++), JSON(o)) - } else { - return nil - } - case .Dictionary: - if let (k, v): (String, AnyObject) = self.dictionayGenerate!.next() { - return (k, JSON(v)) - } else { - return nil - } + public subscript (position: Index) -> (String, JSON) { + switch position { + case .array(let idx): + return (String(idx), JSON(self.rawArray[idx])) + case .dictionary(let idx): + let (key, value) = self.rawDictionary[idx] + return (key, JSON(value)) default: - return nil + return ("", JSON.null) } } } @@ -404,35 +351,35 @@ public struct JSONGenerator : GeneratorType { // MARK: - Subscript /** -* To mark both String and Int can be used in subscript. -*/ + * To mark both String and Int can be used in subscript. + */ public enum JSONKey { - case Index(Int) - case Key(String) + case index(Int) + case key(String) } public protocol JSONSubscriptType { - var jsonKey:JSONKey { get } + var jsonKey: JSONKey { get } } extension Int: JSONSubscriptType { - public var jsonKey:JSONKey { - return JSONKey.Index(self) + public var jsonKey: JSONKey { + return JSONKey.index(self) } } extension String: JSONSubscriptType { - public var jsonKey:JSONKey { - return JSONKey.Key(self) + public var jsonKey: JSONKey { + return JSONKey.key(self) } } extension JSON { - /// If `type` is `.Array`, return json which's object is `array[index]`, otherwise return null json with error. - private subscript(index index: Int) -> JSON { + /// If `type` is `.Array`, return json whose object is `array[index]`, otherwise return null json with error. + fileprivate subscript(index index: Int) -> JSON { get { - if self.type != .Array { + if self.type != .array { var r = JSON.null r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"]) return r @@ -440,12 +387,12 @@ extension JSON { return JSON(self.rawArray[index]) } else { var r = JSON.null - r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) + r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) return r } } set { - if self.type == .Array { + if self.type == .array { if self.rawArray.count > index && newValue.error == nil { self.rawArray[index] = newValue.object } @@ -453,11 +400,11 @@ extension JSON { } } - /// If `type` is `.Dictionary`, return json which's object is `dictionary[key]` , otherwise return null json with error. - private subscript(key key: String) -> JSON { + /// If `type` is `.Dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. + fileprivate subscript(key key: String) -> JSON { get { var r = JSON.null - if self.type == .Dictionary { + if self.type == .dictionary { if let o = self.rawDictionary[key] { r = JSON(o) } else { @@ -469,41 +416,41 @@ extension JSON { return r } set { - if self.type == .Dictionary && newValue.error == nil { + if self.type == .dictionary && newValue.error == nil { self.rawDictionary[key] = newValue.object } } } /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. - private subscript(sub sub: JSONSubscriptType) -> JSON { + fileprivate subscript(sub sub: JSONSubscriptType) -> JSON { get { switch sub.jsonKey { - case .Index(let index): return self[index: index] - case .Key(let key): return self[key: key] + case .index(let index): return self[index: index] + case .key(let key): return self[key: key] } } set { switch sub.jsonKey { - case .Index(let index): self[index: index] = newValue - case .Key(let key): self[key: key] = newValue + case .index(let index): self[index: index] = newValue + case .key(let key): self[key: key] = newValue } } } /** - Find a json in the complex data structuresby using the Int/String's array. + Find a json in the complex data structures by using array of Int and/or String as path. - - parameter path: The target json's path. Example: + - parameter path: The target json's path. Example: - let json = JSON[data] - let path = [9,"list","person","name"] - let name = json[path] + let json = JSON[data] + let path = [9,"list","person","name"] + let name = json[path] - The same as: let name = json[9]["list"]["person"]["name"] + The same as: let name = json[9]["list"]["person"]["name"] - - returns: Return a json found by the path or a null json with error - */ + - returns: Return a json found by the path or a null json with error + */ public subscript(path: [JSONSubscriptType]) -> JSON { get { return path.reduce(self) { $0[sub: $1] } @@ -515,7 +462,7 @@ extension JSON { case 1: self[sub:path[0]].object = newValue.object default: - var aPath = path; aPath.removeAtIndex(0) + var aPath = path; aPath.remove(at: 0) var nextJSON = self[sub: path[0]] nextJSON[aPath] = newValue self[sub: path[0]] = nextJSON @@ -524,16 +471,16 @@ extension JSON { } /** - Find a json in the complex data structuresby using the Int/String's array. + Find a json in the complex data structures by using array of Int and/or String as path. - - parameter path: The target json's path. Example: + - parameter path: The target json's path. Example: - let name = json[9,"list","person","name"] + let name = json[9,"list","person","name"] - The same as: let name = json[9]["list"]["person"]["name"] + The same as: let name = json[9]["list"]["person"]["name"] - - returns: Return a json found by the path or a null json with error - */ + - returns: Return a json found by the path or a null json with error + */ public subscript(path: JSONSubscriptType...) -> JSON { get { return self[path] @@ -546,64 +493,64 @@ extension JSON { // MARK: - LiteralConvertible -extension JSON: Swift.StringLiteralConvertible { +extension JSON: Swift.ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { - self.init(value) + self.init(value as Any) } public init(extendedGraphemeClusterLiteral value: StringLiteralType) { - self.init(value) + self.init(value as Any) } public init(unicodeScalarLiteral value: StringLiteralType) { - self.init(value) + self.init(value as Any) } } -extension JSON: Swift.IntegerLiteralConvertible { +extension JSON: Swift.ExpressibleByIntegerLiteral { public init(integerLiteral value: IntegerLiteralType) { - self.init(value) + self.init(value as Any) } } -extension JSON: Swift.BooleanLiteralConvertible { +extension JSON: Swift.ExpressibleByBooleanLiteral { public init(booleanLiteral value: BooleanLiteralType) { - self.init(value) + self.init(value as Any) } } -extension JSON: Swift.FloatLiteralConvertible { +extension JSON: Swift.ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { - self.init(value) + self.init(value as Any) } } -extension JSON: Swift.DictionaryLiteralConvertible { - - public init(dictionaryLiteral elements: (String, AnyObject)...) { - self.init(elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in - var d = dictionary - d[element.0] = element.1 - return d - }) +extension JSON: Swift.ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, Any)...) { + var dictionary = [String: Any](minimumCapacity: elements.count) + for (k, v) in elements { + dictionary[k] = v + } + self.init(dictionary as Any) } } -extension JSON: Swift.ArrayLiteralConvertible { +extension JSON: Swift.ExpressibleByArrayLiteral { - public init(arrayLiteral elements: AnyObject...) { - self.init(elements) + public init(arrayLiteral elements: Any...) { + self.init(elements as Any) } } -extension JSON: Swift.NilLiteralConvertible { +extension JSON: Swift.ExpressibleByNilLiteral { + @available(*, deprecated, message: "use JSON.null instead. Will be removed in future versions") public init(nilLiteral: ()) { - self.init(NSNull()) + self.init(NSNull() as Any) } } @@ -611,42 +558,127 @@ extension JSON: Swift.NilLiteralConvertible { extension JSON: Swift.RawRepresentable { - public init?(rawValue: AnyObject) { - if JSON(rawValue).type == .Unknown { + public init?(rawValue: Any) { + if JSON(rawValue).type == .unknown { return nil } else { self.init(rawValue) } } - public var rawValue: AnyObject { + public var rawValue: Any { return self.object } - public func rawData(options opt: NSJSONWritingOptions = NSJSONWritingOptions(rawValue: 0)) throws -> NSData { - guard NSJSONSerialization.isValidJSONObject(self.object) else { + public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data { + guard JSONSerialization.isValidJSONObject(self.object) else { throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"]) } - return try NSJSONSerialization.dataWithJSONObject(self.object, options: opt) - } - - public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String? { + return try JSONSerialization.data(withJSONObject: self.object, options: opt) + } + + public func rawString(_ encoding: String.Encoding = .utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? { + do { + return try _rawString(encoding, options: [.jsonSerialization: opt]) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + public func rawString(_ options: [writingOptionsKeys: Any]) -> String? { + let encoding = options[.encoding] as? String.Encoding ?? String.Encoding.utf8 + let maxObjectDepth = options[.maxObjextDepth] as? Int ?? 10 + do { + return try _rawString(encoding, options: options, maxObjectDepth: maxObjectDepth) + } catch { + print("Could not serialize object to JSON because:", error.localizedDescription) + return nil + } + } + + fileprivate func _rawString( + _ encoding: String.Encoding = .utf8, + options: [writingOptionsKeys: Any], + maxObjectDepth: Int = 10 + ) throws -> String? { + if maxObjectDepth < 0 { + throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "Element too deep. Increase maxObjectDepth and make sure there is no reference loop"]) + } switch self.type { - case .Array, .Dictionary: + case .dictionary: + do { + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try self.rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let dict = self.object as? [String: Any?] else { + return nil + } + let body = try dict.keys.map { key throws -> String in + guard let value = dict[key] else { + return "\"\(key)\": null" + } + guard let unwrappedValue = value else { + return "\"\(key)\": null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "Could not serialize nested JSON"]) + } + if nestedValue.type == .string { + return "\"\(key)\": \"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return "\"\(key)\": \(nestedString)" + } + } + + return "{\(body.joined(separator: ","))}" + } catch _ { + return nil + } + case .array: do { - let data = try self.rawData(options: opt) - return NSString(data: data, encoding: encoding) as? String + if !(options[.castNilToNSNull] as? Bool ?? false) { + let jsonOption = options[.jsonSerialization] as? JSONSerialization.WritingOptions ?? JSONSerialization.WritingOptions.prettyPrinted + let data = try self.rawData(options: jsonOption) + return String(data: data, encoding: encoding) + } + + guard let array = self.object as? [Any?] else { + return nil + } + let body = try array.map { value throws -> String in + guard let unwrappedValue = value else { + return "null" + } + + let nestedValue = JSON(unwrappedValue) + guard let nestedString = try nestedValue._rawString(encoding, options: options, maxObjectDepth: maxObjectDepth - 1) else { + throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "Could not serialize nested JSON"]) + } + if nestedValue.type == .string { + return "\"\(nestedString.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "\"", with: "\\\""))\"" + } else { + return nestedString + } + } + + return "[\(body.joined(separator: ","))]" } catch _ { return nil } - case .String: + case .string: return self.rawString - case .Number: + case .number: return self.rawNumber.stringValue - case .Bool: - return self.rawNumber.boolValue.description - case .Null: + case .bool: + return self.rawBool.description + case .null: return "null" default: return nil @@ -656,10 +688,10 @@ extension JSON: Swift.RawRepresentable { // MARK: - Printable, DebugPrintable -extension JSON: Swift.Printable, Swift.DebugPrintable { +extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible { public var description: String { - if let string = self.rawString(options:.PrettyPrinted) { + if let string = self.rawString(options:.prettyPrinted) { return string } else { return "unknown" @@ -677,27 +709,23 @@ extension JSON { //Optional [JSON] public var array: [JSON]? { - get { - if self.type == .Array { - return self.rawArray.map{ JSON($0) } - } else { - return nil - } + if self.type == .array { + return self.rawArray.map { JSON($0) } + } else { + return nil } } //Non-optional [JSON] public var arrayValue: [JSON] { - get { - return self.array ?? [] - } + return self.array ?? [] } - //Optional [AnyObject] - public var arrayObject: [AnyObject]? { + //Optional [Any] + public var arrayObject: [Any]? { get { switch self.type { - case .Array: + case .array: return self.rawArray default: return nil @@ -705,7 +733,7 @@ extension JSON { } set { if let array = newValue { - self.object = array + self.object = array as Any } else { self.object = NSNull() } @@ -719,12 +747,12 @@ extension JSON { //Optional [String : JSON] public var dictionary: [String : JSON]? { - if self.type == .Dictionary { - return self.rawDictionary.reduce([String : JSON]()) { (dictionary: [String : JSON], element: (String, AnyObject)) -> [String : JSON] in - var d = dictionary - d[element.0] = JSON(element.1) - return d + if self.type == .dictionary { + var d = [String: JSON](minimumCapacity: rawDictionary.count) + for (key, value) in rawDictionary { + d[key] = JSON(value) } + return d } else { return nil } @@ -735,11 +763,12 @@ extension JSON { return self.dictionary ?? [:] } - //Optional [String : AnyObject] - public var dictionaryObject: [String : AnyObject]? { + //Optional [String : Any] + + public var dictionaryObject: [String : Any]? { get { switch self.type { - case .Dictionary: + case .dictionary: return self.rawDictionary default: return nil @@ -747,7 +776,7 @@ extension JSON { } set { if let v = newValue { - self.object = v + self.object = v as Any } else { self.object = NSNull() } @@ -757,21 +786,21 @@ extension JSON { // MARK: - Bool -extension JSON: Swift.BooleanType { +extension JSON { // : Swift.Bool //Optional bool public var bool: Bool? { get { switch self.type { - case .Bool: - return self.rawNumber.boolValue + case .bool: + return self.rawBool default: return nil } } set { - if newValue != nil { - self.object = NSNumber(bool: newValue!) + if let newValue = newValue { + self.object = newValue as Bool } else { self.object = NSNull() } @@ -782,14 +811,20 @@ extension JSON: Swift.BooleanType { public var boolValue: Bool { get { switch self.type { - case .Bool, .Number, .String: - return self.object.boolValue + case .bool: + return self.rawBool + case .number: + return self.rawNumber.boolValue + case .string: + return ["true", "y", "t"].contains { (truthyString) in + return self.rawString.caseInsensitiveCompare(truthyString) == .orderedSame + } default: return false } } set { - self.object = NSNumber(bool: newValue) + self.object = newValue } } } @@ -802,15 +837,15 @@ extension JSON { public var string: String? { get { switch self.type { - case .String: + case .string: return self.object as? String default: return nil } } set { - if newValue != nil { - self.object = NSString(string:newValue!) + if let newValue = newValue { + self.object = NSString(string:newValue) } else { self.object = NSNull() } @@ -821,12 +856,12 @@ extension JSON { public var stringValue: String { get { switch self.type { - case .String: - return self.object as! String - case .Number: - return self.object.stringValue - case .Bool: - return (self.object as! Bool).description + case .string: + return self.object as? String ?? "" + case .number: + return self.rawNumber.stringValue + case .bool: + return (self.object as? Bool).map { String($0) } ?? "" default: return "" } @@ -844,8 +879,10 @@ extension JSON { public var number: NSNumber? { get { switch self.type { - case .Number, .Bool: + case .number: return self.rawNumber + case .bool: + return NSNumber(value: self.rawBool ? 1 : 0) default: return nil } @@ -859,16 +896,18 @@ extension JSON { public var numberValue: NSNumber { get { switch self.type { - case .String: + case .string: let decimal = NSDecimalNumber(string: self.object as? String) - if decimal == NSDecimalNumber.notANumber() { // indicates parse error - return NSDecimalNumber.zero() + if decimal == NSDecimalNumber.notANumber { // indicates parse error + return NSDecimalNumber.zero } return decimal - case .Number, .Bool: - return self.object as! NSNumber + case .number: + return self.object as? NSNumber ?? NSNumber(value: 0) + case .bool: + return NSNumber(value: self.rawBool ? 1 : 0) default: - return NSNumber(double: 0.0) + return NSNumber(value: 0.0) } } set { @@ -877,13 +916,13 @@ extension JSON { } } -//MARK: - Null +// MARK: - Null extension JSON { public var null: NSNull? { get { switch self.type { - case .Null: + case .null: return self.rawNull default: return nil @@ -893,24 +932,30 @@ extension JSON { self.object = NSNull() } } - public func isExists() -> Bool{ - if let errorValue = error where errorValue.code == ErrorNotExist{ - return false + public func exists() -> Bool { + if let errorValue = error, errorValue.code == ErrorNotExist || + errorValue.code == ErrorIndexOutOfBounds || + errorValue.code == ErrorWrongType { + return false } return true } } -//MARK: - URL +// MARK: - URL extension JSON { //Optional URL - public var URL: NSURL? { + public var url: URL? { get { switch self.type { - case .String: - if let encodedString_ = self.rawString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) { - return NSURL(string: encodedString_) + case .string: + // Check for existing percent escapes first to prevent double-escaping of % character + if let _ = self.rawString.range(of: "%[0-9A-Fa-f]{2}", options: .regularExpression, range: nil, locale: nil) { + return Foundation.URL(string: self.rawString) + } else if let encodedString_ = self.rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { + // We have to use `Foundation.URL` otherwise it conflicts with the variable name. + return Foundation.URL(string: encodedString_) } else { return nil } @@ -933,8 +978,8 @@ extension JSON { return self.number?.doubleValue } set { - if newValue != nil { - self.object = NSNumber(double: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -946,7 +991,7 @@ extension JSON { return self.numberValue.doubleValue } set { - self.object = NSNumber(double: newValue) + self.object = NSNumber(value: newValue) } } @@ -955,8 +1000,8 @@ extension JSON { return self.number?.floatValue } set { - if newValue != nil { - self.object = NSNumber(float: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -968,17 +1013,17 @@ extension JSON { return self.numberValue.floatValue } set { - self.object = NSNumber(float: newValue) + self.object = NSNumber(value: newValue) } } public var int: Int? { get { - return self.number?.longValue + return self.number?.intValue } set { - if newValue != nil { - self.object = NSNumber(integer: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -987,20 +1032,20 @@ extension JSON { public var intValue: Int { get { - return self.numberValue.integerValue + return self.numberValue.intValue } set { - self.object = NSNumber(integer: newValue) + self.object = NSNumber(value: newValue) } } public var uInt: UInt? { get { - return self.number?.unsignedLongValue + return self.number?.uintValue } set { - if newValue != nil { - self.object = NSNumber(unsignedLong: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1009,20 +1054,20 @@ extension JSON { public var uIntValue: UInt { get { - return self.numberValue.unsignedLongValue + return self.numberValue.uintValue } set { - self.object = NSNumber(unsignedLong: newValue) + self.object = NSNumber(value: newValue) } } public var int8: Int8? { get { - return self.number?.charValue + return self.number?.int8Value } set { - if newValue != nil { - self.object = NSNumber(char: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: Int(newValue)) } else { self.object = NSNull() } @@ -1031,20 +1076,20 @@ extension JSON { public var int8Value: Int8 { get { - return self.numberValue.charValue + return self.numberValue.int8Value } set { - self.object = NSNumber(char: newValue) + self.object = NSNumber(value: Int(newValue)) } } public var uInt8: UInt8? { get { - return self.number?.unsignedCharValue + return self.number?.uint8Value } set { - if newValue != nil { - self.object = NSNumber(unsignedChar: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1053,20 +1098,20 @@ extension JSON { public var uInt8Value: UInt8 { get { - return self.numberValue.unsignedCharValue + return self.numberValue.uint8Value } set { - self.object = NSNumber(unsignedChar: newValue) + self.object = NSNumber(value: newValue) } } public var int16: Int16? { get { - return self.number?.shortValue + return self.number?.int16Value } set { - if newValue != nil { - self.object = NSNumber(short: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1075,20 +1120,20 @@ extension JSON { public var int16Value: Int16 { get { - return self.numberValue.shortValue + return self.numberValue.int16Value } set { - self.object = NSNumber(short: newValue) + self.object = NSNumber(value: newValue) } } public var uInt16: UInt16? { get { - return self.number?.unsignedShortValue + return self.number?.uint16Value } set { - if newValue != nil { - self.object = NSNumber(unsignedShort: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1097,20 +1142,20 @@ extension JSON { public var uInt16Value: UInt16 { get { - return self.numberValue.unsignedShortValue + return self.numberValue.uint16Value } set { - self.object = NSNumber(unsignedShort: newValue) + self.object = NSNumber(value: newValue) } } public var int32: Int32? { get { - return self.number?.intValue + return self.number?.int32Value } set { - if newValue != nil { - self.object = NSNumber(int: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1119,20 +1164,20 @@ extension JSON { public var int32Value: Int32 { get { - return self.numberValue.intValue + return self.numberValue.int32Value } set { - self.object = NSNumber(int: newValue) + self.object = NSNumber(value: newValue) } } public var uInt32: UInt32? { get { - return self.number?.unsignedIntValue + return self.number?.uint32Value } set { - if newValue != nil { - self.object = NSNumber(unsignedInt: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1141,20 +1186,20 @@ extension JSON { public var uInt32Value: UInt32 { get { - return self.numberValue.unsignedIntValue + return self.numberValue.uint32Value } set { - self.object = NSNumber(unsignedInt: newValue) + self.object = NSNumber(value: newValue) } } public var int64: Int64? { get { - return self.number?.longLongValue + return self.number?.int64Value } set { - if newValue != nil { - self.object = NSNumber(longLong: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1163,20 +1208,20 @@ extension JSON { public var int64Value: Int64 { get { - return self.numberValue.longLongValue + return self.numberValue.int64Value } set { - self.object = NSNumber(longLong: newValue) + self.object = NSNumber(value: newValue) } } public var uInt64: UInt64? { get { - return self.number?.unsignedLongLongValue + return self.number?.uint64Value } set { - if newValue != nil { - self.object = NSNumber(unsignedLongLong: newValue!) + if let newValue = newValue { + self.object = NSNumber(value: newValue) } else { self.object = NSNull() } @@ -1185,138 +1230,135 @@ extension JSON { public var uInt64Value: UInt64 { get { - return self.numberValue.unsignedLongLongValue + return self.numberValue.uint64Value } set { - self.object = NSNumber(unsignedLongLong: newValue) + self.object = NSNumber(value: newValue) } } } -//MARK: - Comparable +// MARK: - Comparable extension JSON : Swift.Comparable {} -public func ==(lhs: JSON, rhs: JSON) -> Bool { +public func == (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { - case (.Number, .Number): + case (.number, .number): return lhs.rawNumber == rhs.rawNumber - case (.String, .String): + case (.string, .string): return lhs.rawString == rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): + case (.bool, .bool): + return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): + case (.null, .null): return true default: return false } } -public func <=(lhs: JSON, rhs: JSON) -> Bool { +public func <= (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { - case (.Number, .Number): + case (.number, .number): return lhs.rawNumber <= rhs.rawNumber - case (.String, .String): + case (.string, .string): return lhs.rawString <= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): + case (.bool, .bool): + return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): + case (.null, .null): return true default: return false } } -public func >=(lhs: JSON, rhs: JSON) -> Bool { +public func >= (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { - case (.Number, .Number): + case (.number, .number): return lhs.rawNumber >= rhs.rawNumber - case (.String, .String): + case (.string, .string): return lhs.rawString >= rhs.rawString - case (.Bool, .Bool): - return lhs.rawNumber.boolValue == rhs.rawNumber.boolValue - case (.Array, .Array): + case (.bool, .bool): + return lhs.rawBool == rhs.rawBool + case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.Dictionary, .Dictionary): + case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.Null, .Null): + case (.null, .null): return true default: return false } } -public func >(lhs: JSON, rhs: JSON) -> Bool { +public func > (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { - case (.Number, .Number): + case (.number, .number): return lhs.rawNumber > rhs.rawNumber - case (.String, .String): + case (.string, .string): return lhs.rawString > rhs.rawString default: return false } } -public func <(lhs: JSON, rhs: JSON) -> Bool { +public func < (lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { - case (.Number, .Number): + case (.number, .number): return lhs.rawNumber < rhs.rawNumber - case (.String, .String): + case (.string, .string): return lhs.rawString < rhs.rawString default: return false } } -private let trueNumber = NSNumber(bool: true) -private let falseNumber = NSNumber(bool: false) -private let trueObjCType = String.fromCString(trueNumber.objCType) -private let falseObjCType = String.fromCString(falseNumber.objCType) +private let trueNumber = NSNumber(value: true) +private let falseNumber = NSNumber(value: false) +private let trueObjCType = String(cString: trueNumber.objCType) +private let falseObjCType = String(cString: falseNumber.objCType) // MARK: - NSNumber: Comparable extension NSNumber { - var isBool:Bool { - get { - let objCType = String.fromCString(self.objCType) - if (self.compare(trueNumber) == NSComparisonResult.OrderedSame && objCType == trueObjCType) - || (self.compare(falseNumber) == NSComparisonResult.OrderedSame && objCType == falseObjCType){ - return true - } else { - return false - } + var isBool: Bool { + let objCType = String(cString: self.objCType) + if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { + return true + } else { + return false } } } -public func ==(lhs: NSNumber, rhs: NSNumber) -> Bool { +func == (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: - return lhs.compare(rhs) == NSComparisonResult.OrderedSame + return lhs.compare(rhs) == .orderedSame } } -public func !=(lhs: NSNumber, rhs: NSNumber) -> Bool { +func != (lhs: NSNumber, rhs: NSNumber) -> Bool { return !(lhs == rhs) } -public func <(lhs: NSNumber, rhs: NSNumber) -> Bool { +func < (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): @@ -1324,11 +1366,11 @@ public func <(lhs: NSNumber, rhs: NSNumber) -> Bool { case (true, false): return false default: - return lhs.compare(rhs) == NSComparisonResult.OrderedAscending + return lhs.compare(rhs) == .orderedAscending } } -public func >(lhs: NSNumber, rhs: NSNumber) -> Bool { +func > (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): @@ -1336,11 +1378,11 @@ public func >(lhs: NSNumber, rhs: NSNumber) -> Bool { case (true, false): return false default: - return lhs.compare(rhs) == NSComparisonResult.OrderedDescending + return lhs.compare(rhs) == ComparisonResult.orderedDescending } } -public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { +func <= (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): @@ -1348,11 +1390,11 @@ public func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { case (true, false): return false default: - return lhs.compare(rhs) != NSComparisonResult.OrderedDescending + return lhs.compare(rhs) != .orderedDescending } } -public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { +func >= (lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): @@ -1360,6 +1402,13 @@ public func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { case (true, false): return false default: - return lhs.compare(rhs) != NSComparisonResult.OrderedAscending + return lhs.compare(rhs) != .orderedAscending } } + +public enum writingOptionsKeys { + case jsonSerialization + case castNilToNSNull + case maxObjextDepth + case encoding +} From c57c722f1c4e84ea4e7ee720942dab4f6e51bd2b Mon Sep 17 00:00:00 2001 From: Lee Pender Date: Sat, 6 May 2017 02:36:34 -0400 Subject: [PATCH 3/3] Fix bridging header not loaded correctly --- plugin.xml | 1 + scripts/iosAddBridgingHeader.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 scripts/iosAddBridgingHeader.js diff --git a/plugin.xml b/plugin.xml index 83b2100d..22ed28d8 100644 --- a/plugin.xml +++ b/plugin.xml @@ -111,6 +111,7 @@ + ${EXECUTABLE_NAME} Would Like to Use Your Current Location Even In Background. diff --git a/scripts/iosAddBridgingHeader.js b/scripts/iosAddBridgingHeader.js new file mode 100644 index 00000000..670aa492 --- /dev/null +++ b/scripts/iosAddBridgingHeader.js @@ -0,0 +1,28 @@ +module.exports = function(context) { + var fs = context.requireCordovaModule('fs'); + var path = context.requireCordovaModule('path'); + var cordova_util = context.requireCordovaModule('cordova-lib/src/cordova/util.js'); + var ConfigParser = context.requireCordovaModule('cordova-common').ConfigParser; + + var projectRoot = context.opts.projectRoot; + + var configXml = cordova_util.projectConfig(projectRoot); + var config = new ConfigParser(configXml); + var projectName = config.name(); + + var platformRoot = path.join(context.opts.projectRoot, 'platforms/ios'); + var projectBridgingHeaderPath = path.join(platformRoot, projectName, + 'Bridging-Header.h'); + + var pluginId = context.opts.plugin.id; + var pluginBridgingHeaderFilename = "Geofence-Plugin" + '-Bridging-Header.h'; + var importDirective = '#import "' + pluginBridgingHeaderFilename + '"'; + + var data = fs.readFileSync(projectBridgingHeaderPath, {'encoding': 'utf8'}); + + var regExp = new RegExp("^" + importDirective + "$", "m"); + + if (!regExp.test(data)) { + fs.appendFileSync(projectBridgingHeaderPath, importDirective + "\n"); + } +}