├── .gitignore
├── JTTree.podspec
├── test
├── JTTreeTests-Info.plist
└── JTTreeTests.m
├── LICENSE
├── README.md
├── src
├── JTTree.h
└── JTTree.m
└── JTTree.xcodeproj
└── project.pbxproj
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pbxuser
3 | !default.pbxuser
4 | *.mode1v3
5 | !default.mode1v3
6 | *.mode2v3
7 | !default.mode2v3
8 | *.perspectivev3
9 | !default.perspectivev3
10 | *.xcworkspace
11 | !default.xcworkspace
12 | xcuserdata
13 |
--------------------------------------------------------------------------------
/JTTree.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "JTTree"
3 | s.version = "0.1.0"
4 | s.summary = "A more useful wrapper around CFTree (with apologies to NSTreeNode)."
5 | s.homepage = "https://github.com/jtbandes/JTTree"
6 | s.license = { :type => "Unlicense", :file => "LICENSE" }
7 | s.author = { "Jacob Bandes-Storch" => "jacob@bandes-storch.net" }
8 | s.source = { :git => "https://github.com/jtbandes/JTTree.git", :tag => "v#{s.version}" }
9 |
10 | s.framework = "Foundation"
11 | s.source_files = "src/*.{h,m}"
12 |
13 | s.requires_arc = true
14 | s.ios.deployment_target = "5.0"
15 | s.osx.deployment_target = "10.7"
16 | end
17 |
--------------------------------------------------------------------------------
/test/JTTreeTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | net.bandes-storch.${PRODUCT_NAME:rfc1034identifier}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## JTTree
2 |
3 | A more useful wrapper around [CFTree](https://developer.apple.com/documentation/CoreFoundation/Reference/CFTreeRef/) (with apologies to [NSTreeNode](https://developer.apple.com/library/mac/documentation/cocoa/reference/NSTreeNode_class/)).
4 |
5 | Born out of a desire to not work with CFTree objects.
6 |
7 | ## Installation with [CocoaPods](http://cocoapods.org/)
8 |
9 | ```ruby
10 | pod "JTTree"
11 | ```
12 |
13 | ## Documentation
14 |
15 | See [`JTTree.h`](src/JTTree.h) for API. To quote the overview:
16 |
17 | > Each tree node can store a reference to an object. Most methods come in pairs: one to act on tree nodes, and one to act on stored objects directly.
18 |
19 | > Two JTTree objects are equal (according to `-isEqual:`) iff they refer to the same underlying tree node. (Pointer equality is not guaranteed.)
20 |
21 | > All behavior not explicitly specified here (such as nil return values, tree lifetime, etc.) should be assumed to be the same as CFTree, which, in many cases, also does not specify its behavior explicitly.
22 |
23 | ## Contributing
24 |
25 | Submit a [pull request](http://github.com/jtbandes/JTTree/pulls)!
26 |
27 | ## License
28 |
29 | JTTree is released under the [Unlicense](http://unlicense.org/):
30 |
31 | > This is free and unencumbered software released into the public domain.
32 |
33 | > Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
34 |
35 | > In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
36 |
37 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 |
--------------------------------------------------------------------------------
/src/JTTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // JTTree.h
3 | // JTTree
4 | //
5 | // Created by Jacob Bandes-Storch on 7/29/13.
6 | //
7 |
8 | #import
9 |
10 |
11 | /**
12 | * Options for tree traversal.
13 | *
14 | * Traversing the following example tree starting from node A
15 | * produces the following forward traversal orders:
16 | *
17 | * Children only: B C
18 | * Breadth-first: A B C D E
19 | * In-order (binary only): D B E A C
20 | * Depth-first pre-order: A B D E C
21 | * Depth-first post-order: D E B C A
22 | *
23 | * A
24 | * / \
25 | * B C
26 | * / \
27 | * D E
28 | *
29 | */
30 | typedef NS_ENUM(NSUInteger, JTTreeTraversalOptions) {
31 | // Forward or reverse traversal
32 | JTTreeTraversalReverse = 1 << 0,
33 | // Traversal order options (mutually exclusive)
34 | JTTreeTraversalChildrenOnly = 1 << 1,
35 | JTTreeTraversalBreadthFirst = 1 << 2,
36 | JTTreeTraversalDepthFirstPreOrder = 1 << 3,
37 | JTTreeTraversalDepthFirstPostOrder = 1 << 4,
38 | JTTreeTraversalBinaryInOrder = 1 << 5,
39 | JTTreeTraversalOrderMask = (JTTreeTraversalChildrenOnly
40 | | JTTreeTraversalBreadthFirst
41 | | JTTreeTraversalDepthFirstPreOrder
42 | | JTTreeTraversalDepthFirstPostOrder
43 | | JTTreeTraversalBinaryInOrder),
44 | };
45 |
46 | /**
47 | * A more useful wrapper around CFTree (with apologies to NSTreeNode).
48 | *
49 | * Each tree node can store a reference to an object.
50 | * Most methods come in pairs: one to act on tree nodes, and one to act on stored objects directly.
51 | *
52 | * Two JTTree objects are equal (according to -isEqual:) iff they refer to the same underlying tree node.
53 | * (Pointer equality is not guaranteed.)
54 | *
55 | * All behavior not explicitly specified here (such as nil return values, tree lifetime, etc.)
56 | * should be assumed to be the same as CFTree, which, in many cases, also does not specify its behavior explicitly.
57 | */
58 | @interface JTTree : NSObject
59 |
60 | /** An object stored at the tree node. */
61 | @property (strong) id object;
62 |
63 | /**
64 | * Returns a new tree node storing the given object.
65 | * @param object The object to store at the tree node.
66 | */
67 | + (instancetype)treeWithObject:(id)object;
68 | /**
69 | * Initializes a new tree node storing the given object.
70 | * @param object The object to store at the tree node.
71 | */
72 | - (instancetype)initWithObject:(id)object;
73 |
74 |
75 | #pragma mark Structure
76 |
77 | /** @return Whether the node is a leaf (has no children). */
78 | - (BOOL)isLeaf;
79 |
80 | /**
81 | * Calculates the depth of the node relative to its root parent by traversing upwards through the tree.
82 | * @return The depth of the node in the tree.
83 | */
84 | - (NSUInteger)depth;
85 |
86 | /**
87 | * Calculates the index path of the node relative to its root parent by traversing upwards through the tree.
88 | * @return The index path of the node in the tree.
89 | * @performance This operation is somewhat expensive due to the limitations of the CFTree API.
90 | */
91 | - (NSIndexPath *)indexPath;
92 |
93 | /** @return The receiver's immediate parent. */
94 | - (JTTree *)parent;
95 | /** @return The object stored at the receiver's immediate parent. */
96 | - (id)parentObject;
97 |
98 | /** @return The number of direct children of the receiver. */
99 | - (NSUInteger)numberOfChildren;
100 |
101 | /**
102 | * @return The receiver's child at the specified index.
103 | * @param index An index path specifying a child.
104 | */
105 | - (JTTree *)childAtIndex:(NSUInteger)index;
106 | /**
107 | * @return The object stored at the specified child.
108 | * @param index An index path specifying a child.
109 | */
110 | - (id)childObjectAtIndex:(NSUInteger)index;
111 |
112 |
113 | #pragma mark Traversal
114 |
115 | /**
116 | * Finds the node's root (the ancestor with no parent) by traversing upwards through the tree.
117 | * @return The root of the tree.
118 | */
119 | - (JTTree *)root;
120 | /** @return The object stored at the root of the tree. */
121 | - (id)rootObject;
122 |
123 | /** @return The receiver's first child. */
124 | - (JTTree *)firstChild;
125 | /** @return The object stored at the receiver's first child. */
126 | - (id)firstChildObject;
127 |
128 | /** @return The receiver's last child. */
129 | - (JTTree *)lastChild;
130 | /** @return The object stored at the receiver's last child. */
131 | - (id)lastChildObject;
132 |
133 | /**
134 | * @return The receiver's previous adjacent sibling.
135 | * @performance This operation is somewhat expensive due to the limitations of the CFTree API.
136 | */
137 | - (JTTree *)previousSibling;
138 | /**
139 | * @return The object stored at the receiver's previous adjacent sibling.
140 | * @performance This operation is somewhat expensive due to the limitations of the CFTree API.
141 | */
142 | - (id)previousSiblingObject;
143 |
144 | /** @return The receiver's next adjacent sibling. */
145 | - (JTTree *)nextSibling;
146 | /** @return The object stored at the receiver's next adjacent sibling. */
147 | - (id)nextSiblingObject;
148 |
149 | /**
150 | * Finds a descendant by traversing downwards through the tree.
151 | * @return The descendant at the specified index path.
152 | * @param indexPath An index path specifying a descendant.
153 | */
154 | - (JTTree *)descendantAtIndexPath:(NSIndexPath *)indexPath;
155 | /**
156 | * @return The object stored at the specified index path.
157 | * @param indexPath An index path specifying a descendant.
158 | */
159 | - (id)descendantObjectAtIndexPath:(NSIndexPath *)indexPath;
160 |
161 | /**
162 | * Traverses the tree in the specified manner and executes the block for each descendant.
163 | * @note Mutating the tree during traversal will result in undefined behavior.
164 | * @param options A bitmask that specifies options for traversal.
165 | * @param block The block to apply to each descendant.
166 | */
167 | - (void)enumerateDescendantsWithOptions:(JTTreeTraversalOptions)options
168 | usingBlock:(void (^)(JTTree *descendant, BOOL *stop))block;
169 |
170 |
171 | #pragma mark Manipulation
172 |
173 | /**
174 | * Adds a child node to the receiver at the specified index.
175 | * If @p child is already a member of some tree, it will be removed.
176 | * @param child A node to insert.
177 | * @param index The index at which to insert the child.
178 | */
179 | - (void)insertChild:(JTTree *)child atIndex:(NSUInteger)index;
180 | /**
181 | * Adds a child object to the receiver at the specified index.
182 | * @param obj An object to store at the new node.
183 | * @param index The index at which to insert the child.
184 | */
185 | - (void)insertChildObject:(id)obj atIndex:(NSUInteger)index;
186 |
187 | /**
188 | * Removes a specific child from the receiver.
189 | * @param index The index of the child to remove.
190 | */
191 | - (void)removeChildAtIndex:(NSUInteger)index;
192 |
193 | /** Empties the receiver of all its children. */
194 | - (void)removeAllChildren;
195 |
196 | /** Removes the receiver from its parent. */
197 | - (void)removeFromParent;
198 |
199 | @end
200 |
--------------------------------------------------------------------------------
/test/JTTreeTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // JTTreeTests.m
3 | // JTTreeTests
4 | //
5 | // Created by Jacob Bandes-Storch on 7/30/13.
6 | //
7 |
8 | #import
9 |
10 | #import "JTTree.h"
11 |
12 |
13 | @interface JTTreeTests : SenTestCase
14 | @end
15 |
16 |
17 | @implementation JTTreeTests
18 |
19 | - (void)testStructure
20 | {
21 | JTTree *tree = [JTTree new];
22 | STAssertNotNil(tree, @"New tree should not be nil");
23 |
24 | STAssertTrue([tree isLeaf], @"New tree should be a leaf");
25 | STAssertTrue([tree isEqual:tree.root], @"New tree should be its own root");
26 | STAssertEquals(tree.numberOfChildren, (NSUInteger)0, @"New tree should have no children");
27 | STAssertEquals(tree.depth, (NSUInteger)0, @"New tree should have depth 0");
28 |
29 | [tree insertChildObject:nil atIndex:0];
30 |
31 | STAssertEquals(tree.numberOfChildren, (NSUInteger)1, @"Adding a child should increment number of children");
32 |
33 | JTTree *child1 = [tree childAtIndex:0];
34 |
35 | STAssertNotNil(child1, @"New child should not be nil");
36 | STAssertEqualObjects([NSIndexPath indexPathWithIndex:0], child1.indexPath, @"Child index path should be 0");
37 | STAssertEquals(child1.depth, (NSUInteger)1, @"Child depth should be 1");
38 | STAssertTrue([child1 isLeaf], @"New child should be a leaf");
39 | STAssertEqualObjects(tree, child1.parent, @"New child's parent should be original tree");
40 | STAssertEqualObjects(tree, child1.root, @"New child's root should be original tree");
41 | STAssertNil(child1.nextSibling, @"New child should have no next sibling");
42 | STAssertNil(child1.previousSibling, @"New child should have no previous sibling");
43 | STAssertEqualObjects(child1, tree.firstChild, @"New child should be parent's first child");
44 | STAssertEqualObjects(child1, tree.lastChild, @"New child should be parent's last child");
45 | STAssertFalse([tree isLeaf], @"Tree with children should not be a leaf");
46 |
47 | [tree insertChildObject:nil atIndex:0];
48 |
49 | JTTree *child2 = [tree childAtIndex:0];
50 | STAssertEqualObjects(child1, child2.nextSibling, @"Child should be inserted before sibling");
51 | STAssertEqualObjects([NSIndexPath indexPathWithIndex:1], child1.indexPath, @"Child index path should be 1");
52 | STAssertEqualObjects(child1, [tree childAtIndex:1], @"Sibling index should have incremented");
53 | STAssertEqualObjects([NSIndexPath indexPathWithIndex:1], child1.indexPath, @"Sibling index path should be 1");
54 | STAssertEqualObjects(child2, child1.previousSibling, @"Child should be inserted before sibling");
55 | STAssertNil(child1.nextSibling, @"Last child should have no next sibling");
56 | STAssertNil(child2.previousSibling, @"First child should have no previous sibling");
57 | STAssertEqualObjects(child2, tree.firstChild, @"New child should be parent's first child");
58 | STAssertEqualObjects(child1, tree.lastChild, @"Parent's last child should not change");
59 |
60 | [child1 insertChildObject:nil atIndex:0];
61 |
62 | JTTree *grandchild = [child1 childAtIndex:0];
63 | STAssertNotNil(grandchild, @"Grandchild should not be nil");
64 | NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:((NSUInteger[]){1,0}) length:2];
65 | STAssertEqualObjects(grandchild, [tree descendantAtIndexPath:indexPath], @"Grandchild should be reachable by index path 1 0");
66 | STAssertEqualObjects(indexPath, grandchild.indexPath, @"Grandchild index path should be 1 0");
67 | STAssertEquals(grandchild.depth, (NSUInteger)2, @"Grandchild depth should be 2");
68 |
69 | JTTree *intermediate = [JTTree treeWithObject:nil];
70 | [child1 insertChild:intermediate atIndex:0];
71 | [grandchild removeFromParent];
72 | [intermediate insertChild:grandchild atIndex:0];
73 | STAssertEquals(grandchild.depth, (NSUInteger)3, @"Descendant depth should be 3 after inserting a node above");
74 | }
75 |
76 | - (void)testTraversal
77 | {
78 | // * A
79 | // * / \
80 | // * B C
81 | // * / \
82 | // * D E
83 |
84 | JTTree *a = [JTTree treeWithObject:@"a"];
85 | JTTree *b = [JTTree treeWithObject:@"b"];
86 | JTTree *c = [JTTree treeWithObject:@"c"];
87 | JTTree *d = [JTTree treeWithObject:@"d"];
88 | JTTree *e = [JTTree treeWithObject:@"e"];
89 |
90 | [a insertChild:b atIndex:0];
91 | [a insertChild:c atIndex:1];
92 | [b insertChild:d atIndex:0];
93 | [b insertChild:e atIndex:1];
94 |
95 | NSMutableArray *results = [NSMutableArray array];
96 | id block = ^(JTTree *descendant, BOOL *stop) { [results addObject:descendant]; };
97 |
98 | STAssertThrows([a enumerateDescendantsWithOptions:JTTreeTraversalChildrenOnly|JTTreeTraversalBreadthFirst
99 | usingBlock:block], @"Two traversal options should fail");
100 |
101 | [a enumerateDescendantsWithOptions:JTTreeTraversalChildrenOnly usingBlock:block];
102 | STAssertEqualObjects(results, (@[ b, c ]), @"Children-only traversal should visit B C");
103 | [results removeAllObjects];
104 |
105 | [a enumerateDescendantsWithOptions:JTTreeTraversalBreadthFirst usingBlock:block];
106 | STAssertEqualObjects(results, (@[ a, b, c, d, e ]), @"Breadth-first traversal should visit A B C D E");
107 | [results removeAllObjects];
108 |
109 | [a enumerateDescendantsWithOptions:JTTreeTraversalDepthFirstPreOrder usingBlock:block];
110 | STAssertEqualObjects(results, (@[ a, b, d, e, c ]), @"Depth-first pre-order traversal should visit A B D E C");
111 | [results removeAllObjects];
112 |
113 | [a enumerateDescendantsWithOptions:JTTreeTraversalDepthFirstPostOrder usingBlock:block];
114 | STAssertEqualObjects(results, (@[ d, e, b, c, a ]), @"Depth-first post-order traversal should visit D E B C A");
115 | [results removeAllObjects];
116 |
117 | [a enumerateDescendantsWithOptions:JTTreeTraversalBinaryInOrder usingBlock:block];
118 | STAssertEqualObjects(results, (@[ d, b, e, a, c ]), @"In-order traversal should visit D B E A C");
119 | [results removeAllObjects];
120 |
121 | [a insertChildObject:nil atIndex:2];
122 | STAssertThrows([a enumerateDescendantsWithOptions:JTTreeTraversalBinaryInOrder usingBlock:block],
123 | @"Binary traversal should fail for non-binary tree");
124 | }
125 |
126 | - (void)testObjectLifetime
127 | {
128 | JTTree *tree = [JTTree new];
129 | id obj = [NSObject new];
130 | id __weak weakObj = obj;
131 |
132 | tree.object = obj;
133 |
134 | obj = nil;
135 | STAssertNotNil(weakObj, @"Object should not be released before tree node");
136 |
137 | tree = nil;
138 | STAssertNil(weakObj, @"Object should be released along with tree node");
139 |
140 | tree = [JTTree new];
141 | obj = [NSObject new];
142 | weakObj = obj;
143 |
144 | [tree insertChildObject:obj atIndex:0];
145 | obj = nil;
146 | STAssertNotNil(weakObj, @"Object should not be released before parent");
147 |
148 | [tree removeAllChildren];
149 | STAssertNil(weakObj, @"Object should be released when removed from parent");
150 |
151 | tree = [JTTree new];
152 | obj = [NSObject new];
153 | weakObj = obj;
154 |
155 | [tree insertChildObject:obj atIndex:0];
156 | obj = nil;
157 | STAssertNotNil(weakObj, @"Object should not be released before parent");
158 |
159 | tree = nil;
160 | STAssertNil(weakObj, @"Child should be released along with parent");
161 | }
162 |
163 | @end
164 |
--------------------------------------------------------------------------------
/JTTree.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 165EC8D317A8AD3300B43CF4 /* JTTreeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16F895AA17A7D10900552CFC /* JTTreeTests.m */; };
11 | 16B8E25E17A8ADE700A70EDB /* JTTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 1635386017A652EE00FC5CAA /* JTTree.m */; };
12 | 16F895A317A7D10900552CFC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16F895A217A7D10900552CFC /* SenTestingKit.framework */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXFileReference section */
16 | 1635385017A6522500FC5CAA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
17 | 1635385F17A652EE00FC5CAA /* JTTree.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JTTree.h; sourceTree = ""; };
18 | 1635386017A652EE00FC5CAA /* JTTree.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JTTree.m; sourceTree = ""; };
19 | 16F895A117A7D10900552CFC /* JTTreeTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JTTreeTests.octest; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 16F895A217A7D10900552CFC /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; };
21 | 16F895A617A7D10900552CFC /* JTTreeTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "JTTreeTests-Info.plist"; sourceTree = ""; };
22 | 16F895AA17A7D10900552CFC /* JTTreeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JTTreeTests.m; sourceTree = ""; };
23 | /* End PBXFileReference section */
24 |
25 | /* Begin PBXFrameworksBuildPhase section */
26 | 16F8959E17A7D10900552CFC /* Frameworks */ = {
27 | isa = PBXFrameworksBuildPhase;
28 | buildActionMask = 2147483647;
29 | files = (
30 | 16F895A317A7D10900552CFC /* SenTestingKit.framework in Frameworks */,
31 | );
32 | runOnlyForDeploymentPostprocessing = 0;
33 | };
34 | /* End PBXFrameworksBuildPhase section */
35 |
36 | /* Begin PBXGroup section */
37 | 1635384417A6522500FC5CAA = {
38 | isa = PBXGroup;
39 | children = (
40 | 1635385217A6522500FC5CAA /* src */,
41 | 16F895A417A7D10900552CFC /* test */,
42 | 1635384F17A6522500FC5CAA /* Frameworks */,
43 | 1635384E17A6522500FC5CAA /* Products */,
44 | );
45 | sourceTree = "";
46 | };
47 | 1635384E17A6522500FC5CAA /* Products */ = {
48 | isa = PBXGroup;
49 | children = (
50 | 16F895A117A7D10900552CFC /* JTTreeTests.octest */,
51 | );
52 | name = Products;
53 | sourceTree = "";
54 | };
55 | 1635384F17A6522500FC5CAA /* Frameworks */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 1635385017A6522500FC5CAA /* Foundation.framework */,
59 | 16F895A217A7D10900552CFC /* SenTestingKit.framework */,
60 | );
61 | name = Frameworks;
62 | sourceTree = "";
63 | };
64 | 1635385217A6522500FC5CAA /* src */ = {
65 | isa = PBXGroup;
66 | children = (
67 | 1635385F17A652EE00FC5CAA /* JTTree.h */,
68 | 1635386017A652EE00FC5CAA /* JTTree.m */,
69 | );
70 | path = src;
71 | sourceTree = "";
72 | };
73 | 16F895A417A7D10900552CFC /* test */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 16F895AA17A7D10900552CFC /* JTTreeTests.m */,
77 | 16F895A617A7D10900552CFC /* JTTreeTests-Info.plist */,
78 | );
79 | path = test;
80 | sourceTree = "";
81 | };
82 | /* End PBXGroup section */
83 |
84 | /* Begin PBXNativeTarget section */
85 | 16F895A017A7D10900552CFC /* JTTreeTests */ = {
86 | isa = PBXNativeTarget;
87 | buildConfigurationList = 16F895AF17A7D10900552CFC /* Build configuration list for PBXNativeTarget "JTTreeTests" */;
88 | buildPhases = (
89 | 16F8959D17A7D10900552CFC /* Sources */,
90 | 16F8959E17A7D10900552CFC /* Frameworks */,
91 | 16F8959F17A7D10900552CFC /* Resources */,
92 | );
93 | buildRules = (
94 | );
95 | dependencies = (
96 | );
97 | name = JTTreeTests;
98 | productName = JTTreeTests;
99 | productReference = 16F895A117A7D10900552CFC /* JTTreeTests.octest */;
100 | productType = "com.apple.product-type.bundle";
101 | };
102 | /* End PBXNativeTarget section */
103 |
104 | /* Begin PBXProject section */
105 | 1635384517A6522500FC5CAA /* Project object */ = {
106 | isa = PBXProject;
107 | attributes = {
108 | LastUpgradeCheck = 0500;
109 | ORGANIZATIONNAME = "Jacob Bandes-Storch";
110 | };
111 | buildConfigurationList = 1635384817A6522500FC5CAA /* Build configuration list for PBXProject "JTTree" */;
112 | compatibilityVersion = "Xcode 3.2";
113 | developmentRegion = English;
114 | hasScannedForEncodings = 0;
115 | knownRegions = (
116 | en,
117 | );
118 | mainGroup = 1635384417A6522500FC5CAA;
119 | productRefGroup = 1635384E17A6522500FC5CAA /* Products */;
120 | projectDirPath = "";
121 | projectRoot = "";
122 | targets = (
123 | 16F895A017A7D10900552CFC /* JTTreeTests */,
124 | );
125 | };
126 | /* End PBXProject section */
127 |
128 | /* Begin PBXResourcesBuildPhase section */
129 | 16F8959F17A7D10900552CFC /* Resources */ = {
130 | isa = PBXResourcesBuildPhase;
131 | buildActionMask = 2147483647;
132 | files = (
133 | );
134 | runOnlyForDeploymentPostprocessing = 0;
135 | };
136 | /* End PBXResourcesBuildPhase section */
137 |
138 | /* Begin PBXSourcesBuildPhase section */
139 | 16F8959D17A7D10900552CFC /* Sources */ = {
140 | isa = PBXSourcesBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 165EC8D317A8AD3300B43CF4 /* JTTreeTests.m in Sources */,
144 | 16B8E25E17A8ADE700A70EDB /* JTTree.m in Sources */,
145 | );
146 | runOnlyForDeploymentPostprocessing = 0;
147 | };
148 | /* End PBXSourcesBuildPhase section */
149 |
150 | /* Begin XCBuildConfiguration section */
151 | 1635385917A6522500FC5CAA /* Debug */ = {
152 | isa = XCBuildConfiguration;
153 | buildSettings = {
154 | ALWAYS_SEARCH_USER_PATHS = NO;
155 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
156 | CLANG_CXX_LIBRARY = "libc++";
157 | CLANG_ENABLE_MODULES = YES;
158 | CLANG_ENABLE_OBJC_ARC = YES;
159 | CLANG_WARN_BOOL_CONVERSION = YES;
160 | CLANG_WARN_CONSTANT_CONVERSION = YES;
161 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
162 | CLANG_WARN_EMPTY_BODY = YES;
163 | CLANG_WARN_ENUM_CONVERSION = YES;
164 | CLANG_WARN_INT_CONVERSION = YES;
165 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
166 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
167 | COPY_PHASE_STRIP = NO;
168 | GCC_C_LANGUAGE_STANDARD = gnu99;
169 | GCC_DYNAMIC_NO_PIC = NO;
170 | GCC_ENABLE_OBJC_EXCEPTIONS = YES;
171 | GCC_OPTIMIZATION_LEVEL = 0;
172 | GCC_PREPROCESSOR_DEFINITIONS = (
173 | "DEBUG=1",
174 | "$(inherited)",
175 | );
176 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
177 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
178 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
179 | GCC_WARN_UNDECLARED_SELECTOR = YES;
180 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
181 | GCC_WARN_UNUSED_FUNCTION = YES;
182 | GCC_WARN_UNUSED_VARIABLE = YES;
183 | MACOSX_DEPLOYMENT_TARGET = 10.9;
184 | ONLY_ACTIVE_ARCH = YES;
185 | SDKROOT = macosx;
186 | };
187 | name = Debug;
188 | };
189 | 1635385A17A6522500FC5CAA /* Release */ = {
190 | isa = XCBuildConfiguration;
191 | buildSettings = {
192 | ALWAYS_SEARCH_USER_PATHS = NO;
193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
194 | CLANG_CXX_LIBRARY = "libc++";
195 | CLANG_ENABLE_MODULES = YES;
196 | CLANG_ENABLE_OBJC_ARC = YES;
197 | CLANG_WARN_BOOL_CONVERSION = YES;
198 | CLANG_WARN_CONSTANT_CONVERSION = YES;
199 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
200 | CLANG_WARN_EMPTY_BODY = YES;
201 | CLANG_WARN_ENUM_CONVERSION = YES;
202 | CLANG_WARN_INT_CONVERSION = YES;
203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
204 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
205 | COPY_PHASE_STRIP = YES;
206 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
207 | ENABLE_NS_ASSERTIONS = NO;
208 | GCC_C_LANGUAGE_STANDARD = gnu99;
209 | GCC_ENABLE_OBJC_EXCEPTIONS = YES;
210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
212 | GCC_WARN_UNDECLARED_SELECTOR = YES;
213 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
214 | GCC_WARN_UNUSED_FUNCTION = YES;
215 | GCC_WARN_UNUSED_VARIABLE = YES;
216 | MACOSX_DEPLOYMENT_TARGET = 10.9;
217 | SDKROOT = macosx;
218 | };
219 | name = Release;
220 | };
221 | 16F895B017A7D10900552CFC /* Debug */ = {
222 | isa = XCBuildConfiguration;
223 | buildSettings = {
224 | FRAMEWORK_SEARCH_PATHS = (
225 | "$(DEVELOPER_FRAMEWORKS_DIR)",
226 | "$(inherited)",
227 | );
228 | GCC_PREPROCESSOR_DEFINITIONS = (
229 | "DEBUG=1",
230 | "$(inherited)",
231 | );
232 | INFOPLIST_FILE = "test/JTTreeTests-Info.plist";
233 | PRODUCT_NAME = "$(TARGET_NAME)";
234 | WRAPPER_EXTENSION = octest;
235 | };
236 | name = Debug;
237 | };
238 | 16F895B117A7D10900552CFC /* Release */ = {
239 | isa = XCBuildConfiguration;
240 | buildSettings = {
241 | FRAMEWORK_SEARCH_PATHS = (
242 | "$(DEVELOPER_FRAMEWORKS_DIR)",
243 | "$(inherited)",
244 | );
245 | INFOPLIST_FILE = "test/JTTreeTests-Info.plist";
246 | PRODUCT_NAME = "$(TARGET_NAME)";
247 | WRAPPER_EXTENSION = octest;
248 | };
249 | name = Release;
250 | };
251 | /* End XCBuildConfiguration section */
252 |
253 | /* Begin XCConfigurationList section */
254 | 1635384817A6522500FC5CAA /* Build configuration list for PBXProject "JTTree" */ = {
255 | isa = XCConfigurationList;
256 | buildConfigurations = (
257 | 1635385917A6522500FC5CAA /* Debug */,
258 | 1635385A17A6522500FC5CAA /* Release */,
259 | );
260 | defaultConfigurationIsVisible = 0;
261 | defaultConfigurationName = Release;
262 | };
263 | 16F895AF17A7D10900552CFC /* Build configuration list for PBXNativeTarget "JTTreeTests" */ = {
264 | isa = XCConfigurationList;
265 | buildConfigurations = (
266 | 16F895B017A7D10900552CFC /* Debug */,
267 | 16F895B117A7D10900552CFC /* Release */,
268 | );
269 | defaultConfigurationIsVisible = 0;
270 | defaultConfigurationName = Release;
271 | };
272 | /* End XCConfigurationList section */
273 | };
274 | rootObject = 1635384517A6522500FC5CAA /* Project object */;
275 | }
276 |
--------------------------------------------------------------------------------
/src/JTTree.m:
--------------------------------------------------------------------------------
1 | //
2 | // JTTree.m
3 | // JTTree
4 | //
5 | // Created by Jacob Bandes-Storch on 7/29/13.
6 | //
7 |
8 | #import "JTTree.h"
9 | #import
10 |
11 |
12 | CF_INLINE CFTreeRef JT_CFTreeGetDescendantAtIndexPath(CFTreeRef tree, NSIndexPath *indexPath)
13 | {
14 | NSUInteger numIndices = [indexPath length];
15 | NSUInteger *indices = malloc(numIndices * sizeof(NSUInteger));
16 |
17 | [indexPath getIndexes:indices];
18 |
19 | CFTreeRef current = tree;
20 | for (NSUInteger i = 0; i < numIndices; i++)
21 | current = CFTreeGetChildAtIndex(current, indices[i]);
22 |
23 | free(indices);
24 | return current;
25 | }
26 |
27 | CF_INLINE CFTreeRef JT_CFTreeCreateWithContextObject(id obj)
28 | {
29 | return CFTreeCreate(NULL, &(CFTreeContext){
30 | .version = 0,
31 | .info = (__bridge void *)obj,
32 | .retain = obj ? CFRetain : NULL,
33 | .release = obj ? CFRelease : NULL,
34 | .copyDescription = CFCopyDescription
35 | });
36 | }
37 |
38 | CF_INLINE void JT_CFTreeSetContextObject(CFTreeRef tree, id obj)
39 | {
40 | CFTreeSetContext(tree, &(CFTreeContext){
41 | .version = 0,
42 | .info = (__bridge void *)obj,
43 | .retain = obj ? CFRetain : NULL,
44 | .release = obj ? CFRelease : NULL,
45 | .copyDescription = CFCopyDescription
46 | });
47 | }
48 |
49 | CF_INLINE id JT_CFTreeGetContextObject(CFTreeRef tree)
50 | {
51 | if (!tree) return nil;
52 |
53 | CFTreeContext context;
54 | CFTreeGetContext(tree, &context);
55 | return (__bridge id)context.info;
56 | }
57 |
58 | CF_INLINE CFIndex JT_CFTreeGetIndexInParent(CFTreeRef tree, CFTreeRef *outParent)
59 | {
60 | if (!tree) return kCFNotFound;
61 |
62 | CFTreeRef parent = CFTreeGetParent(tree);
63 | if (outParent) *outParent = parent;
64 |
65 | if (!parent) return kCFNotFound;
66 |
67 | CFIndex numChildren = CFTreeGetChildCount(parent);
68 | CFTreeRef *children = malloc(numChildren * sizeof(CFTreeRef));
69 |
70 | CFTreeGetChildren(parent, children);
71 |
72 | CFIndex index = kCFNotFound;
73 | for (CFIndex i = 0; i < numChildren; i++)
74 | {
75 | if (children[i] == tree)
76 | {
77 | index = i;
78 | break;
79 | }
80 | }
81 |
82 | free(children);
83 | return index;
84 | }
85 |
86 |
87 | @implementation JTTree {
88 | CFTreeRef _tree;
89 | }
90 |
91 | /**
92 | * Creates a tree with the specified backing tree, or if it is nil, returns nil.
93 | * If @p passthrough is not nil, and its backing tree is equal to @p tree, returns @p passthrough itself.
94 | */
95 | NS_INLINE JTTree *JTTreeWithCFTreeOrJTTree(CFTreeRef tree, JTTree *passthrough)
96 | {
97 | if (passthrough && tree == passthrough->_tree) return passthrough;
98 |
99 | if (!tree) return nil;
100 |
101 | return [[JTTree alloc] _initWithCFTree:tree];
102 | }
103 |
104 | - (id)_initWithCFTree:(CFTreeRef)cfTree
105 | {
106 | if ((self = [super init]))
107 | {
108 | _tree = (CFTreeRef)CFRetain(cfTree);
109 | }
110 | return self;
111 | }
112 |
113 | + (instancetype)treeWithObject:(id)object
114 | {
115 | return [[self alloc] initWithObject:object];
116 | }
117 | - (instancetype)initWithObject:(id)object
118 | {
119 | if ((self = [super init]))
120 | {
121 | _tree = JT_CFTreeCreateWithContextObject(object);
122 | }
123 | return self;
124 | }
125 |
126 | - (id)init
127 | {
128 | return [self initWithObject:nil];
129 | }
130 |
131 | - (void)dealloc
132 | {
133 | CFRelease(_tree);
134 | }
135 |
136 | - (NSString *)description
137 | {
138 | return [NSString stringWithFormat:@"<%@: %p>{children = %ld, object = %@}",
139 | [self class], self, CFTreeGetChildCount(_tree), JT_CFTreeGetContextObject(_tree)];
140 | }
141 |
142 | - (BOOL)isEqual:(JTTree *)object
143 | {
144 | return object && CFEqual(_tree, object->_tree);
145 | }
146 | - (NSUInteger)hash
147 | {
148 | return CFHash(_tree);
149 | }
150 |
151 | - (id)object
152 | {
153 | return JT_CFTreeGetContextObject(_tree);
154 | }
155 | - (void)setObject:(id)obj
156 | {
157 | JT_CFTreeSetContextObject(_tree, obj);
158 | }
159 |
160 |
161 | #pragma mark Structure
162 |
163 | - (BOOL)isLeaf
164 | {
165 | return CFTreeGetChildCount(_tree) == 0;
166 | }
167 |
168 | - (JTTree *)parent
169 | {
170 | return JTTreeWithCFTreeOrJTTree(CFTreeGetParent(_tree), nil);
171 | }
172 | - (id)parentObject
173 | {
174 | return JT_CFTreeGetContextObject(CFTreeGetParent(_tree));
175 | }
176 |
177 | - (NSUInteger)numberOfChildren
178 | {
179 | return CFTreeGetChildCount(_tree);
180 | }
181 |
182 | - (JTTree *)childAtIndex:(NSUInteger)index
183 | {
184 | return JTTreeWithCFTreeOrJTTree(CFTreeGetChildAtIndex(_tree, index), nil);
185 | }
186 | - (id)childObjectAtIndex:(NSUInteger)index
187 | {
188 | return JT_CFTreeGetContextObject(CFTreeGetChildAtIndex(_tree, index));
189 | }
190 |
191 | - (NSUInteger)depth
192 | {
193 | NSUInteger depth = 0;
194 |
195 | for (CFTreeRef current = _tree ? CFTreeGetParent(_tree) : NULL; current; current = CFTreeGetParent(current))
196 | depth++;
197 |
198 | return depth;
199 | }
200 |
201 | - (NSIndexPath *)indexPath
202 | {
203 | NSUInteger pathLength = 0;
204 |
205 | // Store a reverse linked list allowing us to build up the index path in linear time.
206 | typedef struct _indexList {
207 | NSUInteger index;
208 | struct _indexList *prev;
209 | } indexList;
210 |
211 | indexList *node = NULL;
212 |
213 | CFTreeRef current = _tree;
214 | while (current != nil)
215 | {
216 | CFTreeRef parent = NULL;
217 |
218 | NSUInteger indexInParent = JT_CFTreeGetIndexInParent(current, &parent);
219 |
220 | if (!parent) break;
221 |
222 | indexList *newNode = malloc(sizeof(indexList));
223 | newNode->index = indexInParent;
224 | newNode->prev = node;
225 | node = newNode;
226 | pathLength++;
227 |
228 | current = parent;
229 | }
230 |
231 | // Convert the linked list into a contiguous array.
232 | NSUInteger *indices = malloc(pathLength * sizeof(NSUInteger));
233 | for (NSUInteger i = 0; i < pathLength; i++) {
234 | indices[i] = node->index;
235 |
236 | indexList *oldNode = node;
237 | node = oldNode->prev;
238 | free(oldNode);
239 | }
240 |
241 | NSIndexPath *indexPath = [NSIndexPath indexPathWithIndexes:indices length:pathLength];
242 |
243 | free(indices);
244 | return indexPath;
245 | }
246 |
247 |
248 | #pragma mark Traversal
249 |
250 | - (JTTree *)root
251 | {
252 | return JTTreeWithCFTreeOrJTTree(CFTreeFindRoot(_tree), self);
253 | }
254 | - (id)rootObject
255 | {
256 | return JT_CFTreeGetContextObject(CFTreeFindRoot(_tree));
257 | }
258 |
259 | - (JTTree *)firstChild
260 | {
261 | return JTTreeWithCFTreeOrJTTree(CFTreeGetFirstChild(_tree), nil);
262 | }
263 | - (id)firstChildObject
264 | {
265 | return JT_CFTreeGetContextObject(CFTreeGetFirstChild(_tree));
266 | }
267 |
268 | - (JTTree *)lastChild
269 | {
270 | return JTTreeWithCFTreeOrJTTree(CFTreeGetChildAtIndex(_tree, CFTreeGetChildCount(_tree)-1), nil);
271 | }
272 | - (id)lastChildObject
273 | {
274 | return JT_CFTreeGetContextObject(CFTreeGetChildAtIndex(_tree, CFTreeGetChildCount(_tree)-1));
275 | }
276 |
277 | - (JTTree *)previousSibling
278 | {
279 | CFTreeRef parent = NULL;
280 |
281 | NSUInteger indexInParent = JT_CFTreeGetIndexInParent(_tree, &parent);
282 |
283 | if (!parent) return nil;
284 |
285 | return JTTreeWithCFTreeOrJTTree(CFTreeGetChildAtIndex(CFTreeGetParent(_tree), (CFIndex)indexInParent-1), nil);
286 | }
287 | - (id)previousSiblingObject
288 | {
289 | CFTreeRef parent = NULL;
290 |
291 | NSUInteger indexInParent = JT_CFTreeGetIndexInParent(_tree, &parent);
292 |
293 | if (!parent) return nil;
294 |
295 | return JT_CFTreeGetContextObject(CFTreeGetChildAtIndex(CFTreeGetParent(_tree), (CFIndex)indexInParent-1));
296 | }
297 |
298 | - (JTTree *)nextSibling
299 | {
300 | return JTTreeWithCFTreeOrJTTree(CFTreeGetNextSibling(_tree), nil);
301 | }
302 | - (id)nextSiblingObject
303 | {
304 | return JT_CFTreeGetContextObject(CFTreeGetNextSibling(_tree));
305 | }
306 |
307 | - (JTTree *)descendantAtIndexPath:(NSIndexPath *)indexPath
308 | {
309 | return JTTreeWithCFTreeOrJTTree(JT_CFTreeGetDescendantAtIndexPath(_tree, indexPath), self);
310 | }
311 | - (id)descendantObjectAtIndexPath:(NSIndexPath *)indexPath
312 | {
313 | return JT_CFTreeGetContextObject(JT_CFTreeGetDescendantAtIndexPath(_tree, indexPath));
314 | }
315 |
316 | - (void)enumerateDescendantsWithOptions:(JTTreeTraversalOptions)options
317 | usingBlock:(void (^)(JTTree *, BOOL *))block
318 | {
319 | NSAssert(__builtin_popcount(options & JTTreeTraversalOrderMask) == 1, @"Must specify exactly 1 traversal order");
320 |
321 | NSMutableArray *descendants = [NSMutableArray array];
322 |
323 | // Most traversals are queue-based.
324 | NSMutableArray *nodes = [NSMutableArray arrayWithObject:self];
325 |
326 | // Children-only traversal is easy, as CFTree gives us an API for it.
327 | if (options & JTTreeTraversalChildrenOnly)
328 | {
329 | for (CFTreeRef current = CFTreeGetFirstChild(_tree); current; current = CFTreeGetNextSibling(current))
330 | [descendants addObject:[[JTTree alloc] _initWithCFTree:current]];
331 | }
332 | else if (options & JTTreeTraversalBreadthFirst)
333 | {
334 | while ([nodes count] > 0)
335 | {
336 | JTTree *parent = [nodes objectAtIndex:0];
337 | [nodes removeObjectAtIndex:0];
338 |
339 | [descendants addObject:parent];
340 |
341 | for (CFTreeRef child = CFTreeGetFirstChild(parent->_tree); child; child = CFTreeGetNextSibling(child))
342 | [nodes addObject:[[JTTree alloc] _initWithCFTree:child]];
343 | }
344 | }
345 | else if (options & JTTreeTraversalDepthFirstPreOrder)
346 | {
347 | while ([nodes count] > 0)
348 | {
349 | JTTree *parent = [nodes objectAtIndex:0];
350 | [nodes removeObjectAtIndex:0];
351 |
352 | // Visit a node as soon as it is reached, then.
353 | [descendants addObject:parent];
354 |
355 | NSMutableArray *children = [NSMutableArray array];
356 |
357 | for (CFTreeRef child = CFTreeGetFirstChild(parent->_tree); child; child = CFTreeGetNextSibling(child))
358 | [children addObject:[[JTTree alloc] _initWithCFTree:child]];
359 |
360 | nodes = [[children arrayByAddingObjectsFromArray:nodes] mutableCopy];
361 | }
362 | }
363 | else if (options & JTTreeTraversalDepthFirstPostOrder)
364 | {
365 | NSObject *visitedKey = [NSObject new];
366 | while ([nodes count] > 0)
367 | {
368 | JTTree *parent = [nodes objectAtIndex:0];
369 |
370 | // Visit a node only after all its descendants have been visited.
371 | if (objc_getAssociatedObject(parent, (__bridge void *)visitedKey))
372 | {
373 | objc_setAssociatedObject(parent, (__bridge void *)visitedKey, nil, OBJC_ASSOCIATION_RETAIN);
374 |
375 | [nodes removeObjectAtIndex:0];
376 | [descendants addObject:parent];
377 | }
378 | else
379 | {
380 | objc_setAssociatedObject(parent, (__bridge void *)visitedKey, @YES, OBJC_ASSOCIATION_RETAIN);
381 |
382 | NSMutableArray *children = [NSMutableArray array];
383 |
384 | for (CFTreeRef child = CFTreeGetFirstChild(parent->_tree); child; child = CFTreeGetNextSibling(child))
385 | [children addObject:[[JTTree alloc] _initWithCFTree:child]];
386 |
387 | nodes = [[children arrayByAddingObjectsFromArray:nodes] mutableCopy];
388 | }
389 | }
390 | }
391 | else if (options & JTTreeTraversalBinaryInOrder)
392 | {
393 | NSObject *visitedKey = [NSObject new];
394 | while ([nodes count] > 0)
395 | {
396 | JTTree *parent = [nodes objectAtIndex:0];
397 |
398 | JTTree *left = [parent childAtIndex:0];
399 | JTTree *right = [left nextSibling];
400 | NSAssert([right nextSibling] == nil, @"Binary traversal reached a node with more than 2 children");
401 |
402 | // Visit a node and its right subtree only after its left subtree has been visited already.
403 | if (objc_getAssociatedObject(parent, (__bridge void *)visitedKey))
404 | {
405 | objc_setAssociatedObject(parent, (__bridge void *)visitedKey, nil, OBJC_ASSOCIATION_RETAIN);
406 |
407 | [nodes removeObjectAtIndex:0];
408 | [descendants addObject:parent];
409 |
410 | if (right) [nodes insertObject:right atIndex:0];
411 | }
412 | else
413 | {
414 | objc_setAssociatedObject(parent, (__bridge void *)visitedKey, @YES, OBJC_ASSOCIATION_RETAIN);
415 |
416 | if (left) [nodes insertObject:left atIndex:0];
417 | }
418 | }
419 | }
420 |
421 | BOOL stop = NO;
422 | for (JTTree *descendant in (options & JTTreeTraversalReverse ? [descendants reverseObjectEnumerator] : descendants))
423 | {
424 | if (stop) break;
425 | block(descendant, &stop);
426 | }
427 | }
428 |
429 |
430 | #pragma mark Manipulation
431 |
432 | - (void)insertChild:(JTTree *)child atIndex:(NSUInteger)index
433 | {
434 | [child removeFromParent];
435 |
436 | if (index == 0)
437 | CFTreePrependChild(_tree, child->_tree);
438 | else
439 | CFTreeInsertSibling(CFTreeGetChildAtIndex(_tree, index-1), child->_tree);
440 | }
441 | - (void)insertChildObject:(id)obj atIndex:(NSUInteger)index
442 | {
443 | CFTreeRef child = JT_CFTreeCreateWithContextObject(obj);
444 |
445 | if (index == 0)
446 | CFTreePrependChild(_tree, child);
447 | else
448 | CFTreeInsertSibling(CFTreeGetChildAtIndex(_tree, index-1), child);
449 |
450 | CFRelease(child);
451 | }
452 |
453 | - (void)removeChildAtIndex:(NSUInteger)index
454 | {
455 | CFTreeRemove(CFTreeGetChildAtIndex(_tree, index));
456 | }
457 |
458 | - (void)removeAllChildren
459 | {
460 | CFTreeRemoveAllChildren(_tree);
461 | }
462 |
463 | - (void)removeFromParent
464 | {
465 | CFTreeRemove(_tree);
466 | }
467 |
468 | @end
469 |
--------------------------------------------------------------------------------