本节书摘来自异步社区《iOS 6高级开发手册(第4版)》一书中的第2章,第2.1节秘诀:处理统一类型标识符,作者 【美】Erica Sadun,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.1 秘诀:处理统一类型标识符
iOS 6高级开发手册(第4版)
统一类型标识符(Uniform Type Identifier,UTI)代表iOS信息共享的中心组件。可以把它们视作下一代MIME类型。UTI是标识资源类型(比如图像和文本)的字符串,它们指定哪些类型的信息将用于公共数据对象。它们不需要为此依赖于老式的指示符,比如文件扩展名、MIME类型,或者文件类型的元数据(比如OSType)。UTI利用更新、更灵活的技术替代了这些项目。

UTI使用反向域名风格的命名约定。常见的源于Apple的标识符看起来如下:public.html和public.jpeg,它们分别指HTML源文本和JPEG图像,二者都是专用的文件信息类型。

继承对于UTI起着重要作用。UTI使用类似于OO的继承系统,其中子UTI具有对父UTI的“is-a”(是一个)关系。子UTI会继承它们的父UTI的所有属性,但是会添加它们表示的数据种类的更多特征。这是由于每个UTI都可以根据需要承担更一般或者更特定的角色。例如,以JPEG UTI为例。JPEG图像(public.jpeg)是一幅图像(public.image),而图像反过来又是一种数据(public.data),数据反过来又是一种用户可查看(或者可收听)的内容(public.content),它是一种项目(public.item),即用于UTI的普通的基本类型。这种层次结构被称为顺应性,其中子UTI顺应父UTI。例如,更具体的jpeg UTI顺应更一般的图像或数据UTI。

图2-1显示了Apple的基本顺应性树的一部分。这棵树上位于较低位置的任何项目都必须顺应其所有父数据属性。声明一个父UTI意味着支持它的所有子UTI。因此,可以打开public.data的应用程序必须能够服务于文本、电影、图像文件等。


987fe88f900b1d36b94dcc6e56265f21b67dd9d3

UTI支持多重继承。一个项目可以顺应多个父UTI 。因此,你可能设想一种同时提供文本和图像容器的数据类型,它声明了对二者的顺应性。

没有用于UTI项目的中心注册表,尽管每个UTI都应该遵守约定。public域是为特定于iOS的类型预留的,是大多数应用程序所共有的。Apple生成了公共项目的一个完整的家族层次结构。可以使用标准的预留域命名方式添加任何特定于第三方公司的名称(例如,com.sadun.myCustomType和com.apple.quicktime-movie)。

2.1.1 通过文件扩展名确定UTI
Mobile Core Services框架提供了一些实用程序,允许基于文件扩展名获取UTI信息。在使用这些基于C语言的函数时,一定要包括头文件,并把应用程序连接到框架。当给下面的函数传递一个路径扩展字符串时,它将返回一个首选的UTI。首选的标识符是单个UTI字符串:

#import <MobileCoreServices/MobileCoreServices.h>

NSString *preferredUTIForExtension(NSString *ext)
{
    // Request the UTI via the file extension
    NSString *theUTI = (__bridge_transfer NSString *)
        UTTypeCreatePreferredIdentifierForTag(
            kUTTagClassFilenameExtension,
            (__bridge CFStringRef) ext, NULL);
    return theUTI;
}

可以使用kUTTagClassMIMEType作为第一个参数,给UTTypeCreatePreferredIdentifierForTag()传递一种MIME类型代替文件扩展名。该函数将为给定的MIME类型返回首选的UTI:

NSString *preferredUTIForMIMEType(NSString *mime)
{
    // Request the UTI via the file extension
    NSString *theUTI = (__bridge_transfer NSString *)
        UTTypeCreatePreferredIdentifierForTag(
        kUTTagClassMIMEType,
        (__bridge CFStringRef) mime, NULL);
    return theUTI;
}

结合使用这些函数,将允许你从文件扩展名和MIME类型转向用于现代文件访问UTI类型。

2.1.2 从UTI转向扩展名或MIME类型
也可以另辟蹊径,从UTI产生首选的扩展名或MIME类型,这要使用UTTypeCopy PreferredTagWithClass()。当给下面的函数传递public.jpeg时,它们将分别返回jpeg和image/jpeg:

NSString *extensionForUTI(NSString *aUTI)
{
    CFStringRef theUTI = (__bridge CFStringRef) aUTI;
    CFStringRef results =
        UTTypeCopyPreferredTagWithClass(
            theUTI,kUTTagClassFilenameExtension);
    return (__bridge_transfer NSString *)results;
}
NSString *mimeTypeForUTI(NSString *aUTI)
{
    CFStringRef theUTI = (__bridge CFStringRef) aUTI;
    CFStringRef results =
        UTTypeCopyPreferredTagWithClass(
            theUTI, kUTTagClassMIMEType);
    return (__bridge_transfer NSString *)results;
}

必须在叶子层使用这些函数,这意味着直接在该层级声明类型扩展名。扩展名声明在属性列表中,其中将描述像文件扩展名和默认图标这样的特性。因此,例如,给扩展名函数传递public.text或public.movie将返回nil,而public.plain-text和public.mpeg则将分别返回扩展名txt和mpg。

前面的项目将在顺应性树中处于较高的位置,从而提供一种抽象类型,而不是特定的实现。对于目前为应用程序定义的给定类,没有现成的API函数用于寻找从它传承下来的项目。你可能想在bugreport.apple.com上归档一个增强请求。诚然,所有的扩展名和MIME类型都会注册在某个位置(否则,UTTypeCopyPreferredTagWithClass()将怎样从一开始就执行向上查找的工作),因此把扩展名映射到更一般的UTI的能力应该是可能存在的。

  1. MIME助手

尽管从扩展名到UTI的服务是很详尽的,可以为几乎任何扩展名返回UTI,但是从UTI到MIME的结果则很随意,有些漫无目标。通常可以为任何公共项目生成正确的MIME表示;公共程度较低的项目则很少见。

下面的代码行显示了各种各样的扩展名、它们的UTI(通过首选的UTIForExtension()获取),以及从每个UTI生成的MIME类型(通过mimeTypeForUTI())。可以看到,其中有相当多的空白。当这些函数不能找到匹配时,它们将返回nil:

xlv: dyn.age81u5d0 / (null)
xlw: com.microsoft.excel.xlw / application/vnd.ms-excel
xm: dyn.age81u5k / (null)
xml: public.xml / application/xml
z: public.z-archive / application/x-compress
zip: public.zip-archive / application/zip
zoo: dyn.age81y55t / (null)
zsh: public.zsh-script / (null)

为了解决这个问题,用于这个秘诀的示例代码包括了一个额外的MIMEHelper类。它定义了一个函数,返回所提供的扩展名的MIME类型:

NSString mimeForExtension(NSString extension);
它的扩展名和MIME类型源于Apache Software Foundation,它将其列表放入公共域中。对于用于这个秘诀的示例代码中的450个扩展名,iOS返回了全部450个UTI,但是只会返回88个MIME类型。Apache列表把这个数字增加到230个可识别的MIME类型。

2.1.3 测试顺应性
使用UTTypeConformsTo()函数测试顺应性。该函数接受两个参数:一个源UTI和一个要比较的UTI,如果第一个UTI顺应第二个UTI,就返回True。可以使用这个函数来测试一个更具体的项目是否顺应一个更一般的项目。相等性测试则使用UTTypeEqual()。下面显示了一个示例,说明了可能如何使用顺应性测试,确定文件路径是否可能指向图像资源:

BOOL pathPointsToLikelyUTIMatch(NSString *path, CFStringRef theUTI)
{
    NSString *extension = path.pathExtension;
    NSString *preferredUTI = preferredUTIForExtension(extension);
    return (UTTypeConformsTo(
        (__bridge CFStringRef) preferredUTI, theUTI));
}

BOOL pathPointsToLikelyImage(NSString *path)
{
    return pathPointsToLikelyUTIMatch(path, CFSTR("public.image"));
}

BOOL pathPointsToLikelyAudio(NSString *path)
{
    return pathPointsToLikelyUTIMatch(path, CFSTR("public.audio"));
}

2.1.4 获取顺应性列表
UTTypeCopyDeclaration()是iOS API中的所有UTI函数中最一般(并且最有用)的函数,它返回包含以下键的字典。

kUTTypeIdentifierKey:UTI名称,它将被传递给函数(例如,public.mpeg)。
kUTTypeConformsToKey:类型顺应的任何父项目(例如,public.mpeg顺应public.movie)。
kUTTypeDescriptionKey:正在考虑的类型(如果存在的话)的现实描述(例如,“MPEG movie”)。
kUTTypeTagSpecificationKey:给定UTI的等价OSType(例如,MPG和MPEG)、文件扩展名(mpg、mpeg、mpe、m75和m15)和MIME类型(视频/mpeg、视频/mpg、视频/x-mpeg和视频/x-mpg)的字典。
除了这些公共项目之外,还会遇到更多的键,它们指定了导入和导出的UTI描述(kUTImportedTypeDeclarationsKey和kUTExportedTypeDeclarationsKey)、与UTI相关联的图标资源(kUTTypeIconFileKey)、指向描述类型的页面的URL(kUTTypeReferenceURLKey),以及为UTI提供版本字符串的版本键(kUTTypeVersionKey)。

使用返回的字典向上通过顺应性树来构建一个数组,表示给定UTI顺应的所有项目。例如,public.mpeg类型顺应public.movie、public.audiovisual-content、public.data、public.item和public.content。通过conformanceArray函数将这些项目返回为一个数组,如秘诀2-1所示。

秘诀2-1 测试顺应性

// Build a declaration dictionary for the given type
NSDictionary *utiDictionary(NSString *aUTI)
{
    NSDictionary *dictionary =
        (__bridge_transfer NSDictionary *)
            UTTypeCopyDeclaration((__bridge CFStringRef) aUTI);
    return dictionary;
}

// Return an array where each member is guaranteed unique
// but that preserves the original ordering wherever possible
NSArray *uniqueArray(NSArray *anArray)
{
    NSMutableArray *copiedArray =
    [NSMutableArray arrayWithArray:anArray];

    for (id object in anArray)
    {
        [copiedArray removeObjectIdenticalTo:object];
        [copiedArray addObject:object];
    }

    return copiedArray;
}

// Return an array representing all UTIs that a given UTI conforms to
NSArray *conformanceArray(NSString *aUTI)
{
    NSMutableArray *results =
        [NSMutableArray arrayWithObject:aUTI];
    NSDictionary *dictionary = utiDictionary(aUTI);
    id conforms = [dictionary objectForKey:
        (__bridge NSString *)kUTTypeConformsToKey];

    // No conformance
    if (!conforms) return results;

    // Single conformance
    if ([conforms isKindOfClass:[NSString class]])
    {
        [results addObjectsFromArray:conformanceArray(conforms)];
        return uniqueArray(results);
    }
    // Iterate through multiple conformance
    if ([conforms isKindOfClass:[NSArray class]])
    {
        for (NSString *eachUTI in (NSArray *) conforms)
            [results addObjectsFromArray:conformanceArray(eachUTI)];
        return uniqueArray(results);
    }
    // Just return the one-item array
    return results;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐