Dus*_*tin 5 macos cocoa objective-c
在命令行程序中使用NSBundle时,我注意到了一些奇怪的行为.如果,在我的程序中,我使用现有的bundle并制作它的副本,然后尝试使用pathForResource在Resources文件夹中查找某些内容,除非我在程序启动之前查找我正在查找的包,否则总是会返回nil.我创建了一个复制问题的示例应用程序,相关代码是:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *exePath = [NSString stringWithCString:argv[0]
encoding:NSASCIIStringEncoding];
NSString *path = [exePath stringByDeletingLastPathComponent];
NSString *templatePath = [path stringByAppendingPathComponent:@"TestApp.app"];
// This call works because TestApp.app exists before this program is run
NSString *resourcePath = [NSBundle pathForResource:@"InfoPlist"
ofType:@"strings"
inDirectory:templatePath];
NSLog(@"NOCOPY: %@", resourcePath);
NSString *copyPath = [path stringByAppendingPathComponent:@"TestAppCopy.app"];
[[NSFileManager defaultManager] removeItemAtPath:copyPath
error:nil];
if ([[NSFileManager defaultManager] copyItemAtPath:templatePath
toPath:copyPath
error:nil])
{
// This call will fail if TestAppCopy.app does not exist before
// this program is run
NSString *resourcePath2 = [NSBundle pathForResource:@"InfoPlist"
ofType:@"strings"
inDirectory:copyPath];
NSLog(@"COPY: %@", resourcePath2);
[[NSFileManager defaultManager] removeItemAtPath:copyPath
error:nil];
}
[pool release];
}
Run Code Online (Sandbox Code Playgroud)
出于此测试应用程序的目的,我们假设TestApp.app已存在于与我的测试应用程序相同的目录中.如果我运行它,第二个NSLog调用将输出:COPY:(null)
现在,如果我在if语句中注释掉最后的removeItemAtPath调用,那么当我的程序退出TestAppCopy.app仍然存在然后重新运行时,程序将按预期工作.
我在普通的Cocoa应用程序中试过这个,但我无法重现这种行为.它只发生在shell工具目标中.谁能想到这个失败的原因?
顺便说一句:我在10.6.4上尝试这个,我还没有尝试过任何其他版本的Mac OS X.
我可以确认这是 CoreFoundation 中的错误,而不是 Foundation 中的错误。该错误是由于 CFBundle 代码依赖于包含过时数据的目录内容缓存造成的。该代码显然假设捆绑目录及其直接父目录在应用程序运行时都不会更改。
对应的 CoreFoundation 调用+[NSBundle pathForResource:ofType:inDirectory:]是CFBundleCopyResourceURLInDirectory(),并且它表现出相同的错误行为。(这并不奇怪,因为-pathForResource:ofType:inDirectory:它本身使用了这个调用。)
问题最终出在_CFBundleCopyDirectoryContentsAtPath()上。这是在包加载和所有资源查找期间调用的。它缓存有关它在其中查找的目录的信息contentsCache。
问题是这样的:当需要获取 的内容时TestAppCopy.app,包含 的目录的缓存内容TestApp.app不包括TestAppCopy.app。因为缓存表面上具有该目录的内容,所以仅搜索缓存的内容TestAppCopy.app。当TestAppCopy.app未找到时,该函数将其视为明确的“此路径不存在”,并且不会尝试打开该目录:
__CFSpinLock(&CFBundleResourceGlobalDataLock);
if (contentsCache) dirDirContents = (CFArrayRef)CFDictionaryGetValue(contentsCache, dirName);
if (dirDirContents) {
Boolean foundIt = false;
CFIndex dirDirIdx, dirDirLength = CFArrayGetCount(dirDirContents);
for (dirDirIdx = 0; !foundIt && dirDirIdx < dirDirLength; dirDirIdx++) if (kCFCompareEqualTo == CFStringCompare(name, CFArrayGetValueAtIndex(dirDirContents, dirDirIdx), kCFCompareCaseInsensitive)) foundIt = true;
if (!foundIt) tryToOpen = false;
}
__CFSpinUnlock(&CFBundleResourceGlobalDataLock);
Run Code Online (Sandbox Code Playgroud)
因此,内容数组保持为空,并为此路径进行缓存,然后继续查找。我们现在已经缓存了 的(错误地为空)内容TestAppCopy.app,并且当查找深入到该目录时,我们不断遇到错误的缓存信息。当语言查找没有找到任何内容并希望有一个闲逛时,它就会进行尝试en.lproj,但我们仍然找不到任何东西,因为我们正在寻找陈旧的缓存。
CoreFoundation 包含用于刷新 CFBundle 缓存的 SPI 函数。CoreFoundation 中唯一对它们进行公共 API 调用的地方是__CFBundleDeallocate(). 这会刷新有关捆绑包目录本身的所有缓存信息,但不会刷新其父目录:_CFBundleFlushContentsCacheForPath(),这实际上会从缓存中删除数据,仅删除与捆绑包路径的锚定、不区分大小写搜索匹配的键。
似乎 CoreFoundation 的客户端可以刷新有关 的TestApp.app父目录的错误信息的唯一公开方式是使父目录成为捆绑目录(与TestApp.app并存Contents),为父捆绑目录创建一个 CFBundle,然后释放该 CFBundle。TestAppCopy.app但是,似乎如果您在刷新之前尝试使用该捆绑包,那么错误的数据TestAppCopy.app将不会被刷新。
| 归档时间: |
|
| 查看次数: |
1122 次 |
| 最近记录: |