【发布时间】:2017-08-28 20:37:25
【问题描述】:
我有一个[URL],它代表一组特殊的父目录。我得到另一个[URL],它代表分散在系统各处的文件。我想知道这些文件中的任何一个是否在我的任何特殊目录或其任何子目录中。 是否有一种简单/有意的方法可以做到这一点,而无需手动解析/遍历绝对 URL 的路径?
【问题讨论】:
我有一个[URL],它代表一组特殊的父目录。我得到另一个[URL],它代表分散在系统各处的文件。我想知道这些文件中的任何一个是否在我的任何特殊目录或其任何子目录中。 是否有一种简单/有意的方法可以做到这一点,而无需手动解析/遍历绝对 URL 的路径?
【问题讨论】:
NSURL 中没有任何方法可以让您查看另一个 NSURL 是否代表另一个的根路径。
一种可能的解决方案是使用path 属性将两个URL 转换为路径字符串。然后查看一个字符串是否是另一个字符串的前缀。但在获取 URL 的路径之前,请同时使用 URLByStandardizingPath 和 URLByResolvingSymlinksInPath 以确保结果一致。
例子:
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
}
【讨论】:
specialPath 添加一个斜杠,否则/super 之类的内容将被视为/supermarket/qfc 的父级。 (NSURLs path 属性去除任何尾随 /。)
NSURL 时,会显示尾部斜杠。但是path 的结果不包括斜线。
略有改进的 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))
}
}
【讨论】:
URL 上将其用作extension
前缀解决方案的问题是它会在以下情况下失败:例如。
PathA: /Documents/untitled folder (1)/SomeFolder/Xyz/Waterfall.mp3
PathB: /Documents/untitled folder
前缀解决方案会告诉您 PathA 是 PathB 的子节点,但事实并非如此。
手动遍历解决方案:
extension String {
func isChildPath(of path: String) -> Bool {
self.count < path.count
&& !zip(URL(fileURLWithPath: self).pathComponents,
URL(fileURLWithPath: path).pathComponents)
.contains(where: !=)
}
}
@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
【讨论】:
self.count < path.count && !zip((self as NSString).pathComponents, (path as NSString).pathComponents).contains(where: !=) - 如果我用它更新您的答案,您可以吗?此外,使用它们创建一个新 URL(使用 URL(fileURLWithPath:))而不是将它们桥接到 NSString 具有相同的成本 O(1),但适用于 Ubuntu。