Access an object in an NSArray using a key path - macos

I've read through the KVC docs on Apple and it talks in depth about making your indexed collections accessible through key value coding, but I can't find any examples of a key path being used to access an arbitrary element within the array.
If my Blob class has an NSArray *widgets, I'd like to be able to get the widget at index 4 by doing something like:
[myBlob valueForKeyPath:#"widgets[4]"]
Is there anything like this?

myBlob answers to 'valueForKey:' and widgets being an NSArray answers to 'objectAtIndex:'.
So '[[myBlob valueForKey:#"widgets"] objectAtIndex:4]' should do the trick.

Related

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

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.

How to filter an NSArray of file urls by Spotlight File Metadata Attributes / NSMetadataQuery?

The NSMetadataQuery class seems to be how Finder/Spotlight searches for files via their metadata.
NSMetadataQuery class provided by the Foundation framework. Queries can be run in two modes: asynchronous, and asynchronous with live updates. The first simply performs the search on the files that exist at the time of the initial search. The latter continues to search. updating the data as the files that fulfill or no longer fulfill the search parameters update.
https://developer.apple.com/library/content/documentation/Carbon/Conceptual/SpotlightQuery/Concepts/Introduction.html#//apple_ref/doc/uid/TP40001843-BBCFBCAG
However, it seems oriented around providing a directory (searchScopes), and then asynchronously returning results that were found in those directories (NSMetadataQueryDidFinishGathering).
I already have an NSArray containing file urls. I would like to construct a filter/search of those NSURLs using the same metadata and query syntax as a Spotlight Search. But I will provide a list of files to quickly filer, rather than a provide a directory with and receive asynchronous results.
// Something like this...
let imageFileTypePredicate = NSPredicate(fromMetadataQueryString: "(kMDItemGroupId = 13)")
let imageURLs = allURLs.filter{ imageFileTypePredicate.evaluate(with:$0) };
However, that is using a standard NSPredicate search rather than a file metadata filter and is throwing the error:
this class is not key value coding-compliant for the key _kMDItemGroupId.
The Spotlight Metadata Attributes I'm interested in filtering by are listed here:
https://developer.apple.com/library/content/documentation/CoreServices/Reference/MetadataAttributesRef/Reference/CommonAttrs.html#//apple_ref/doc/uid/TP40001694-SW1
How can an array of file urls be filtered by Spotlight metadata?
Create an MDItem for each url to get the file's spotlight attributes.
MDItem is a CF-compliant object that represents a file and the metadata associated with the file.
https://developer.apple.com/reference/coreservices/1658213-mditem
MDItemRef item = MDItemCreateWithURL(kCFAllocatorDefault, url);
CFArrayRef attributes = MDItemCopyAttributeNames(item);
NSDictionary *attributeValues = CFBridgingRelease(MDItemCopyAttributes(item, attributes));

NSUserDefaults per document

Does the AppKit framework provide a way to store NSUserDefaults per NSDocument? If not, how would you recommend to implement this?
If you have some key that identifies the document you can store an object (dictionary...) with that key in NSUserDefaults. Or, depending on how you define a "document" it could contain its own meta-data.
Use an NSDictionary. Store its contents along with the document. If the format you're saving into can't be modified to contain this dictionary, you might be able to switch to "document bundles", also called "document packages" -- directories containing both the data and a plist with the contents of the dictionary.
If you want to explicitly want to store per-document data inside the [NSUserDefaults standardUserDefaults]-provided facility, you may want to look into organizing things like this:
documentSpecificSettings - NSArray
item 0 - NSDictionary
url - NSData containing NSURL's bookmark
some_key - your custom key
another_key - your custom key
item 1 - NSDictionary
url - NSData containing NSURL's bookmark
some_key - your custom key
See documentation for NSURL, specifically +bookmarkDataWithContentsOfURL:error: and +URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:. There are more methods around, but these are the ones you'll want to use.
As far as I understand it, you'll have to manually track whether or not the document still exists when resolving the bookmark into the URL. And I suspect you'll have to resolve each URL in the dictionary to see if it applies to your document.
This appears more complex than simply storing a dictionary along with the document.

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. :)

want to filter out Windows admin shares from my arrangedObjects NSArrayController

I have everything set up in my NSArrayController to have my files owner representedObject be the data source for a tableview that I have.
Within my bindings in interface builder I would like to use the filter predicate to filter out any items in the arraycontroller that have a $ in the name.... specifically ending in $ actually as this is how windows admin shares look like when I get the array of FTP shares I am collecting.
I have set up the filter predicate as so:
controller key: selection
modelKeyPath : directorsAndFiles
value Transformer: hmm I dont know? can you help
wanted:
eliminate any items that have $ characters in them
thanks!
First of all, you don't need a value transformer in this case. A value transformer changes the display of a value. For example, it may cause the number 2 to be displayed as "€ 2,00" -- or whatever you may like.
Second, the predicate syntax you're looking for is "ENDSWITH" as in :
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name not ENDSWITH '$'"];
When you "set up" the filter predicate, what you're doing is binding it--you need to set the key path to a valid property of the whatever object you set as the controller key. So, instead, you should set the controller key to some controller object, and the key path to some property, perhaps named something like windowsPredicate.
Then, in whatever object you've bound to, add a windowsPredicate property which returns an NSPredicate. You'll create the NSPredicate yourself, using something like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name not like '*$'"];
(The * means "anything", so that roughly translates to "don't end with $".) Check the NSPredicate documentation for more info. Good luck!

Resources