I have an NSTableview which s bound to a NSArrayController. The Table/Arraycontroller contains Core Data "Person" entities. The people are added to the NSTableview by the GUI's user.
Let's say a person entity looks like
NSString* Name;
int Age;
NSString* HairColor;
Now I want to iterate over what is stored in the array controller to perform some operation in it. The actual operation I want to do isn't important I don't really want to get bogged down in what I am trying to do with the information. It's just iterating over everything held in the NSArraycontroller which is confusing me. I come from a C++ and C# background and am new to Cocoa. Let's say I want to build a NSMutableArray that contains each person from nsarraycontroller 1 year in the future.
So I would want to do something like
NSMutableArray* mutArray = [[NSMutableArray alloc] init];
foreach(PersonEntity p in myNsArrayController) // foreach doesn't exist in obj-c
{
Person* new_person = [[Person alloc] init];
[new_person setName:p.name];
[new_person setHairColor:p.HairColor];
[new_person setAge:(p.age + 1)];
[mutArray addObject:new_person];
}
I believe the only thing holding me back from doing something like the code above is that foreach does not exist in Obj-c. I just don't see how to iterate over the nsarraycontroller.
Note: This is for OSX so I have garbage collection turned on
You're looking for fast enumeration.
For your example, something like
for (PersonEntity *p in myNsArrayController.arrangedObjects)
{
// Rest of your code
}
You can also enumerate using blocks. For example:
[myNsArrayController enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop)
{
PersonEntity *p = object;
// Rest of your code
}];
There's pro's and cons to both approaches. These are discussed in depth in the answer to this question:
Objective-C enumerateUsingBlock vs fast enumeration?
You can find a great tutorial on blocks in Apple's WWDC 2010 videos. In that they say that at Apple they use blocks "all the time".
Related
I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)
When using Swift, the Cocoa frameworks are declared to return native Swift types, even though the frameworks are actually returning Objective-C objects. Likewise, the methods takes Swift types as parameters, where that makes sense.
Suppose I want to call a Cocoa method that (in Objective-C) would give me an NSArray and then pass that to a Cocoa method that takes an NSArray. With code like this:
let a: [AnyObject] = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a)
It looks like the huge NSArray is going to get bridged to a Swift array when assigned to a and then bridged back to an NSArray when passed as a parameter. At least that's how it seems from profiling and looking at the disassembly.
Is there a way to avoid these potentially slow conversions when I don't need to actually work with the array in Swift? When I'm just receiving it from Cocoa and then passing it back to Cocoa?
At first, I thought that it would help to add type information for a:
let a: NSArray = [] // Imagine calling a method that returns a huge NSArray.
let mutable = NSMutableArray()
mutable.addObjectsFromArray(a as [AnyObject])
But then I have to convert the parameter to a Swift array later or the compiler will complain.
Furthermore, the disassembly for code like:
let c: NSArray = mutable.subarrayWithRange(NSMakeRange(0, 50))
shows calls to __TF10Foundation22_convertNSArrayToArrayurFGSqCSo7NSArray_GSaq__ and __TFer10FoundationSa19_bridgeToObjectiveCurfGSaq__FT_CSo7NSArray, seemingly converting the return value to Swift and then back to Objective-C. (This happens even with Release builds.) I had hoped that by typing c as NSArray there would be no bridging necessary.
I'm concerned that this could lead to inefficiencies with very large data structures, with many disparate conversions of regular ones, and with collections that are lazy/proxied because they are not necessarily large but may be expensive to compute. It would be nice to be able to receive such an array from Objective-C code and pass it back without having to realize all of the elements of the array if they are never accessed from Swift.
This is a very different performance model than with Core Foundation/Foundation where the bridging was toll-free. There are so many cases where code passes objects back and forth assuming that it will be O(1), and if these are invisibly changed to O(n) the outer algorithms could become quadratic or worse. It's not clear to me what one is supposed to do in this case. If there is no way to turn off the bridging, it seems like everything that touches those objects would need to be rewritten in Objective-C.
Here is some sample timing code based on the above example:
NSArray *getArray() {
static NSMutableArray *result;
if (!result) {
NSMutableArray *array = [NSMutableArray array];
for (NSUInteger i = 0; i < 1000000; i++) {
[array addObjectsFromArray:#[#1, #2, #3, #"foo", #"bar", #"baz"]];
}
result = array;
}
return result;
}
#interface ObjCTests : XCTestCase
#end
#implementation ObjCTests
- (void)testObjC { // 0.27 seconds
[self measureBlock:^{
NSArray *a = getArray();
NSMutableArray *m = [NSMutableArray array];
[m addObjectsFromArray:a];
}];
}
#end
class SwiftTests: XCTestCase {
func testSwift() { // 0.33 seconds
self.measureBlock() {
let a: NSArray = getArray() as NSArray
let m = NSMutableArray()
m.addObjectsFromArray(a as [AnyObject])
}
}
func testSwiftPure() { // 0.83 seconds
self.measureBlock() {
let a = getArray()
var m = [AnyObject]()
m.appendContentsOf(a)
}
}
}
In this example, testSwift() is about 22% slower than testObjC(). Just for fun, I tried doing the array append with the native Swift array, and this was much slower.
A related issue is that when Objective-C code passes Swift code an NSMutableString, the Swift String ends up with a copy of the mutable string. This is good in the sense that it won’t be unexpectedly mutated behind Swift’s back. But if all you need to do is pass a string to Swift and look at it briefly, this copy could add unexpected overhead.
have you tried making an extension?
extension NSMutableArray
{
func addObjectsFromNSArray(array:NSArray)
{
for item in array
{
self.addObject(item);
}
}
}
Now that I had time to actually play with this instead of talking in theory, I am going to revise my answer
Create an extension, but instead, do it in an objective c file
#interface NSMutableArray(Extension)
- (void)addObjectsFromNSArray:(NSObject*) array;
#end
#implementation NSMutableArray(Extension)
- (void)addObjectsFromNSArray:(NSObject*) array
{
[self addObjectsFromArray:(NSArray*)array];
}
#end
I found the code to work a lot faster doing it this way. (Almost 2x from my tests)
testSwift 4.06 seconds
testSwiftPure 7.97 seconds
testSwiftExtension 2.30 seconds
I know there are a whole bunch of questions that have been asked and answered in stackoverflow about the challenge of getting keys in an NSDictionary sorted by putting those keys into sort order in an array. I understand that objects are not stored in sort order within the actual dictionary and that is, I think, for reasons of efficiency or maybe memory management on the part of Foundation code.
I have been working on trying out examples from several answers out here and in apple documentation and blogs (some I can get to work, others not!) , but I can't seem to find an example that solves my confusion.
I think my confusion is that the examples I'm encountering both here, in apple documentation and in the different helpful blogs, all seem to have examples where there is just a key value pair and the second value is not an object - it's more like just a value. (However isn't it really an object at some level? I would think it is)
One example, that I couldn't get to work (Sorting an NSArray by an NSDictionary value ) , uses this idea
[array sortedArrayUsingComparator:^(NSDictionary *item1, NSDictionary *item2) {
NSString *age1 = [item1 objectForKey:#"age"];
NSString *age2 = [item2 objectForKey:#"age"];
return [age1 compare:age2 options:NSNumericSearch];
}];
I thought maybe this idea, specifying the key in a more specific manner, might be my problem.
I wonder if maybe I'm not communicating to the compiler what the key is, and what the object is, and that is why I'm getting an "unrecognized selector sent to instance" error.
..... Code Snips Follow .....
1)
I have a class called "Dog". A given dog object has several properties, including an NSString key.
My key is "licenseString" is an alphanumeric key - I'm also wondering if I should use decimalNumberWithString but that's not the question here
#property (strong,nonatomic) NSString *licenseString;
#property (strong, nonatomic) NSString *dogName;
#property (strong, nonatomic) NSString *whatMakesDogSpecial;
#property (strong, nonatomic) UIImage *dogPhoto;
2) I have an NSDictionary
#property (nonatomic, strong) NSDictionary *dogDictionary;
I hardcode information into the dogDictionary in this not very sophisticated way,
Dog *aDog;
// Dog one
aDog = [[Dog alloc]init] ;
aDog.licenseString = #"1";
aDog.dogName = #"Oscar";
aDog.whatMakesDogSpecial = #"This animal was found at the Mid-Penn humane society. He is super friendly, plays well with other dogs and likes people too. He enjoys fetching balls and frisbees too. He also goes to the park daily." ;
aDog.dogPhoto = [UIImage imageNamed:#"webVuMiniAppDogOscar.jpg"];
[self.dogDictionary setValue:aDog forKey:aDog.licenseString];
3) Then once I have several dog objects in my dogDictionary, I want to sort on the license tag values, so that I can populate a table view with dog names, but by order of their license tags.
BTW it seems that the compiler does recognize "vars.dogDictionary" which appears in the code snip below, because when I look at the debugger I can see that two valid instances are coming up from my dog dictionary. The debugger output is in an attachment
So, using ideas from a stackoverflow answer and the apple documentation, I write this
NSArray *sortedKeys = [vars.dogDictionary keysSortedByValueUsingComparator:
^NSComparisonResult(id obj1, id obj2) {
return [obj1 compare:obj2];
}];
NSLog(#" The sorted array is %#", sortedKeys);
And that's where my problem happens. I recognize that 0x1182f740 refers to "obj1" as shown in the debugger attachment
2013-08-06 15:13:58.276 SortDogLIcenseTags[3876:11303] -[Dog compare:]: unrecognized selector sent to instance 0x1182f740
(lldb)
Attachment is a picture showing debugger values - they don't like to paste very well
Here's how I resolved this challenge. It works and was pretty straightforward to integrate into my Master/Detail project
I know I found a tutorial on the web somewhere that led me to this solution , I'm sorry I can't find it now.
Note that sortedDogDictionaryArray and dogDictionaryArray are declared as properties in the .h file.
self.dogDictionaryArray = [vars.dogDictionary allValues];
// Sort
NSSortDescriptor *sortDescriptorDog =
[[NSSortDescriptor alloc] initWithKey:#"licenseString" ascending:YES];
NSArray *sortDescriptorsDogs =
[NSArray arrayWithObject:sortDescriptorDog];
self.sortedDogDictionaryArray =
[self.dogDictionaryArray sortedArrayUsingDescriptors:sortDescriptorsDogs];
NSLog(#"%#",self.sortedDogDictionaryArray );
int doggie;
Dog *someDogName;
NSLog(#"Sorted Order is...");
for (doggie = 0; doggie < [self.sortedDogDictionaryArray count]; doggie++) {
//NSLog(#"%#", [sortedArray objectAtIndex:i]);
//NSLog(#"%#", [sortedArrayDogs objectAtIndex:doggie]);
someDogName = [self.sortedDogDictionaryArray objectAtIndex:doggie];
//NSLog(#"name is %#", someDogName.dogName);
NSLog(#"name is %# tag is %#", someDogName.dogName, someDogName.licenseString);
}
I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.
I previously posted this question as a comment on a related thread thinking it was simple. That thread is here:
Cocoa binding to a particular item in an array controller
The questions relates to (and I'll more fully describe it here) a game I'm building to try and learn objective-c and cocoa. Its good enough to think of it like texas hold-em poker. One server holds the game information and manages input from a variable number of clients (always more than one). Through cocoa bindings, it displays to each player the public information of the game which is stored in an array on the server using an array controller in IB. Think of the five cards on the table being stored in an NSArray on the server and bound to the content field of an NSArrayController for each client.
This part works fine, like a charm. However, each player has two cards that he needs to keep private. Each client should display a different card depending on what was dealt to that particular player. (Because what is really happening is I'm binding to an array of player objects
NSArray * thePlayers,
imagine all the cards being stored on the same array). So my question is, how do I set up bindings to a single object out of the array controller (or do I need some other controller)? That is, how to I bind to one player of thePlayers array?'
You set up a property in the controller or model to access that particular player and bind to that. There is no way to bind directly to an object at a particular index in an array.
If you do want to bind to specific array indices, you could could create a wrapper object. Something like this. It lets you bind to item0, item1 and so on. There is no range checking and it breaks if you change the size of the array, but you get the idea.
Interface
#interface MyArrayBinder : NSObject {
NSMutableArray *array;
}
- (id)initWithMutableArray:(NSMutableArray *)theArray;
- (NSMutableArray *)array;
#end
Implementation
#include <objc/runtime.h>
static NSInteger _indexFromSelector(SEL sel) {
return [[NSStringFromSelector(sel) stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] integerValue];
}
static void _dynamicSetItem(MyArrayBinder *self, SEL sel, id obj) {
[self.array replaceObjectAtIndex:_indexFromSelector(sel) withObject:obj];
}
static id _dynamicItem(MyArrayBinder *self, SEL sel) {
return [self.array objectAtIndex:_indexFromSelector(sel)];
}
#implementation MyArrayBinder
- (id)initWithMutableArray:(NSMutableArray *)theArray {
self=[super init];
if (self) {
array=theArray;
for(NSUInteger i=0; i<[array count]; i++) {
class_addMethod([self class], NSSelectorFromString([NSString stringWithFormat:#"item%lu", i]), (IMP) _dynamicItem, "##:");
class_addMethod([self class], NSSelectorFromString([NSString stringWithFormat:#"setItem%lu:", i]), (IMP) _dynamicSetItem, "v#:#");
}
}
return self;
}
- (NSMutableArray *)array {
return array;
}
#end
However, each player has two cards that he needs to keep private. Each client should display a different card depending on what was dealt to that particular player. (Because what is really happening is I'm binding to an array of player objects …
The client knows which player it's representing, right? Not by index—it should have a direct reference to the Player object for the player sitting at its keyboard. Something like MyPlayer *userPlayer;. This is in addition to the dealer object holding an array of all the players, including that one.
Once you have it lain out that way, with the client controller having a property whose value is the user's Player object, the binding becomes simple: You'll bind the card views directly to card A and card B of the userPlayer property of the client controller. (This is essentially what Chuck already suggested in his answer, and what I suggested in my comment on your answer on that other question.)
imagine all the cards being stored on the same array).
Why would I want to imagine that? Why don't the players own their own cards separately?
OK, so the dealer should own all the cards (that is, the deck). It should co-own those also held by a player. The players don't access their cards through the dealer; each player should directly hold his or her cards.
It sounds like you made the same mistake with cards as with players: Thinking that one object can/should know another through an array by index. You can't—certainly not if you want to use that knowledge with Bindings—and shouldn't. The one object needs to know the other directly. This is not only the correct solution, it's the correct way for objects to know each other. Any array-index-based reference would be more complex for no benefit.
Very similar to Nick Moore's solution:
If you do want to bind to specific array indices, you could create a wrapper object. Something like this. It lets you bind to item0, item1 and so on. There is no range checking and it breaks if you change the size of the array, but you get the idea.
Interface
#interface MyArrayBinder : NSObject
#property NSMutableArray *array;
- (id)initWithMutableArray:(NSMutableArray *)theArray;
#end
Implementation
static NSInteger _indexFromString(NSString *key) {
return [[key stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] integerValue];
}
#implementation MyArrayBinder
- (id)initWithMutableArray:(NSMutableArray *)theArray {
if ( self=[super init] ) {
_array=theArray;
}
return self;
}
- (id)valueForUndefinedKey:(NSString *)key {
return _array[_indexFromString( key )];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
_array[_indexFromString( key )] = value;
}
#end