我如何查看某个文件夹是否是另一个使用 NSURL 的子文件夹?

How do I see if some folder is a child of another using NSURL?

我有一个 [URL] 代表一组特殊的父目录。我得到另一个 [URL] 代表分散在系统各处的文件。我想知道这些文件中的任何一个是否在我的任何特殊目录或其任何子目录中。 是否有 simple/intended 方法可以做到这一点,无需手动 parsing/traversing 绝对 URL 路径?

NSURL 中没有方法可以让您查看另一个 NSURL 是否代表另一个的根路径。

一种可能的解决方案是使用 path 属性 将两个 URL 转换为路径字符串。然后查看一个字符串是否是另一个字符串的前缀。但在获取 URL 的路径之前,请同时使用 URLByStandardizingPathURLByResolvingSymlinksInPath 以确保结果一致。

示例:

NSURL *specialURL = ... // a URL for one of the special parent directories
NSURL *fileURL = ... // a URL for one of the files to check
NSString *specialPath = [specialURL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path stringByAppendingString:@"/"];
NSString *filePath = fileURL.URLByStandardizingPath.URLByResolvingSymlinksInPath.path
if ([filePath hasPrefix:specialPath]) {
    // This file is in this special directory
}

前缀解决方案的问题在于它会在以下情况下失败:例如

PathA: /Documents/untitled folder (1)/SomeFolder/Xyz/Waterfall.mp3
PathB: /Documents/untitled folder

前缀解决方案会告诉您 PathA 是 PathB 的子级,但事实并非如此。

手动遍历解决方案:

Swift:

extension String {
    func isChildPath(of path: String) -> Bool {
        self.count < path.count
            && !zip(URL(fileURLWithPath: self).pathComponents,
                    URL(fileURLWithPath: path).pathComponents)
                .contains(where: !=)
    }
}

Objective-C:

@interface NSString (Additions)
-(BOOL)isChildOfPath:(NSString *)path;
@end
@implementation NSString (Additions)
-(BOOL)isChildOfPath:(NSString *)path {
   NSString * child = self;
   NSString * parent = path;
   NSArray<NSString *> * childPathComponents = child.pathComponents;
   NSArray<NSString *> * parentPathComponents = parent.pathComponents;
   if (parentPathComponents.count >= childPathComponents.count) {
        return NO;
   }

   for (NSInteger i = 0; i < parentPathComponents.count; i++) {
       NSString * parentComponent = parentPathComponents[i];
       NSString * childComponent = childPathComponents[i];
       if ( ![parentComponent isEqualToString:childComponent] ) {
           return NO;
       }
   }
   return YES;
}
@end

略有改进的 Swift 5 版本结合了 Umair 和 rmaddy 现有优秀答案的优良特性。

就像 Umair 的回答一样,这正确地处理了路径中的文件夹是部分匹配的情况(也就是说,它不会说 /foo/bar-baz/bang/foo/bar/ 作为祖先)。

它还执行规范化(标准化路径以消除 .../ 之类的东西,并解析符号链接),如 rmaddy 的答案。

func pathHasAncestor(maybeChild: URL, maybeAncestor: URL) -> Bool {
    let ancestorComponents: [String] = canonicalize(maybeAncestor).pathComponents
    let childComponents: [String] = canonicalize(maybeChild).pathComponents

    return ancestorComponents.count < childComponents.count
        && !zip(ancestorComponents, childComponents).contains(where: !=)
}

func canonicalize(_ url: URL) -> URL {
    url.standardizedFileURL.resolvingSymlinksInPath()
}

这是一个非常简单的单元测试来验证基本功能:

import XCTest

class FileUtilsTests: XCTestCase {
    func testPathHasAncestor() throws {
        let leaf = URL(fileURLWithPath: "/foo/bar/baz")
        let parent = leaf.deletingLastPathComponent()
        XCTAssertTrue(pathHasAncestor(maybeChild: leaf, maybeAncestor: parent))
        XCTAssertFalse(pathHasAncestor(maybeChild: URL(fileURLWithPath: "/foo/bar 1/baz"), maybeAncestor: parent))
        XCTAssertFalse(pathHasAncestor(maybeChild: leaf, maybeAncestor: leaf))
    }
}