macOS command line app - User Defaults dictionaryRepresentation shows too many values - xcode

I a developing a macOS commandline application in Xcode, which uses User Defaults. I have the following code for my User Defaults
if let configDefaults = UserDefaults.init(suiteName: "com.tests.configuration") {
configDefaults.set("myStringValue", forKey: "stringKey")
configDefaults.synchronize()
print(configDefaults.dictionaryRepresentation())
}
This will create my own .plist file in the ~/Library/Preferences folder. If I look into the file, I can see only my single value which I added, which is perfectly fine. But when I call dictionaryRepresentation() on my UserDefaults object, the there are a lot of other attributes (I guess from the shared UserDefaults object), like
com.apple.trackpad.twoFingerFromRightEdgeSwipeGesture or AKLastEmailListRequestDateKey
Looking into the documentation of UserDefaults, it seems that this has to do with the search list of UserDefaults and that the standard object is in the search list:
func dictionaryRepresentation() -> [String : Any]
Returns a dictionary that contains a union of all key-value pairs in the domains in the search list.
There are also the methods addSuite and removeSuite for a UserDefaults object, so I am guessing I need to remove the .standard suite from my configDefaults object, but I don't know the name, which should be used for that in the method.
Is it possible to remove the .standard defaults from the dictionary representation? I basically just want all of my own data in a dictionary, nothing more.
The reason I am trying to get only my values from the UserDefaults, is that a have a number of object of a custom type Connection (which store the configuration to connect to a server), which are saved in the UserDefaults. On program start I want to be able to load all objects into my app. Therefore I thought I could use dictionaryRepresentation(), as it would return all elements in the UserDefaults. But then there should be only my Connection objects in the dictionary, so that I can cast it to [String: Connection].

Given your purpose (in your latest edit of your question), what you should do is store a collection of Connection objects under a single key. Then, look up that key to get the collection.
It's not clear if the collection should be an array of Connection objects or a dictionary mapping strings to Connections. That's a choice you can make.
But, in any case, you shouldn't rely on the defaults being empty of everything else.
In other words, you would do:
UserDefaults.standard.set(yourStringToConnectionDictionary, forKey:"yourSingleKey")
and later:
let connectionMap = UserDefaults.dictionary(forKey:"yourSingleKey")
then look up Connections in the connectionMap by their name/ID/whatever.

Though the other solution proposed by Ken Thomases may be better from a design standpoint, I've found a solution that does exactly what I initially wanted. Calling
UserDefaults.standard.persistentDomain(forName: "com.company.TestApp.configuration")
Returns a dictionary containing only the values I've added to the domain com.company.TestApp.configuration, using
let configs = UserDefaults.init(suiteName: "com.company.TestApp.configuration")!
configs.set(someData, forKey: someKey)
Strangely in the Apple documentation says this about persistentDomain(forName:):
Calling this method is equivalent to initializing a user defaults object with init(suiteName:) passing domainName and calling the dictionaryRepresentation() method on it.
But this is not the case (see my question). Clarification on that subject is more than welcome.

Related

Reading a large batch of objects taking way too long

I’m experimenting with scripting a batch of OmniFocus tasks in JXA and running into some big speed issues. I don't think the problem is specific to OmniFocus or JXA; rather I think this is a more general misunderstanding of how getting objects works - I'm expecting it to work like a single SQL query that loads all objects in memory but instead it seems to do each operation on demand.
Here’s a simple example - let’s get the names of all uncompleted tasks (which are stored in a SQLite DB on the backend):
var tasks = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})
var totalTasks = tasks.length
for (var i = 0; i < totalTasks; i++) {
tasks[i].name()
}
[Finished in 46.68s]
Actually getting the list of 900 tasks takes ~7 seconds - already slow - but then looping and reading basic properties takes another 40 seconds, presumably because it's hitting the DB for each one. (Also, tasks doesn't behave like an array - it seems to be recomputed every time it's accessed.)
Is there any way to do this quickly - to read a batch of objects and all their properties into memory at once?
Introduction
With AppleEvents, the IPC technology that JavaScript for Automation (JXA) is built upon, the way you request information from another application is by sending it an "object specifier," which works a little bit like dot notation for accessing object properties, and a little bit like a SQL or GraphQL query.
The receiving application evaluates the object specifier and determines which objects, if any, it refers to. It then returns a value representing the referred-to objects. The returned value may be a list of values, if the referred-to object was a collection of objects. The object specifier may also refer to properties of objects. The values returned may be strings, or numbers, or even new object specifiers.
Object specifiers
An example of a fully-qualified object specifier written in AppleScript is:
a reference to the name of the first window of application "Safari"
In JXA, that same object specifier would be expressed:
Application("Safari").windows[0].name
To send an IPC request to Safari to ask it to evaluate this object specifier and respond with a value, you can invoke the .get() function on an object specifier:
Application("Safari").windows[0].name.get()
As a shorthand for the .get() function, you can invoke the object specifier directly:
Application("Safari").windows[0].name()
A single request is sent to Safari, and a single value (a string in this case) is returned.
In this way, object specifiers work a little bit like dot notation for accessing object properties. But object specifiers are much more powerful than that.
Collections
You can effectively perform maps or comprehensions over collections. In AppleScript this looks like:
get the name of every window of Application "Safari"
In JXA it looks like:
Application("Safari").windows.name.get()
Even though this requests multiple values, it requires only a single request to be sent to Safari, which then iterates over its own windows, collecting the name of each one, and then sends back a single list value containing all of the name strings. No matter how many windows Safari has open, this statement only results in a single request/response.
For-loop anti-pattern
Contrast that approach to the for-loop anti-pattern:
var nameOfEveryWindow = []
var everyWindowSpecifier = Application("Safari").windows
var numberOfWindows = everyWindowSpecifier.length
for (var i = 0; i < numberOfWindows; i++) {
var windowNameSpecifier = everyWindowSpecifier[i].name
var windowName = windowNameSpecifier.get()
nameOfEveryWindow.push(windowName)
}
This approach may take much longer, as it requires length+1 number of requests to get the collection of names.
(Note that the length property of collection object specifiers is handled specially, because collection object specifiers in JXA attempt to behave like native JavaScript Arrays. No .get() invocation is needed (or allowed) on the length property.)
Filtering, and why your code example is slow
The really interesting part of AppleEvents is the so-called "whose clause". This allows you provide criteria with which to filter the objects from which the values will be returned from.
In the code you included in your question, tasks is an object specifier that refers to a collection of objects that have been filtered to only include uncompleted tasks using a whose clause. Note that this is still just reference at this point; until you call .get() on the object specifier, it's just a pointer to something, not the thing itself.
The code you included then implements the for-loop anti-pattern, which is probably why your observed performance is so slow. You are sending length+1 requests to OmniFocus. Each invocation of .name() results in another AppleEvent.
Furthermore, you're asking OmniFocus to re-filter the collection of tasks every time, because the object specifier you're sending each time contains a whose clause.
Try this instead:
var taskNames = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false}).name.get()
This should send a single request to OmniFocus, and return an array of the names of each uncompleted task.
Another approach to try would be to ask OmniFocus to evaluate the "whose clause" once, and return an array of object specifiers:
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
Iterating over the returned array of object specifies and invoking .name.get() on each one would likely be faster than your original approach.
Answer
While JXA can get arrays of single properties of collections of objects, it appears that due to an oversight on the part of the authors, JXA doesn't support getting all of the properties of all of the objects in a collection.
So, to answer you actual question, with JXA, there is not a way to read a batch of objects and all their properties into memory at once.
That said, AppleScript does support it:
tell app "OmniFocus" to get the properties of every flattened task of default document whose completed is false
With JXA, you have to fall back to the for-loop anti-pattern if you really want all of the properties of the objects, but we can avoid evaluating the whose clause more than once by pulling its evaluation outside of the for loop:
var tasks = []
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
var totalTasks = taskSpecifiers.length
for (var i = 0; i < totalTasks; i++) {
tasks[i] = taskSpecifiers[i].properties()
}
Finally, it should be noted that AppleScript also lets you request specific sets of properties:
get the {name, zoomable} of every window of application "Safari"
But there is no way with JXA to send a single request for multiple properties of an object, or collection of objects.
Try something like:
tell app "OmniFocus"
tell default document
get name of every flattened task whose completed is false
end tell
end tell
Apple event IPC is not OOP, it’s RPC + simple first-class relational queries. AppleScript obfuscates this, and JXA not only obfuscates it even worse but cripples it too; but once you learn to see through the faux-OO syntactic nonsense it makes a lot more sense. This and this may give a bit more insight.
[ETA: Omni recently implemented its own embedded JavaScriptCore-based scripting support in its apps; if JS is your thing you might find that a better bet.]

CoreAudio: What is "AudioBox" as contrasted to "AudioDevice"

The header file CoreAudio/AudioHardware.h refers to a class "AudioBox" and indicates that it is distinct from but related to the class "AudioDevice". Searching developer.apple.com yields no hits for AudioBox. There is, unfortunately, a commercial product called AudioBox™, which makes googling for the term painfully low-yield.
Here are the comments containing the references:
kAudioHardwarePropertyBoxList
An array of AudioObjectIDs that represent all the AudioBox
objects currently provided by the system.
kAudioHardwarePropertyTranslateUIDToBox
This property fetches the AudioObjectID that corresponds to the
AudioBox that has the given UID. The UID is passed in via the qualifier as a CFString while the AudioObjectID for the AudioBox is
returned to the caller as the property's data. Note that an error
is not returned if the UID doesn't refer to any AudioBoxes.
Rather, this property will return kAudioObjectUnknown as the value of the property.
The header file: AudioHardwareBase.h contains numerous references to AudioBox, but does not define or explain it, although it associates it with AudioDevice.
Searching the docs via XCode just takes me back to AudioHardwareBase.h.
I can infer that perhaps an "AudioBox" is an audio device that is accessed via a plugin. But this does not appear to be stated anywhere.
So What Is An AudioBox?
An AudioBox is a container of (usually) AudioDevices

In Local Datastore chapter, fromPin and fromlocaldatastore

Just like as title, I want to ask what difference between
fromPin()
and
fromLocalDatastore()
By the way, Pin and datastore two terminologies. What difference between two of them ?
Thanks.
There is a slight difference and you can see it from the docs and from the decompiled code of the Parse library (okay, this last one is more complicated...).
The docs says:
fromLocalDatastore(): Change the source of this query to all pinned objects.
fromPin(): Change the source of this query to the default group of pinned objects.
Here you can see that, interally on Parse, there is a way to get all the objects from the entire set of pinned data, without filters, but also from a so-called "default group". This group is defined in the Parse code with the following string: _default (o'rly?).
When you pin something using pinInBackground, you can do it in different ways:
pinInBackground() [without arguments]: Stores the object and every object it points to in the local datastore.
This is what the docs say, but if you look at the code you'll discover that the pin will be actually performed to the... _default group!
public Task<Void> pinInBackground() {
return pinAllInBackground("_default", Arrays.asList(new ParseObject[] { this }));
}
On the other hand, you can always call pinInBackground(String group) to specify a precise group.
Conclusion: every time you pin an object, it's guaranteed to be pinned to a certain group. The group is "_default" if you don't specify anything in the parameters. If you pin an object to your custom group "G", then a query using fromPin() will not find it! Because you didn't put it on "_default", but "G".
Instead, using fromLocalDatastore(), the query is guaranteed to find your object because it will search into "_default", "G" and so on.

Differentiating between new documents and restored documents in a Cocoa application

In my project I need to be able to tell the difference between documents created by the user and those restored at application launch by restoreStateWithCoder because there are some thing s that need to be done for new documents, but not restored ones. How can I do this?
How about subclassing "NSDocument" and using that subclass for your document?
Then, you can catch "restoreStateWithCoder" as it happens and set a unique flag (e.g. a BOOL property) for those documents that are restored from disk and not created fresh via "File -> New" command.
You can also attempt to "method swizzle" "restoreStateWithCoder", but you have to decide what property to set in which object.
[Answering this for Swift, but the general idea works for Objective-C as well]
When a document is brand new, you generally get a call to the following function:
convenience init(type tyepName: String) throws
You could set a flag in that function (say needSpecialHandling = true, a variable which is originally initialised to false) to say whether you need some special handling for such cases.
Then in the makeWindowControllers() function you use that variable to trigger invoking the special code (if true) the same way you invoked it possibly in the windowControllerDidLoadNib function.

RestKit many-to-many with Core Data: it works, but am I doing it right?

I'm new to RestKit. I'm trying to map a many-to-many relationship, between entities Space and User. For the purposes of this question, assume all the Space objects are in the Core Data store correctly already.
In the REST API, I can make a single call to get all the users for a given space. First of all, when I initialise my network fetching class, I set up a mapping appropriate for parsing a list of users:
RKManagedObjectMapping *teamMapping = [RKManagedObjectMapping mappingForEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:self.manager.objectStore.primaryManagedObjectContext] inManagedObjectStore:self.manager.objectStore];
[teamMapping mapKeyPath:#"id" toAttribute:#"userID"];
[teamMapping mapKeyPath:#"name" toAttribute:#"name"];
teamMapping.primaryKeyAttribute = #"userID";
[self.manager.mappingProvider setMapping:teamMapping forKeyPath:#"users.user"];
Then, to populate the links for the users to the spaces, I do this:
NSArray *spaces = [self.managedObjectContext executeFetchRequest:[NSFetchRequest fetchRequestWithEntityName:#"Space"] error:nil];
for (QBSpace *space in spaces)
{
[self.manager loadObjectsAtResourcePath:[NSString stringWithFormat:#"space/%#/users", space.wikiName] usingBlock:^(RKObjectLoader *loader) {
loader.onDidLoadObjects = ^(NSArray *objects){
space.team = nil;
for (QBUser *user in objects)
{
[space addTeamObject:user];
}
};
}];
}
That is, I manually add the space->user link based on the returned array of objects.
Is that the correct way to do it? It seems to work fine, but I'm worried I'm missing a trick with regards to RestKit doing things automatically.
The returned XML for the file in question looks like
<users type="array">
<user>
<id>aPrimaryKey</id>
<login>loginName</login>
<login_name warning="deprecated">loginName</login_name>
<name>Real Name</name>
</user>
So there is nothing in the returned file to indicate the space that I'm trying to link to: I passed that in in the URL I requested, but there's nothing in the returned document about it, which is why I came up with this block based solution.
To all the RestKit users, does this look like an acceptable way to form many-to-many links?
If it works, it's ok?
You could think about having a Space custom ManagedObject. Have it respond to the RKObjectLoaderDelegate.
Then, when you get the reply, you could trigger the loadAtResourcePath from within its
objectLoader:didLoadObject:(id)object or objectLoader:didLoadObjectDictionary:
methods.
You could also make your Space class respond to the delegates from User and then tie them into their space at that point?
It might be better, but that would depend on what you are trying to achieve. Food for thought anyway. :)

Resources