Traverse a tree from root to all children in Objective-C - objective-c

A simplified version of what I want to do is to turn a tree like this:
Into an array like this: ["abc","abd","ae"]
Basically I want to traverse the tree from it's root node to each of the children.
I've tried doing this by putting a for-in loop in a recursive block, but the problem was that the for loop would start over each time the block was recursed. When I tried running the block asynchronously I kept getting EXC_BAD_ACCESS
Any suggestions?

Say the tree is represented like this:
#interface TreeNode : NSObject
#property(weak,nonatomic) TreeNode *parent;
#property(strong,nonatomic) NSArray *children;
#end
The lineage (which is what you're looking for) of any node, is a list of nodes from the root to the node. This can be defined recursively like this:
- (NSArray *)lineage {
if (!self.parent) {
return #[self];
} else {
NSMutableArray *lineage = [[self.parent lineage] mutableCopy];
[lineage addObject:self];
return lineage;
}
}
You're looking for the lineages of the leaves, so we need a way to collect the leaves. We can do that if we can traverse the tree. This is a good application for blocks, like this:
- (void)depthFirst:(void (^)(TreeNode *))block {
for (TreeNode *node in self.children) {
[node depthFirst:block];
}
return block(self);
}
And that provides a natural way to collect leaves:
- (NSArray *)leaves {
NSMutableArray *leaves = [#[] mutableCopy];
[self depthFirst:^(TreeNode *node) {
if (!node.children) [leaves addObject:node];
}];
return leaves;
}
Putting it together, we get:
- (NSArray *)lineagesOfLeaves {
NSMutableArray lineages = [#[] mutableCopy];
for (TreeNode *leaf in [self leaves]) {
[lineages addObject:[leaf lineage]];
}
return lineages;
}
These methods work on any node in the tree. Though, for your question, you'll want to send lineagesOfLeaves to the tree's root.

Related

How do I allow a custom item to be copied?

I have a custom class that I want to be able to put on the pasteboard.
Here is the code I have so far
- (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard {
static NSArray *writableTypes = nil;
if (!writableTypes) {
writableTypes=[NSArray arrayWithObjects:(NSString *)kUTTypeXML, nil];
}
return writableTypes;
}
- (id)pasteboardPropertyListForType:(NSString *)type {
return [NSKeyedArchiver archivedDataWithRootObject:[MyClass class]];
}
This part isn't right:
- (id)pasteboardPropertyListForType:(NSString *)type {
return [NSKeyedArchiver archivedDataWithRootObject:[MyClass class]];
}
It looks like you're trying to archive the actual MyClass class, where you should actually be archiving the MyClass object, i.e. a specific instance of MyClass rather than the class itself. Also, you should probably be checking the type that's passed in to make sure that you're offering the data for the right thing. So, it should look something like:
- (id)pasteboardPropertyListForType:(NSString *)type {
if ([type isEqualToString:kUTTypeXML]) {
return [NSKeyedArchiver archivedDataWithRootObject:self.someInstanceOfMyClass];
}
return nil;
}
Finally, make sure that MyClass implements the NSCoding protocol so that you can create an archive containing the object's data.

Creating objects in a loop, but ivar / instance variable not keeping state

I have a method that creates a dictionary from NSJSONSerialization class. I then enumerate the json, and create objects to store state for each instance.
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *moviesJson = [json objectForKey:#"movies"];
for(NSDictionary *element in moviesJson)
{
RABMovie *myMovie = [[RABMovie alloc] initWithJson:element];
// RABMovie *myMovie = [RABMovie new];
// myMovie.json = element;
[_myMovieNames addObject: myMovie];
myMovie = nil;
}
[self.movieTableView reloadData];
}
Problem: I want to create my object by passing in element in the allocator, however when I do this, my UITTableView rows all contain the same movie data. It is always the last item, leading me to believe I am working with only one memory address, and the last update affects the whole collection.
If I uncomment the code to use the json as a property instead of a alloc param, I no longer have the issue and all works well. I've also tried creating a completely new NSDictionary via a deep copy of element to no avail.
Does someone know why this is happening? BTW, I am using ARC. -Thanks for the time.
Edit: Added more code. I've included a property movieName to illustrate how I use the ivar _json.
#implementation RABMovie
NSDictionary *_json;
- (id) initWithJson: (NSDictionary*) jsonAsDictionary
{
if (self = [super init])
{
_json = jsonAsDictionary;
}
return self;
}
- (NSString*) movieName
{
return [_json objectForKey:#"title"];
}
I think you meant to declare _json as an instance variable. Instead it's a globally visible (at least within that class) variable - not 100% sure on the scoping rules, but regardless, it's not an instance variable - it's a single variable shared by all instances! Try this instead:
#implementation RABMovie {
NSDictionary *_json;
}
/* ...rest of class */
#end
Putting it inside the curly braces after the #implementation directive makes it an instance variable. Hope this helps!
EDIT: Do you have a property called json on RABMovie already? Then you can skip the instance declaration altogether and the compiler will generate the an instance variable for you. That's probably happening already actually, which is why it works when you go through the property - it's accessing the ivar rather than the "global".

How to efficiently flatten a NSOutlineView?

I have a NSOutlineView which is binded to my NSTreeController. The content of the NSTreecontroller (myTreeController) is set with data using the command:
[self.myTreeController setContent:self.myArrayOfFiles];
The content of the array consists of NSTreeNode parent and children objects that are added using a NSTreeNode subclass (TKnode):
[TKnode treeNodeWithRepresentedObject:myRootObject].
This works very well and displays my NSoutlineView correctly. But when I want to iterate over the content of the NSOutlineView I need to flatten it and store the objects in an array. This is where I could need some help as the solution I have is not optimal and potentially prone to errors.
I first return the content of the NSTreeController using:
- (NSArray *)rootNodes;
{
return [[myTreeController arrangedObjects] childNodes] ;
}
This returns a NSTreeControllerTreeNode array of size one that holds the tree structure. I then access the first layer of the tree structure using the childNodes method.
- (NSArray *)flattenedNodes
{
NSMutableArray *mutableArray = [NSMutableArray array];
for (TKnode *rootnode in [self rootNodes]){
for (TKnode *node in [rootnode childNodes]){
if (![[node representedObject] isLeaf]){
[mutableArray addObjectsFromArray:[self descendants:[node representedObject]]];
}
else {
[mutableArray addObject:[[node representedObject] representedObject]];
}
}
}
DLog(#"My files: %lu",[mutableArray count]);
return [[mutableArray copy] autorelease];
}
The children of the children are accessed recursively using the following method:
- (NSArray *)descendants:(TKnode *) node
{
NSMutableArray *descendantsArray = [NSMutableArray array];
for (TKnode *mynode in [node childNodes]) {
[descendantsArray addObject:[mynode representedObject]];
if (!mynode.isLeaf){
[descendantsArray addObjectsFromArray:[self descendants:mynode]];
}
}
return [[descendantsArray copy] autorelease]; // return immutable
}
This works and returns an array containing all of my objects, not the NSTreeNodes, but the objects I am interested in. But it seems error prone that I have to call representedObject sometimes twice on an object to access my object. Is this the way I am supposed to work with NSTReeController and NSTreeNodes? Is there a better way? The reason I wanted to use NSTreeNode and NSTreeController in the first place was to get some of the already implemented methods for free such as sorting and arrangedObjects, which I am used to with NSTableView. But the method I use to access the NSTreeController content does not seem correct ? Should I use the arrangedObjects to get the content of the NSTReeController or should I use a different approach? Any suggestions for how to correctly flatten a NSOutlineView is highly appreciated.
Thanks! Cheers, Trond
This question turned out to be a real tumbleweed (my second in one month) so I wanted to follow up with some more info. I ended up using the above posted code for flattening my NSOutlineView as I was unable to find a better option. When you have to recursively iterate through an unknown number of subfolders (in my case) this seems to be the best option where the descendants method is called for each time you reach a deeper level. I found a very useful website with a number of useful NSTreeController extensions here, which uses the same approach as I had taken. Unless someone helps me figure out a faster and better algorithm for flattening the arrangedObjects of a NSTreeController, I believe i will stick with this approach.
Cheers, Trond
Update: After a question for the solution I decided to post my method for how to flatten an outlineview. I hope his can help others.
- (void) flattenOutlineview
{
NSMutableArray *nodeArray = [NSMutableArray array];
[self.myFlattenedFiles removeAllObjects];
[self.myFlattenedNodes removeAllObjects];
[self.myIndexDict removeAllObjects];
[nodeArray removeAllObjects];
for (SDNode *rootnode in self.rootNodes)
{
[nodeArray addObject:rootnode];
[self.myIndexDict setObject:[rootnode indexPath]
forKey:[[[rootnode representedObject] representedObject] fullpathL]];
for (SDNode *node in [rootnode childNodes])
{
if (node.isLeaf){
[nodeArray addObject:node];
[self.myIndexDict setObject:[node indexPath]
forKey:[[[node representedObject] representedObject] fullPathCopy]];
}
else {
[nodeArray addObjectsFromArray:[self descendants:node]];
[self descendantsIndex:node];
}
}
}
[self.myFlattenedNodes setArray:[nodeArray copy]];
for (SDNode *node in self.myFlattenedNodes)
{
[self.myFlattenedFiles addObject:[[node representedObject] representedObject]];
}
}
Saw this linked from another answer I just wrote. Provided you have everything in the tree expanded/displayed this would work:
for (NSUInteger row = 0; row < [outlineView numberOfRows]; row++)
{
NSTreeNode* node = [outlineView itemAtRow:row];
[nodeArray addObject:node];
}
The rows of the outlineView are basically a flattened tree. And you could perhaps expand every item while iterating to get them all.

Does Objective-C support Generics?

I wonder whether Objective-C offers any support for generics?
For instance, consider a method:
-(void) sort: (NSMutableArray *) deck {
}
Is there any way for me to make it only deal with Deck of Cards?
Is something like this possible to enforce?
-(void) sort: (NSMutableArray <Card *>) deck {
}
Objective-C supports lightweight Generics since 2015, with the Xcode 7.
The Xcode 7 compiler will give you the compiler warning if there is a type mismatch.
For example, the following line will raise a compiler warning as the second object in the array causes type mismatch. The array allows only NSString objects.
NSArray <NSString *> *myArray = [#"str2", #1, #"str2"];
You can use the introspection tools offered by the objective-c runtime.
Basically, it means you can check if all objects in an array either are a kind of class (Class A or one subclass of it) or a member of class (class A), or if a objects conforms to a protocol or responds to a selector (a certain method is present).
-(void) sort: (NSMutableArray *) deck {
for(id obj in deck){
if(obj isKindOfClass:[A class]]){
//this is of right class
}
}
}
You could write a Category method on NSArray that checkouts this on every object.
BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];
Normally you actually don't need this very often, as you know what you put inside a collection.
If you need to check the type or ability of an object in a Array, this might be an indicator, that your Architecture is broken
Another option could be a subclass of NSMutableArray that only accepts certain classes. But be aware of the subclassing notes for NSMutableArray and NSArray, as these are Class-Clusters and therefore not easy to subclass.
Note: In my other answer I created a NSMutableArray subclass, that uses a block to test, if a certain requirement is fulfilled. If you test against class-membership, this will do exactly what you want. Use the second block for error handling.
As of Xcode 7's release, Apple has added support for Objective-C generics.
NSArray <NSString *> *arrayOfStrings = #[#"a", #"b"];
NSDictionary <NSString *, NSDate *> *dictionaryOfDates = #{ #"a" : #1 };
Inspired by MonomorphicArray I came up with another idea:
Create a subclass on NSMutableArray, that takes two blocks:
AddBlock — a block that test, if one or more requirements are full filed and adds the object only, if its passes the test
FailBlock — a block, that defines what happens, if the test was not successful.
The AddBlock could test for a certain class membership like
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
and the FailBlock can raise an exception, fail silently or add the element, that failed the test, to another Array. If no failBlock is provided, a default block will raise an error.
The blocks will define, if an array acts like an generic array, or as a filter.
I will give an complete example for the second case.
VSBlockTestedObjectArray.h
#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element);
typedef void(^FailBlock)(id element);
#interface VSBlockTestedObjectArray : NSMutableArray
#property (nonatomic, copy, readonly) AddBlock testBlock;
#property (nonatomic, copy, readonly) FailBlock failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;
#end
VSBlockTestedObjectArray.m
#import "VSBlockTestedObjectArray.h"
#interface VSBlockTestedObjectArray ()
#property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
#end
#implementation VSBlockTestedObjectArray
#synthesize testBlock = _testBlock;
#synthesize failBlock = _failBlock;
#synthesize realArray = _realArray;
-(id)initWithCapacity:(NSUInteger)capacity
{
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock
FailBlock:(FailBlock)failBlock
Capacity:(NSUInteger)capacity
{
self = [self initWithCapacity:capacity];
if (self) {
_testBlock = [testBlock copy];
_failBlock = [failBlock copy];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}
-(id)initWithTestBlock:(AddBlock)testBlock
{
return [self initWithTestBlock:testBlock FailBlock:^(id element) {
[NSException raise:#"NotSupportedElement" format:#"%# faild the test and can't be add to this VSBlockTestedObjectArray", element];
} Capacity:0];
}
- (void)dealloc {
[_failBlock release];
[_testBlock release];
self.realArray = nil;
[super dealloc];
}
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if(self.testBlock(anObject))
[self.realArray insertObject:anObject atIndex:index];
else
self.failBlock(anObject);
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[self.realArray removeObjectAtIndex:index];
}
-(NSUInteger)count
{
return [self.realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [self.realArray objectAtIndex:index];
}
-(void)errorWhileInitializing:(SEL)selector
{
[NSException raise:#"NotSupportedInstantiation" format:#"not supported %#", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}
#end
Use it like:
VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSString", element);
}];
VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];
[stringArray addObject:#"test"];
[stringArray addObject:#"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:#"test2"];
[stringArray addObject:#"test3"];
[numberArray addObject:#"test"];
[numberArray addObject:#"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:#"test2"];
[numberArray addObject:#"test3"];
NSLog(#"%#", stringArray);
NSLog(#"%#", numberArray);
Note: This code is not fully tested. Probably some of the unimplemented method should be implemented for usage in real world programs.
Not directly, no. There a few ways to simulate it, but it requires a lot of wrapper code, boilerplate code, and runtime overhead. I just switch to Objective-C++ and use C++ templates when I want or need proper generics.
So if you wanted to introduce typesafety/checks to an NSArray, you could approach it using something like this:
template <typename T>
class t_typed_NSMutableArray {
public:
t_typed_NSMutableArray() : d_array([NSMutableArray new]) {}
~t_typed_NSMutableArray() { [d_array release]; }
/* ... */
T* operator[](const size_t& idx) {
T* const obj([this->d_array objectAtIndex:idx]);
assert([obj isKindOfClass:[T class]]);
return obj;
}
void addObject(T* const obj) {
assert([obj isKindOfClass:[T class]]);
[this->d_array addObject:obj];
}
private:
NSMutableArray * const d_array;
};
in use:
t_typed_NSMutableArray<Card> array([self cards]); // < note this exact constructor is not defined
Card * firstCard = array[0]; // << ok
NSString * string = array[0]; // << warning
then you also get type safety and overloading when passing the collection, so you could not pass t_typed_NSArray<Card> as an t_typed_NSArray<NSURL>.
There is an easy, effective way of doing this (I've been using it on projects for a couple of years now). Sadly, someone deleted the answer, and my attempts to get it re-instated were rejected. Here goes again:
You can re-implement a cut-down version of C++ templating within Obj-C because Obj-C encapsulates all of C (and C++ templates are C-macros with some improved compiler/debugger support):
This only needs to be done once, using a single header file. Someone has done it for you:
https://github.com/tomersh/Objective-C-Generics
You end up with 100% legal Obj-C code that looks like this:
NSArray<CustomClass> anArray= ...
CustomClass a = anArray[0]; // works perfectly, and Xcode autocomplete works too!
This all works fine in XCode, with autocomplete, etc.

Cocoa style: using polymorphism in collections

I have a data model that includes a big list (array) of heterogeneous items. There are only 2-3 different kinds of item, each kind inheriting from a base class. Using classic examples, let's say the base class is Vehicle, and the subclasses are Car, Train, and Plane.
I have a larger owning model/controller that wants to operate on this ordered list of Vehicles, and while some of the operations are shared (and are in the base class and overridden in subclasses), many of the operations are specific to only one of the kinds of items.
So I end up with a lot of code that looks like this:
for (Vehicle * vehicle in vehicles) {
if (![vehicle isKindOfClass:[Car class]]) {
continue;
}
Car * car = (Car *)vehicle;
// Do stuff only with "car".
}
So I've got lots of -isKindOfClass: everywhere and lots of casting the base class to the subclass. This all works, of course, but there seems like enough "code smell" to make me think there might be a more elegant way of either writing this code, or designing my object model.
Thoughts? Thanks.
I guess the usual polymorphic pattern would be to push the body of the loop out into the classes in question, so your loop turns into
for (Vehicle * vehicle in vehicles) {
[vehicle doSubclassSpecificThing];
}
The question then is how to share the common part of the loop body. It's possible you could break it into a series of chunks:
for (Vehicle * vehicle in vehicles) {
/* common code */
[vehicle doThingy];
/* further common code */
[vehicle doOtherThingy];
}
Or you could have -doSubclassSpecificThing be required to call [super doSubclassSpecificThing] first, and put the common part in the base class if it all comes first.
Basically, it sounds like your loop body has a number of things going on in it. If you extract each one to a method you can choose which pieces to share or override, and your loop body becomes a very high level description of what to do instead of the details.
If you want to access behaviour that’s specific to the subclasses and hasn’t been defined by their common superclass, you can’t escape from doing some sort of checking. Polymorphism is usually related to behaviour defined in a superclass (or interface) that can be overridden by subclasses, and the system knows which behaviour is appropriate according to the actual object type.
That said, in your example that particular loop is interested in a subset of the elements of the array that belong to one of the subclasses, namely Car. In that case, you can use [-NSArray indexesOfObjectsPassingTest:] to create an index set containing only the indexes of Car objects. Having done that, you can iterate the index set knowing that it points to elements in the original array whose class is Car. For instance:
NSIndexSet *cars = [vehicles indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return (BOOL)([obj isKindOfClass:[Car class]]);
}];
[cars enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
Car *car = [vehicles objectAtIndex:idx];
// Do stuff that’s specific to cars
}];
I would eliminate the casts and isolate the the isKindOfClass checks with a set of methods that filter the collection to have only the elements of the desired type.
Header:
#interface VehicleManager : NSObject {
#private
NSArray *vehicles;
}
#property (readonly) NSArray *vehicles;
#property (readonly) NSArray *cars;
#property (readonly) NSArray *planes;
#property (readonly) NSArray *trains;
#end
Implementation File:
#implementation VehicleManager
#synthesize vehicles;
static NSArray *MyFilterArrayByClass(NSArray *array, Class class) {
NSMutableArray *result = [NSMutableArray array];
for (id object in array) {
if ([object isKindOfClass:class]) {
[result addObject:object];
}
}
return result;
}
- (NSArray *)cars {
return MyFilterArrayByClass([self vehicles], [Car self]);
}
- (NSArray *)planes {
return MyFilterArrayByClass([self vehicles], [Plane self]);
}
- (NSArray *)trains {
return MyFilterArrayByClass([self vehicles], [Train self]);
}
- (BOOL)areAllCarsParked {
BOOL allParked = YES;
for (Car *car in [self cars]) {
allParked = allParked && [car isParked];
}
return allParked;
}
#end