I am creating an app that needs to save a counter variable (which is an integer) into the cloud when the app exits, then loads the counter when the app becomes active. Ive never used CloudKit before could someone simplify how i could do this using swift? many of the examples I've tried to replicate are too complex for what I am trying to achieve.
Note: Before anyone mentions it , I know there are other ways to achieve this but I want to do it using CloudKit.
Also, I already understand how appDelegate transitions work so i don't need help with that :)
CloudKit: Ok lets do some planning and get out assumptions agreed.
You need to check the network is up and reachable
You need check said user is logged into the cloud
Unclear as to the nature of what your really want to do here beyond writing a noddy method; lets assuming you want something a bit more.
You save your integer using cloud kit, ensuring any errors that come thru are handled. What sort of errors. Here a list for you.
enum CKErrorCode : Int {
case InternalError
case PartialFailure
case NetworkUnavailable
case NetworkFailure
case BadContainer
case ServiceUnavailable
case RequestRateLimited
case MissingEntitlement
case NotAuthenticated
case PermissionFailure
case UnknownItem
case InvalidArguments
case ResultsTruncated
case ServerRecordChanged
case ServerRejectedRequest
case AssetFileNotFound
case AssetFileModified
case IncompatibleVersion
case ConstraintViolation
case OperationCancelled
case ChangeTokenExpired
case BatchRequestFailed
case ZoneBusy
case BadDatabase
case QuotaExceeded
case ZoneNotFound
case LimitExceeded
case UserDeletedZone
}
You might want to read the thing back to check it even if you don't get any errors, it is a very important integer. You need to handle these errors if you do that too.
OK, you saved it; what about next time. Ok its the same palaver, network, cloud kit, read, deal with errors etc etc.
If your still here, well done. Here the code just to save a record.
func save2Cloud(yourInt:Int) {
let container = CKContainer(identifier: "iCloud.blah")
let publicDB = container.publicCloudDatabase
let newRecord = CKRecord(recordType: "BlahBlah")
newRecord.setObject(yourInt, forKey: "theInt")
var localChanges:[CKRecord] = []
var recordIDsToDelete:[CKRecord] = []
localChanges.append(newRecord)
let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: nil)
saveRecordsOperation.perRecordCompletionBlock = { record, error in
if error != nil {
self.showAlert(message: error!.localizedDescription)
print(error!.localizedDescription)
}
dispatch_async(dispatch_get_main_queue()) {
// give the UI a all good sign for that record
}
}
saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
if error != nil {
self.showAlert(message: error!.localizedDescription)
print(error!.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
// give the UI a all good sign for all records
}
}
}
saveRecordsOperation.qualityOfService = .Background
publicDB.addOperation(saveRecordsOperation)
}
And here the code to read it back.
var readerOperation: CKQueryOperation!
func read4Cloud(theLink: String, theCount: Int) {
var starCount:Int = 0
let container = CKContainer(identifier: "iCloud.blah")
let publicDB = container.publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "BlahBlah", predicate: predicate)
readerOperation = CKQueryOperation(query: query)
readerOperation.recordFetchedBlock = { (record) in
let YourInt = record["theInt"] as! Int
}
readerOperation.queryCompletionBlock = {(cursor, error) in
if error != nil {
// oh dingbats, you need to check for one of those errors
} else {
// got it
}
}
readerOperation.qualityOfService = .Background
publicDB.addOperation(readerOperation)
}
But wait Matt, this is going to save a new record everytime, and read back multiple Ints when you re-open. No this solution needs some more work; and I haven't done the network or the cloud check or any of the errors... :\
Disclaimer; I edited this code in SO, it may not compile cleanly :)
Matt, the reason you should do this another way is cause it is far simpler and will almost certainly be more reliable than cloud kit.
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(yourInt, forKey: "blah")
Your got these options in swift ...
func setBool(value: Bool, forKey defaultName: String)
func setInteger(value: Int, forKey defaultName: String)
func setFloat(value: Float, forKey defaultName: String)
func setDouble(value: Double, forKey defaultName: String)
func setObject(value: AnyObject?, forKey defaultName: String)
func setURL(url: NSURL, forKey defaultName: String)
To fetch the value the next time
let defaults = NSUserDefaults.standardUserDefaults()
if let yourInt = defaults.integerForKey("blah")
{
print(yourInt)
}
You got a few more methods to get them back
func boolForKey(defaultName: String) -> Bool
func integerForKey(defaultName: String) -> Int
func floatForKey(defaultName: String) -> Float
func doubleForKey(defaultName: String) -> Double
func objectForKey(defaultName: String) -> AnyObject?
func URLForKey(defaultName: String) -> NSURL?
func dataForKey(defaultName: String) -> NSData?
func stringForKey(defaultName: String) -> String?
func stringArrayForKey(defaultName: String) -> [AnyObject]?
func arrayForKey(defaultName: String) -> [AnyObject]?
func dictionaryForKey(defaultName: String) -> [NSObject : AnyObject]?
And that is it; but wait, you want to use cloud kit... let me give you a second answer after I post this.
Related
I have 2 functions where one basically retrieves string in User Defaults and another writes the string in User Defaults. I'm not able to understand that should I do the unit test or no? and I'm pretty new to the concept for Unit testing.
I scoured the internet for testing user defaults but I was advised to mock the test in the end.
My question is If there is a way to test User Defaults what is the best way to do it?
Constants and Structs
let defaults = UserDefaults.standard
let defaultinformation = "ABCDEFG"
struct Keys {
static let Information = "Information"
}
Function where saves Information
func SetDefaultInformation() {
defaults.set(defaultinformation, forKey: Keys.Information)
}
Function where retrieves Information
func checkForInformation() -> String {
let Information = defaults.value(forKey: Keys.Information) as? String ?? ""
return Information
}
Thanks in Advance
should I do the unit test or no
No. You know what UserDefaults does and you know how it works and that it works. It is ridiculous to test Apple's code. Testing defaults.set is just as silly; you know exactly what it does and you know that it will do it.
What you want to test is your code: not your retrieval from UserDefaults per se, but your response to that retrieval. Give yourself methods such that you can see what you do when information is a String and what you do when information is nil. As you've been told, a trivial mock can supply the back end, playing the part of UserDefaults and giving back different sorts of result. Just don't let your tests involve the real UserDefaults.
class ViewModel {
func saveUserName(name: String, userDefaults: UserDefaults = UserDefaults.standard) {
userDefaults.set(name, forKey: "username")
}
}
class MockUserDefault: UserDefaults {
var persistedUserName: String? = nil
var persistenceKey: String? = nil
override func set(_ value: Any?, forKey defaultName: String) {
persistedUserName = value as? String
persistenceKey = defaultName
}
}
func testSavingUserName() {
let viewModel = ViewModel()
let mockUserDefaults = MockUserDefault()
viewModel.saveUserName(name: "Oliver", userDefaults: mockUserDefaults)
XCTAssertEqual("Oliver", mockUserDefaults.persistedUserName)
XCTAssertEqual("username", mockUserDefaults.persistenceKey)
}
I am having difficulties with making this function pass through optional values, I am using an #escaping closure, but the issue arises where my code demands a certain parameter. So in this instance, I am trying to upload two images. However, the third parameter is giving me trouble. How can I make it so that a certain parameter(s) is optional/doesn't need to be called. And I can inject the data into a certain parameter if it does exist?
This is my code -
static func uploadImagesToFirebaseStorage(data: Data? = nil, secondData: Data? = nil, thirdData: Data? = nil, onSuccess: #escaping (_ imageURL: String, _ secondImageURL: String?, _ thirdImageURL: String?) -> Void) {
let firstPhotoIdString = NSUUID().uuidString
let secondPhotoIdString = NSUUID().uuidString
let thirdPhotoIdString = NSUUID().uuidString
let storageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(firstPhotoIdString)
storageRef.putData(data!, metadata: nil) { (metadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
return
}
let secondStorageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(secondPhotoIdString)
secondStorageRef.putData(secondData!, metadata: nil) { (secondMetadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
secondStorageRef.setValue(nil, forKey: secondPhotoIdString)
return
}
let thirdStorageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(thirdPhotoIdString)
thirdStorageRef.putData(thirdData!, metadata: nil) { (thirdMetadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
return
}
if let firstPhotoURL = metadata?.downloadURL()?.absoluteString, let secondPhotoURL = secondMetadata?.downloadURL()?.absoluteString, let thirdPhotoURL = thirdMetadata?.downloadURL()?.absoluteString {
onSuccess(firstPhotoURL, secondPhotoURL, thirdPhotoURL)
}
}
}
}
}
I will do conditional checks to see if there is valid data to pass through but I want this type of outcome:
If value doesn't exist I do not need to include a certain parameter(s), after the conditional checks have been done
If it doesn't exist, forget about that parameter and carry on
I have found that if can make multiple instances of the same code and change the parameters, but I will need 20 instances, and as you can tell, that would not be efficient what so ever. So how can I have all parameters optional and pass data through without the code needing/demand every parameter?
I know a similar question has been asked, but I read it and didn't find it useful.
Thank you.
EDIT - More information
I am trying to upload images to Firebase. I have three parameters (for 3 images), however, I would like the function to be scalable in terms of it doesn't matter how many images I have, I can upload accordingly.
This is my call to the function:
static func uploadDataToServer(data: Data, secondData: Data? = nil, thirdData: Data? = nil, firstVideoURL: URL? = nil, secondVideoURL: URL? = nil, thirdVideoURL: URL? = nil, caption: String, onSuccess: #escaping () -> Void) {
if let secondImageData = secondData {
uploadImagesToFirebaseStorage(data: data, secondData: secondImageData) { (firstPhotoURL, secondPhotoURL, nil ) in
self.sendDataToDatabase(firstPhotoURL: firstPhotoURL, secondPhotoURL: secondPhotoURL, caption: caption, onSuccess: onSuccess)
}
}
}
}
In the above code, I am trying to upload two images, even thought I have three parameters hence why I set all but the first data AKA first image to nil. My issue is the code either expects a third image or it will crash because there is no third image. I inject the image from the ViewController class by passing the image (from the ImagePickerController) and using UIImageJPEGRepresentation().
So basically I have a function that connects to Firebase and gets data in the form of a string and returns a string(at least I think).
this question Is very simple and kind of a dumb question but how would I call this method in a different thread or core. Sorry don't really know the terms yet. I am trying.
also, I don't think it returns a String which I need so how would I accomplish that?
also this is not a duplicate because if you look at all these type of questions I have not found one that calls the actual method.
typealias someting = (String?) -> Void
func getOpposingUsername( _ index: Int, completionHandler: #escaping someting) {
var opposingUser: String = ""
self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
guard let dict = snapshot.value as? [String: AnyHashable] else {
return
}
opposingUser = dict["OpposingUsername"] as! String
if opposingUser.isEmpty {
completionHandler(nil)
} else {
completionHandler(opposingUser)
}
})
}
Changing threads would be something like this DispatchQueue.main.async {your code} here is the documentation on GCD Dispatch.
Here is an example of a function that returns a string,
func stringReturn() -> String {
let aString = "a string"
return aString
}
I am trying to generate .aiff files using NSSpeechSynthesizer.startSpeakingString() and am using GCd using a serial queue as NSSpeechSynthesizer takes in a string and creates an aiff file at a specified NSURL address. I used the standard for loop method for a list of strings in a [String:[String]] but this creates some files which have 0 bytes.
Here is the function to generate the speech:
func createSpeech(type: String, name: String) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
print("Attempting to save speech \(name).aiff")
self.synth.startSpeakingString(name, toURL: URL)
}catch{
print("error occured")
}
}
}
And here is the function that traverses the dictionary to create the files:
for key in self.nodeLibrary.keys{
dispatch_sync(GlobalBackgroundQueue){
let type = self.nodeLibrary[key]?.0
let name = key.componentsSeparatedByString("_")[0]
if !speechCheck.contains(name){
mixer.createSpeech(type!, name: name)
}
}
}
The globalBackgroundQueue is an alias to the GCD queue call _T for readability.
The routine runs fine, creates folders and subfolders as required by another external function then synthesizes the speech but in my case I always get one or some which don't load properly, giving 0 bytes or a too small number of bytes which makes the file unuseable.
I read the following post and have been using these GCD methods for a while but I'm not sure where I'm wrong here:
http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
Any help greatly appreciated as usual
edit: Updated with completion closure and found possibly a bug
I have created a closure function as below and use it in another helper method which checks for any errors such as sourceFile.length being 0 once loaded. However, all files exhibit a 0 length which is not possible as I checked each file's audio properties using finder's property command+i.
func synthesise(type: String, name: String, completion: (success: Bool)->()) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
let success = self.synth.startSpeakingString(name, toURL: URL)
completion(success: success)
}catch{
print("error occured")
}
}
}
func loadSpeech(type: String, name: String){
synthesise(type, name: name, completion: {(success: Bool)->Void in
if success{
print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity")
let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff")
do{
let source = try AVAudioFile(forReading: URL)
print("File has length: \(source.)")
}catch{
print("error loading file")
}
}else{
print("creation unsuccessful, trying again")
self.loadSpeech(type, name: name)
}
})
}
The files are generated with their folders and both the method startSpeakingString->Bool and the delegate function I have in my class which updates the synthSuccess property show true. So I load an AVAudioFile to check its length. All file lengths are 0. Which they are not except for one.
When I say bug, this is from another part of the app where I load an AVAudioEngine and start loading buffers with the frameCount argument set to sourceAudioFile.length which gives a diagnostic error but this is out of context right now.
startSpeakingString(_:toURL:) will start an asynchronous task in the background. Effectively, your code starts a number of asynchronous tasks that run concurrently. This may be the cause of the problem that you experience.
A solution would need to ensure that only one task is active at a time.
The problem with startSpeakingString(_:toURL:) is, that it starts an asynchronous task - but the function itself provides no means to get notified when this task is finished.
However, there's a delegate which you need to setup in order to be notified.
So, your solution will require to define a NSSpeechSynthesizerDelegate.
You may want to create your own helper class that exposes an asynchronous function which has a completion handler:
func exportSpeakingString(string: String, url: NSURL,
completion: (NSURL?, ErrorType?) -> ())
Internally, the class creates an instance of NSSpeechSynthesizer and NSSpeechSynthesizerDelegate and implements the delegate methods accordingly.
To complete the challenge, you need to search for an approach to run several asynchronous functions sequentially. There are already solutions on SO.
Edit:
I setup my own project to either confirm or neglect a possible issue in the NSSpeechSynthesizer system framework. So far, may own tests confirm that NSSpeechSynthesizer works as expected.
However, there are few subtleties worth mentioning:
Ensure you create a valid file URL which you pass as an argument to parameter URL in method startSpeakingString(:toURL:).
Ensure you choose an extension for the output file which is known by NSSpeechSynthesizer and the system frameworks playing this file, for example .aiff. Unfortunately, the documentation is quite lacking here - so I had to trial and error. The list of supported audio file formats by QuickTime may help here. Still, I have no idea how NSSpeechSynthesizer selects the output format.
The following two classes compose a simple easy to use library:
import Foundation
import AppKit
enum SpeechSynthesizerError: ErrorType {
case ErrorActive
case ErrorURL(message: String)
case ErrorUnknown
}
internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate {
typealias CompletionFunc = (NSURL?, ErrorType?) -> ()
private let synthesizer = NSSpeechSynthesizer(voice: nil)!
private var _completion: CompletionFunc?
private var _url: NSURL?
override init() {
super.init()
synthesizer.delegate = self
}
// CAUTION: This call is not thread-safe! Ensure that multiple method invocations
// will be called from the same thread!
// Only _one_ task can be active at a time.
internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) {
guard _completion == nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorActive)
}
return
}
guard output.path != nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL."))
}
return
}
_completion = completion
_url = output
if !synthesizer.startSpeakingString(input, toURL: output) {
fatalError("Could not start speeaking")
}
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakWord characterRange: NSRange,
ofString string: String)
{
NSLog("willSpeakWord")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakPhoneme phonemeOpcode: Int16)
{
NSLog("willSpeakPhoneme")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didEncounterErrorAtIndex characterIndex: Int,
ofString string: String,
message: String)
{
NSLog("didEncounterErrorAtIndex")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didFinishSpeaking finishedSpeaking: Bool)
{
assert(self._url != nil)
assert(self._url!.path != nil)
assert(self._completion != nil)
var error: ErrorType?
if !finishedSpeaking {
do {
error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError
} catch let err {
error = err
}
}
let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil
let completion = self._completion!
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
if url == nil && error == nil {
error = SpeechSynthesizerError.ErrorUnknown
}
completion(url, error)
}
_completion = nil
_url = nil
}
}
public struct SpeechSynthesizer {
public init() {}
private let _synthesizer = InternalSpeechSynthesizer()
public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) -> ()) {
_synthesizer.synthesize(input, output: output) { (url, error) in
completion(url, error)
}
}
}
You can use it as shown below:
func testExample() {
let expect = self.expectationWithDescription("future should be fulfilled")
let synth = SpeechSynthesizer()
let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff")
synth.synthesize("Hello World!", output: url) { (url, error) in
if let url = url {
print("URL: \(url)")
}
if let error = error {
print("Error: \(error)")
}
expect.fulfill()
}
self.waitForExpectationsWithTimeout(1000, handler: nil)
// Test: output file should exist.
}
In the code above, check the result of the call to synth.startSpeakingString(name, toURL: URL), which can return false if the synthesiser could not start speaking. If it fails, find out why, or just retry it.
Plus, add [NSSpeechSynthesiserDelegate][1], and look for the speechSynthesizer:didFinishSpeaking: callbacks there. When the synthesiser thinks it has finished speaking, check the file size. If it is zero, retry the operation.
I'm switching on a flag from the server in order to determine what type of object to instantiate. Each type is a subclass of the return type (Snack, in the example.) My guess is that the whole subclass thing is irrelevant to the main issue, but I include it for completeness.
The trouble is that I'm pretty consistently getting a crash reported from Crashlytics on the case "chips": line. In order to simplify parsing in my initializers, I'm wrapping the server response in a SwiftyJSON JSON. This all worked fine in testing.
class func fromJSON(json: JSON) -> Snack {
switch json["SnackName"] {
case "chips": // CRASH OCCURS HERE
return BagOChips(json: json)
case "apple":
return Apple(json: json)
default:
return Spam(json: json)
}
}
Specifically, the crash is occurring at "SwiftyJSON.swift:1013" (marked below). Crashlytics describes it as "EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x0000000093a4bec8" and "swift_unknownRetain + 32".
public func ==(lhs: JSON, rhs: JSON) -> Bool {
switch (lhs.type, rhs.type) {
case (.Number, .Number):
return (lhs.object as NSNumber) == (rhs.object as NSNumber)
case (.String, .String):
return (lhs.object as String) == (rhs.object as String) // CRASH REALLY OCCURS HERE
case (.Bool, .Bool):
return (lhs.object as Bool) == (rhs.object as Bool)
case (.Array, .Array):
return (lhs.object as NSArray) == (rhs.object as NSArray)
case (.Dictionary, .Dictionary):
return (lhs.object as NSDictionary) == (rhs.object as NSDictionary)
case (.Null, .Null):
return true
default:
return false
}
}
Any idea why this is failing and what I might be able to do to correct it in our next release?
Found the problem, and boy was it obscure!
TL;DR - Avoid SwiftyJSON's == function entirely by replacing
switch json["SnackName"]
with
switch json["SnackName"].stringValue
That's probably a good idea in general, but the reason it's necessary appears to be a bug deep in the bowels of how Swift + Foundation handle strings. I've filed an open radar here.
All it takes to reproduce this problem is Xcode 6.1, SwiftyJSON, and the following sample code that I submitted to Apple:
let d = NSDictionary(dictionary: ["foo": "bar"])
let j = JSON(d)
switch (j["foo"]) {
case "bar":
println("> No crash!")
default:
println("> default")
}
Then throw in these logging statements in your copy of SwiftyJSON.
public func ==(lhs: JSON, rhs: JSON) -> Bool {
// Next 2 lines added just for SwiftyCrasher test project.
println( "> Left: \(_stdlib_getTypeName(lhs.object))" )
println( "> Right: \(_stdlib_getTypeName(rhs.object))" )
switch (lhs.type, rhs.type) {
case (.Number, .Number):
return (lhs.object as NSNumber) == (rhs.object as NSNumber)
case (.String, .String):
...
}
This shows the following console output, just before the crash:
> Left: _TtCSs19_NSContiguousString
> Right: _TtCSs19_NSContiguousString
Again, in Debug mode, this doesn't crash. Boxing "foo" and "bar" in NSString, or changing j["foo"] to j["foo"].stringValue also prevent a crash.