SwiftKeychainWrapper xctest returning nil - keychain

I'm trying to do a basic unit test with the keychain. I can set the value for a key successfully ('setString' returns true), however, when I try to retrieve the value, even after a 5 second delay, the return is still nil:
class MyKeychainTest: XCTestCase {
func checkKeychain(timer: NSTimer) {
debugPrint("check keychain...")
let userInfo = timer.userInfo as! [String: AnyObject]
let expectation = userInfo["expectation"] as! XCTestExpectation
let res = KeychainWrapper.objectForKey("myKey")
debugPrint("got res: \(res)")
XCTAssertNotNil(res)
expectation.fulfill()
}
func testKeychain() {
let expectation = expectationWithDescription("gotKey")
let success = KeychainWrapper.setString("foo", forKey: "myKey")
debugPrint("set key?: \(success)")
NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(self.checkKeychain)
, userInfo: ["expectation": expectation ], repeats: false)
self.waitForExpectationsWithTimeout(10, handler: nil)
}
}
Any ideas on what can cause this?
Thanks

Accidentally used the wrong method to get the key. Instead of KeychainWrapper.objectForKey("myKey") using KeychainWrapper.stringForKey("myKey") works. Strange that an object isn't even returned though in the former case

Related

Swift: post only specific codable objects

I am trying to work out how to upload only certain values via a put request. Essentially, I have an app whereby when the user accesses a specific screen, there are a bunch of fields, some of whose values will be pre-populated from an API (precisely which values are already populated is unpredictable). Any blank fields are editable and the user can put their own values in and upload them via a put request. However, I only want to upload new values (that were essentially 'nil' in the original API response). I do not want to override the values that came with the initial response and 're-upload' them. Here is the codable struct which essentially represents the body of the request:
struct UpdateRequest: Codable {
let total: Double?
let discrep: Double?
let percent: Double?
let pre: Double?
let target: Double?
let req: Double?
let dep: Double?
}
So all values are optional.
Here is my update method which is called once the user has completed the 'blanks' and pressed a specific button:
func updateVals() {
let args = ["id": viewModel.id]
guard let body = convertValues() else { return }
API.client.post(.order, with: args, using: .put, posting: body, expecting: MessageResponse.self) { (success, response) in
switch success {
case .failure:
DispatchQueue.asyncMain {
let viewController = WarningViewController.detailsUpdatedFailure()
self.present(viewController, animated: true, completion: nil)
}
case .success:
DispatchQueue.asyncMain {
let viewController = WarningViewController.detailsUpdatedSuccess()
self.present(viewController, animated: true, completion: nil)
}
}
}
}
}
And here is convertedValues() :
func convertValues() -> UpdateRequest? {
// In here I have a bunch of logic to convert values to specific units which is not relevant to this question
{
return UpdateRequest(
total: convertedTotal
discrep: convertedDiscrep
percent: convertedPercent
pre: convertedPre
target: convertedTarget
req: convertedReq
dep: convertedDep
)
}
return nil
}
However, if 'total' for example was in the initial API response I do not want to upload it to this request - as this end point is essentially the same as the 'get' endpoint from which I am populating these initial values.
Obviously I can write logic to check if the values were in the response, but how can I only upload new values to this UpdateRequest without uploading 'nil' values for the ones I don't need?
All you need to do is to override encode(to:) in UpdateRequest and use encodeIfPresent since that method will not include values that are nil.
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(total, forKey: .total)
try container.encodeIfPresent(discrep, forKey: .discrep)
//... and so on
}
Example with only total set
let req = UpdateRequest(total: 123.5, discrep: nil, percent: nil, pre: nil, target: nil, req: nil, dep: nil)
do {
let data = try JSONEncoder().encode(req)
print(String(data: data, encoding: .utf8))
} catch {
print(error)
}
outputs
Optional("{\"total\":123.5}")

Swift: How to observe if screen is locked in macOS

I want to detect if the user has locked his screen (in macOS) using Swift.
Based on this answer I’ve created the following code:
import Cocoa
import Quartz
if let dict = Quartz.CGSessionCopyCurrentDictionary() as? [String : Any] {
let locked = dict["CGSSessionScreenIsLocked"]
print(locked as? String ?? "")
}
...which seems to work fine if I explicitly run the code.
But how is it possible to observe the value so I get notified when the value got changed?
You can observe distributed notifications. They are not documented.
let dnc = DistributedNotificationCenter.default()
let lockObserver = dnc.addObserver(forName: .init("com.apple.screenIsLocked"),
object: nil, queue: .main) { _ in
NSLog("Screen Locked")
}
let unlockObserver = dnc.addObserver(forName: .init("com.apple.screenIsUnlocked"),
object: nil, queue: .main) { _ in
NSLog("Screen Unlocked")
}
With Combine (available on macOS 10.15+):
import Combine
var bag = Set<AnyCancellable>()
let dnc = DistributedNotificationCenter.default()
dnc.publisher(for: Notification.Name(rawValue: "com.apple.screenIsLocked"))
.sink { _ in print("Screen Locked") }
.store(in: &bag)
dnc.publisher(for: Notification.Name(rawValue: "com.apple.screenIsUnlocked"))
.sink { _ in print("Screen Unlocked") }
.store(in: &bag)

Where to put Firebase Performance trace

I am trying to determine what the best location would be for putting a firebase Performance trace. I want to see how long it is taking my app to pull data.
In my VC I have the following
func pullAllUsersCards() {
// 1 Start Here?
FirebaseUtility.shared.getCards { (cards, errMessage) in
if let theCards = cards {
if theCards.count < 1 {
if let addVC = self.storyboard?.instantiateViewController(withIdentifier: StoryboardKeys.addCardViewControllerStoryboardID) as? AddCardViewController {
let addNavigation = UINavigationController(rootViewController: addVC)
if UIDevice.current.userInterfaceIdiom == .pad {
self.splitViewController?.present(addNavigation, animated: true, completion: nil)
} else {
self.present(addNavigation, animated: true, completion: nil)
}
}
} else {
// 2 Start Here?
MBProgressHUD.showAdded(to: self.view, animated: true)
self.cardArray = theCards
self.tableView.reloadData()
MBProgressHUD.hide(for: self.view, animated: true)
}
}
}
}
Originally I wanted to put the trace on my singleton class FirebaseUtility where the getCards method is.
func getCards(completion: #escaping (_ cards: [Card]?, _ errorMessage: String?) -> Void) {
// let testTrace = Performance.startTrace(name: "Test")
guard let userID = user?.uid else {
let error = "Unknown error occured! User is not logged in."
completion(nil, error)
return
}
let userCardRef = ref.child(FirebaseKeys.newCards).child(userID)
userCardRef.observe(.value, with: { (snapshot) in // changed from Single Event
let enumerator = snapshot.children
var cards = [Card]()
while let cardSnapshot = enumerator.nextObject() as? DataSnapshot {
if let cardDict = cardSnapshot.value as? [String : Any] {
let card = Card(id: cardSnapshot.key, cardDict: cardDict)
cards.append(card)
}
}
completion(cards, nil)
})
// testTrace?.stop()
}
however when I try to use it there I get an error saying Firebase Performance does not support Extensions at this time
are you using Firebase Performance in the context of an App Extension (e.g. Watch, keyboard, today, etc.)? That message is triggered by this line in the FirebasePerformance.h file:
NS_EXTENSION_UNAVAILABLE("FirebasePerformance does not support app extensions at this time.")
Firebase Performance currently only supports normal applications on iOS.

Swift 2 to Swift 3 NSNotification/Notification

Using XCode 8 beta 6 under El Capitan coding in Swift 3.0
Trying to translate these lines in a project from Swift 2.0 to Swift 3.0
let userInfo = ["peer": peerID, "state": state.toRaw()]
NSNotificationCenter.defaultCenter.postNotificationName("Blah", object: nil, userInfo: userInfo)
So I managed to cobble together this ...
public class MyClass {
static let myNotification = Notification.Name("Blah")
}
let userInfo = ["peerID":peerID,"state":state.rawValue] as [String : Any]
NotificationCenter.default.post(name: MyClass.myNotification, object: userInfo)
It compiles and sends the notification when I run it and setup a listener with this line, but with no userInfo that I can decode?
let notificationName = Notification.Name("Blah")
NotificationCenter.default.addObserver(self, selector: #selector(peerChangedStateWithNotification), name: notificationName, object: nil)
This code prints "nil" as in no userInfo ...
func peerChangedStateWithNotification(notification:NSNotification) {
print("\(notification.userInfo)")
}
As #vadian said, NotificationCenter has a
post(name:object:userInfo:) method which you can use.
Here is a self-contained example, which also demonstrates how
to convert the userInfo back to a dictionary of the expected type
(taken from https://forums.developer.apple.com/thread/61578):
class MyClass: NSObject {
static let myNotification = Notification.Name("Blah")
override init() {
super.init()
// Add observer:
NotificationCenter.default.addObserver(self,
selector: #selector(notificationCallback),
name: MyClass.myNotification,
object: nil)
// Post notification:
let userInfo = ["foo": 1, "bar": "baz"] as [String: Any]
NotificationCenter.default.post(name: MyClass.myNotification,
object: nil,
userInfo: userInfo)
}
func notificationCallback(notification: Notification) {
if let userInfo = notification.userInfo as? [String: Any] {
print(userInfo)
}
}
}
let obj = MyClass()
// ["bar": baz, "foo": 1]
Alternatively, you can extract the dictionary values in the
callback like this (also from above Apple Developer Forum thread):
func notificationCallback(notification: Notification) {
guard let userInfo = notification.userInfo else { return }
if let fooValue = userInfo["foo"] as? Int {
print("foo =", fooValue)
}
if let barValue = userInfo["bar"] as? String {
print("bar =", barValue)
}
}

List installed Applications on El Capitan using Spotlight in Swift 2.2

I am currently building a mac app which in the future should be able to kill and start apps on OS X.
For that to be possible, I need to find a way to get a list of all the installed applications on the machine.
I already did quite a bit of research and have decided to use Spotlight with NSMetadataQuery to be able to get the list.
I was able to find this post about the mentioned topic and started to implement the functionality in Swift 2.2 (the weapon of choice for the project). With a bit of translation I was able to make it work and the code now successfully builds and runs. During runtime, however, I seem to be having a problem with the query itself:
<NSMetadataQuery: 0x6080000e3880> is being deallocated without first calling -stopQuery. To avoid race conditions, you should first invoke -stopQuery on the run loop on which -startQuery was called
This is the code I am currently using.
public func doSpotlightQuery() {
query = NSMetadataQuery()
let predicate = NSPredicate(format: "kMDItemKind ==[c] %#", "Application")
let defaultNotificationCenter = NSNotificationCenter()
defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
query.predicate = predicate
query.startQuery()
}
public func queryDidFinish(notification: NSNotification) {
for i in 0 ... query.resultCount {
print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
}
}
Testing the
mdfind "kMDItemKind == 'Application'"
command (with variations of all kind) in the terminal of my mac didn't give me any results either which brings me to my question:
Did I set up the query in a wrong way or does this command not work in 'El Capitan'?
Can someone please help me find my mistake? I'd sure love to finally make this work!
The dealloc message seems like the query is missing a strong reference.
var query: NSMetadataQuery? {
willSet {
if let query = self.query {
query.stopQuery()
}
}
}
public func doSpotlightQuery() {
query = NSMetadataQuery()
let predicate = NSPredicate(format: "kMDItemKind ==[c] %#", "Application")
let defaultNotificationCenter = NSNotificationCenter()
defaultNotificationCenter.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSMetadataQueryDidFinishGatheringNotification, object: nil)
query?.predicate = predicate
query?.startQuery()
}
public func queryDidFinish(notification: NSNotification) {
guard let query = notification.object as? NSMetadataQuery else {
return
}
for i in 0 ... query.resultCount {
print(query.resultAtIndex(i).valueForAttribute(kMDItemDisplayName as String))
}
}
I'd suggest using a different predicate as kMDItemKind is a localized key according to John's comment here
so let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'") would work for what we're doing.
In swift 3 this could look like this:
var query: NSMetadataQuery? {
willSet {
if let query = self.query {
query.stop()
}
}
}
public func doSpotlightQuery() {
query = NSMetadataQuery()
let predicate = NSPredicate(format: "kMDItemContentType == 'com.apple.application-bundle'")
NotificationCenter.default.addObserver(self, selector: #selector(queryDidFinish(_:)), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: nil)
query?.predicate = predicate
query?.start()
}
public func queryDidFinish(_ notification: NSNotification) {
guard let query = notification.object as? NSMetadataQuery else {
return
}
for result in query.results {
guard let item = result as? NSMetadataItem else {
print("Result was not an NSMetadataItem, \(result)")
continue
}
print(item.value(forAttribute: kMDItemDisplayName as String))
}
}
Here is a solution that gets the contents of /applications and /applications/utilities and converts the contents to NSMetaDataItems.
public func getAllApplications() -> [NSMetadataItem] {
let fileManager = FileManager()
guard let applicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .localDomainMask, appropriateFor: nil, create: false) else { return [] }
let applicationUrls = try! fileManager.contentsOfDirectory(at: applicationsFolderUrl , includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants])
guard let systemApplicationsFolderUrl = try? FileManager.default.url(for: .applicationDirectory, in: .systemDomainMask, appropriateFor: nil, create: false) else { return [] }
let utilitiesFolderUrl = NSURL.init(string: "\(systemApplicationsFolderUrl.path)/Utilities") as! URL
guard let utilitiesUrls = try? fileManager.contentsOfDirectory(at: utilitiesFolderUrl, includingPropertiesForKeys: [], options: [FileManager.DirectoryEnumerationOptions.skipsPackageDescendants, FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants]) else { return [] }
let urls = applicationUrls + utilitiesUrls
var applications = [NSMetadataItem]()
for url in urls {
print(url.path, fileManager.isExecutableFile(atPath: url.path))
if fileManager.isExecutableFile(atPath: url.path) {
guard let mdi = NSMetadataItem(url: url) else { continue }
applications.append(mdi)
}
}
for app in applications {
print(app.value(forAttribute: kMDItemDisplayName as String))
}
return applications
}
(There is some clean up that can be done to it but I wrote it in a hurry)