├── .gitignore ├── JTTree.podspec ├── JTTree.xcodeproj └── project.pbxproj ├── LICENSE ├── README.md ├── src ├── JTTree.h └── JTTree.m └── test ├── JTTreeTests-Info.plist └── JTTreeTests.m /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------