如何在Objective-C中读取文件的MIME类型

35

我想检测iPhone应用程序的文档目录中文件的MIME类型。在查看文档时没有找到任何答案。


1
还有一个很好的答案:stackoverflow_Q - Cullen SUN
9个回答

56

这个方法有些不太正规,但应该可以工作,我不能确定因为只是猜测。

有两个选项:

  1. 如果你只需要 MIME 类型,请使用 timeoutInterval:NSURLRequest。
  2. 如果你还想要数据,你应该使用注释掉的 NSURLRequest。

请确保在一个线程中执行请求,因为它是同步的。

NSString* filePath = [[NSBundle mainBundle] pathForResource:@"imagename" ofType:@"jpg"];
NSString* fullPath = [filePath stringByExpandingTildeInPath];
NSURL* fileUrl = [NSURL fileURLWithPath:fullPath];
//NSURLRequest* fileUrlRequest = [[NSURLRequest alloc] initWithURL:fileUrl];
NSURLRequest* fileUrlRequest = [[NSURLRequest alloc] initWithURL:fileUrl cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:.1];

NSError* error = nil;
NSURLResponse* response = nil;
NSData* fileData = [NSURLConnection sendSynchronousRequest:fileUrlRequest returningResponse:&response error:&error];

fileData; // Ignore this if you're using the timeoutInterval
          // request, since the data will be truncated.

NSString* mimeType = [response MIMEType];

[fileUrlRequest release];

2
不幸的是,在iOS应用程序中,这将在大文件上崩溃,例如无法适应内存的视频。 - geon
1
@geon 很好的观点,+1。我从未说过这是一个完美的解决方案,只是一个 hack。 - slf
1
@geon,你可以在 NSURLConnection 的代理中跳过数据加载,并在 connection:didReceiveResponse: 中分析响应。 - user207128
我本以为将fileUrlRequest的HTTPMethod设置为@"HEAD"会消除文件加载到NSData中的情况,但似乎并非如此。 - brainjam
1
抱歉,但我认为[NSURLRequest -initWithURL:cachePolicy:timeoutInterval:]中的cachePolicy类型是NSURLRequestCachePolicy而不是NSURLCacheStoragePolicy。无论如何,感谢您的发布。 - WeZZard
显示剩余6条评论

49

添加MobileCoreServices框架。

Objective C:

#import <MobileCoreServices/MobileCoreServices.h>    
NSString *fileExtension = [myFileURL pathExtension];
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);

Swift:

import MobileCoreServices

func mimeType(fileExtension: String) -> String? {

    guard !fileExtension.isEmpty else { return nil }

    if let utiRef = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil) {
        let uti = utiRef.takeUnretainedValue()
        utiRef.release()

        if let mimeTypeRef = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType) {
            let mimeType = MIMETypeRef.takeUnretainedValue()
            mimeTypeRef.release()
            return mimeType as String
        }
    }

    return nil
}

1
这是一个很短的问题!这段代码只是解释文件扩展名的MIME类型,还是实际上检查文件中的一些头信息?例如,如果我将PDF重命名为以.txt结尾,它会返回文本文件吗? - Daniel Farrell
我想补充一下,有些文件,比如docx和doc文件,并没有被识别为文档。 - Bryan P
实际上,这取决于文件路径扩展名。对于返回 null 的 plain_file.abcd 不起作用。 - Itachi
它返回了URL的nil值:file:///private/var/mobile/Containers/Data/Application/7FB4FACE-B83E-44D6-B7AD-6AA54D25F3FF/Documents/Inbox/pdf-5.pdf - kemdo
@kemdo,不要传递整个URL,只需传递扩展名即可。也就是说,使用mimeType(fileExtension: myUrl.pathExtension)代替mimeType(fileExtension: myUrl) - Ky -
显示剩余2条评论

13

正如其他人所指出的那样,对于大文件,接受的答案存在问题。我的应用程序处理视频文件,将整个视频文件加载到内存中是让iOS耗尽内存的好方法。这里可以找到更好的方法:

https://dev59.com/Jm025IYBdhLWcg3wc1tM#5998683

来自上述链接的代码:

+ (NSString*) mimeTypeForFileAtPath: (NSString *) path {
  if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
    return nil;
  }
  // Borrowed from https://dev59.com/Jm025IYBdhLWcg3wc1tM
  // itself, derived from  https://dev59.com/nnE95IYBdhLWcg3wI6ft
  CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
  CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
  CFRelease(UTI);
  if (!mimeType) {
    return @"application/octet-stream";
  }
  return [NSMakeCollectable((NSString *)mimeType) autorelease];
}

1
是的,您的答案比上面选定的更好。 - Basheer_CAD
@Vineeth:显然这样是行不通的。但是为了使其与ARC兼容,我们可以删除autorelease调用并在函数中添加@autoreleasepool{}块。 - Yogi

6

Prcela solution在Swift 2中无法使用。以下简化的函数将返回Swift 2中给定文件扩展名的mime类型:

import MobileCoreServices

func mimeTypeFromFileExtension(fileExtension: String) -> String? {
    guard let uti: CFString = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as NSString, nil)?.takeRetainedValue() else {
        return nil
    }

    guard let mimeType: CFString = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() else {
        return nil
    }

    return mimeType as String
}

5

我在使用slf提供的答案时注意到,URL请求似乎需要读取整个文件以确定mime类型(对于大文件不太友好),这是在一个cocoa应用程序中(不是iPhone)。

如果有人想在桌面上实现这一功能,这是我使用的代码片段(基于Louis的建议):

NSString *path = @"/path/to/some/file";

NSTask *task = [[[NSTask alloc] init] autorelease];
[task setLaunchPath: @"/usr/bin/file"];
[task setArguments: [NSArray arrayWithObjects: @"-b", @"--mime-type", path, nil]];

NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput: pipe];

NSFileHandle *file = [pipe fileHandleForReading];

[task launch];
[task waitUntilExit];
if ([task terminationStatus] == YES) {
    NSData *data = [file readDataToEndOfFile];
    return [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease];
} else {
    return nil;
}

如果你针对PDF文件进行调用,它会返回:application/pdf。

4

基于上面的Lawrence Dol / slf答案,我通过将前几个字节切成头部存根并获取其MIMEType来解决了NSURL将整个文件加载到内存中的问题。 我没有对其进行基准测试,但这种方法可能更快。

+ (NSString*) mimeTypeForFileAtPath: (NSString *) path {
    // NSURL will read the entire file and may exceed available memory if the file is large enough. Therefore, we will write the first fiew bytes of the file to a head-stub for NSURL to get the MIMEType from.
    NSFileHandle *readFileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
    NSData *fileHead = [readFileHandle readDataOfLength:100]; // we probably only need 2 bytes. we'll get the first 100 instead.

    NSString *tempPath = [NSHomeDirectory() stringByAppendingPathComponent: @"tmp/fileHead.tmp"];

    [[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil]; // delete any existing version of fileHead.tmp
    if ([fileHead writeToFile:tempPath atomically:YES])
    {
        NSURL* fileUrl = [NSURL fileURLWithPath:path];
        NSURLRequest* fileUrlRequest = [[NSURLRequest alloc] initWithURL:fileUrl cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:.1];

        NSError* error = nil;
        NSURLResponse* response = nil;
        [NSURLConnection sendSynchronousRequest:fileUrlRequest returningResponse:&response error:&error];
        [[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil];
        return [response MIMEType];
    }
    return nil;
}

1
在Mac OS X上,这将通过LaunchServices和UTIs处理。在iPhone上不可用。由于数据进入您的沙盒的唯一方式是您将其放在那里,因此大多数应用程序对它们可以读取的任何文件的数据具有内在知识。
如果您需要此功能,您应该向苹果提交功能请求

这是用户上传到应用程序的内容。我正在使用cocoaHTTPServer允许通过Web浏览器将文件上传到设备中。在处理设备上的文件之前,我想检查文件是否为正确的类型。 - coneybeare
当然,我也想到了。我的意思是,与文件独立于应用程序而来去的操作系统相比,这是一种非常不典型的需求,因此很少有支持它的情况并不令人惊讶。 - Louis Gerbarg

0

请确保在您的文件中导入了coreservices

import <CoreServices/CoreServices.h>


0

我不确定iPhone上的惯例是什么,但如果允许的话,我会在这里使用UNIX哲学:使用程序file,它是在UNIX操作系统上检测文件类型的标准方式。它包括用于文件类型检测的大量魔术标记的数据库。由于file可能没有在iPhone上安装,您可以将其包含在应用程序包中。可能有一个实现file功能的库。

或者,您可以信任浏览器。浏览器在HTTP头中发送他们猜测的MIME类型。我知道我可以轻松地在PHP中获取MIME类型信息。当然,这取决于您是否愿意信任客户端。


你不能在iPhone上生成进程,沙盒应用程序不允许使用fork(),因此使用文件也不可行。另外,我认为你的意思是信任服务器,而不是浏览器。HTTP服务器发送文件的MIME类型。如果他从HTTP服务器获取文件,则可能是可行的,但这一点并不清楚。 - Louis Gerbarg
实际上,他在评论中提到他正在应用程序中运行一个HTTP服务器。根据文件的发送方式,MIME类型可能会被传输或不被传输。 - Louis Gerbarg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接