第十六章 应用的构造

应用束

应用束的构造
Cocoa应用会将可执行文件或必要的资源格式保存在一个委托结构中,这称为应用束(application bundle),应用包装(wrapper)或应用包。

像这样,将几个文件或目录以确定的形式保存到目录结构中,然后再作为一个集合进行处理,在Cocoa中是很常见的方法。这样的结构一般称为束。

nib文件和各语言资源
使用Xcode(Interface Builder)生成的GUI定义被写到nib文件中。nib文件的后缀为“nib”,也就是Next Interface Builder。应用的菜单和窗口组件的配置等信息会被归档化,并在执行时被动态读入。

nib文件的后缀名为“nib”或“xib”,xib是XML格式文件,两种后缀的文件内容相同,在构建应用时,会作为带有后缀“nib”的资源文件保存在应用束中。

Storyboard是以场景移动为中心来构造GUI的,所以比较适合边显示边处理这样的应用的架构。在各场景内部,对象间的关系和nib文件相同。在应用被构建的同时,Storyboard内部也会变为nib文件资源。

与语言无关的共享资源在Mac OS系统中被放置在Resources目录下,在iOS系统下则被放置在应用束目录下。根据所选择的语言种类来切换的资源被放置在“语言名.lproj”目录下。后缀来自于language project,并从NeXTstep时期开始使用。

应用在运行时,会在运行环境中查找选择的(或是高优先度的)语言子目录,并使用其中的资源。NSBundle类执行资源检索,因此程序就不需要记录指定语言的编码。

使应用能够对应特定语言的过程称为本地化(localize)。在Cocoa应用中,如果能在编程时使之对应多种语言,那么之后只需在应用束中追加特定语言的资源就能简单的实现本地化。

信息文件的主要内容
使用应用束时,信息内文件关键词CFBundleIdentifier指定的字符串称为应用标识符(application identifier),该标识符常被用来在系统中寻找相应的应用程序。为了不产生重名,推荐使用Java的包定义方式。例如:com.apple.iPhoto。

通过NSBundle访问资源
NSBundle是为各种束提供接口的类,可以从指定的束中搜索有GUI定义的nib文件,图像,声音,加载代码等。

  • (NSBundle *)mainBundle
    //程序所包含的应用束也称为主束,该方法返回鱼主束的路径相对应的对象。不能识别时返回nil。

  • (NSBundle *)bundleWithPath:(NSString *)path
    //返回path指定路径的束对象,没有path可访问的束时返回nil。此外还有使用NSURL指定位置的方法BundleWithURL:

  • (NSString *)bundleIdentifier
    //返回信息文件(info.plist)中指定的应用程序名

  • (NSDictionary *)infoDictionary
    //将信息文件(info.plist)的内容作为字典对象返回

  • (id)objectForInfoDictionaryKey:(NSString *)key
    //在信息文件中返回以参数key为键值的对象。可能的话将其本地化后返回

  • (NSString *)pathForResource:(NSString *)name ofType:(NSString *)extension
    //在接收者的束内,返回name指定的名字和extension指定的有后缀的资源路径名。文件没有后缀时,将extension指定为nil或@“”。找不到文件时返回nil。同样也有使用NSURL返回位置的方法URLForResource:withExtension:。获取保存资源的路径可以采用resourcePath方法。

此外,在Mac OS平台中,可以利用pathForImageResource:方法获得图像路径。它是Application框架中被作为范畴添加到NSBundle中的方法。其他nib文件或声音文件的获取方法请参考“NSBundle Additions Refrerence”。

iOS中的资源访问
如前所述,iOS中也可以使用NSBundle方法来访问资源。但是,因为iOS有特殊的后缀,所以这里先介绍一下概要。

在iOS中,为了适应所有的设备,nib文件的内容和图像的大小就必须要改变,下面有一种指定方法,即根据所使用的设备来自动选择资源或参数。

1.按设备准备任意资源
在iOS中,当束的检索请求被指定为“文件名.后缀”这样的文件时,就会搜索如下的文件。文件名~设备.后缀

2.准备高像素图片
文件名@2x.后缀名
文件名@2x~设备.后缀名(兼容上述设备指定时)
UIKit框架中使用类UIImage来处理图像。该类可以使用如下方法检索并加载主束内的图片文件,生成包含图片的实例。

  • (UIImage)imageNamed:(NSString *)name

3.使用信息文件的键值字符串指定设备
为了和设备相匹配,可以详细设置信息文件中读入的信息。
在一般的键值字符串后加上后缀“~设备”,即可表示仅此设备适用。关于iOS资源的详细内容,请参考“iOS Application Programming Guide”“iPad Programming Guide”“Resource Programming Guide”等参考文档。

通用二进制
通用二进制(universal binary)是苹果公司的专门术语,是为了能在多个操作系统不同的CPU上运行而生成的可执行文件形式。

加载nib文件

nib文件实例化
nib文件定义了GUI组件间的通信消息,其中必须有一个称为所有者的对象。所有者就是连接nib文件内的对象群和外部世界的桥梁。nib文件的访问基本上都是通过所有者来传达的。

nib文件是对象图的归档,所有者的作用就相当于根对象。但是nib文件也可以生成无指向的指针对象。

在Mac OS中加载nib文件

  • (BOOL)loadNibNamed:(NSString *)aNibName owner:(id)owner
    //在束内查找字符串指定的nib文件(不需要后缀名),并将所有者对象owner实例化。如果失败就返回NO。

在使用引用计数的管理方式管理内存时,nib文件内的所有(除了所有者)对象都会被引用计数置为1并被实例化。接着,构造对象间的引用关系,被其他对象持有的对象则通过autorelease来释放所有者关系。如果nib文件中存在没有被引用的对象,那么最终只有该对象不会被释放,所以就需要使用其他方法来释放内存。

使用垃圾回收时,所有者强引用的对象因为可以访问所以没有任何问题。而如果nib文件中存在没有被引用的对象,那么该对象在实例化后就有被释放的危险。

在iOS中加载nib文件

  • (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options
    //在束内查找字符串命名的nib文件,并将所有者对象owner实例化。参数options中可以指定选项,一般置为nil。在nib文件中,没有被其他对象包含的对象(顶级对象)将用数组保存并返回。
    在iOS中,nib文件内的所有(除了所有者)对象都会被引用计数置为1并被实例化。接着,构筑对象间的引用关系,被其他对象持有的对象则通过autorelease来释放所有者关系。使用键值编码的setValue:forKey:方法来设定对象的出口。需要明确出口所使用的实例变量的引用,存储方式时,请准备设置或声明属性。

nib文件内的包含循环
使用ARC管理内存时,nib文件内的对象之间容易形成包含循环,这一点一定要注意避免。

自定义对象包含的出口原则上应该通过弱引用才能使用。

一定要注意不要有空指针,不管是否是弱引用,在释放对象前,都需要将引用其他对象的实例变量赋值为nil。

nib文件内对象的初始化
nib文件内包含的对象如果实现了如下方法,在实例化后该方法就会运行。

  • (void)awakeFromNib
    //从nib文件中读出,在完成实例化,出口及访问的连接后调用该方法。该方法被声明为非正式协议。

启动应用
应用最先读入的nib文件中,保存着菜单等运行应用所需要的重要信息,这称为主nib文件,信息文件中也有文件名,该文件在应用启动后运行回路启动前被读入。

NSApplication实例在应用启动后仅被生成一个,除运行回路外,还负责管理应用的各种资源。

Mac OS 应用:
int NSApplicationMain(int argc, char *argv[]) {
[NSApplication sharedApplication];
//生成NSApplication实例

if ([NSBundle loadNibName:主nib名 owner:NSApp]) //加载主nib文件
    [NSApp run];  //启动运行回路

}

此外,应用中所固有的各种初始化设置,不需要放在main函数中,而是应该写到NSApplication和UIApplication的委托对象中。应用启动后,由于通知启动完成的消息会被送给委托对象,因此应在委托内部书写。详情请参考NSApplicationDelegate协议和UIApplicationDelegate协议的参考文档

iOS的文件保存场所

主要目录及其功能
在iOS中,为了安全起见,应用间的交换等会受到限制。文件保存的场所被局限在了各应用所分配的特定场所中。该场所在应用安装时被决定,称为home目录。应用程序包及生成的文件被保存在home目录下,删除应用时会将其一并删除。

应用的home目录下设置着主要目录,各目录具有不同的功能,所有路径名都是固定的,在这些文件夹中创建的文件,必须由应用程序来管理。不需要的文件会被删除。

1.home/应用名.app/
应用束本身存放在这里。因为有署名所以不能改变。
2.home/Documents/
应用生成并保存文件的场所。与iTunes连接时启动备份。而且将信息文件的键值UIFileSharingEnabled设定为YES后,就可以通过iTunes使文件和电脑同步。
3.home/Library/Preferences/
应用设定被写入。与iTunes连接时被备份。
4.home/Library/Caches/
能够将应用临时使用的信息作为文件保存在这里。在下一次启动时加载可以利用的操作历史或操作过程等信息。但是,在设备还原等时可能会随时消失。iTunes不会备份。
5.home/tmp/
能够将应用临时使用的信息作为文件保存在这里。在应用不运行期间可能会被系统释放。

获取目录路径
在获取上述目录中的Documents和Caches的路径时,推荐使用Foundation框架的函数NSSearchPathForDirectoriesInDomains()。函数和使用的参数在Foundation/NSPathUtilities.h中声明。
NSArray *NSSearchPathForDirectoriesInDomains(
NSSearchDirectory directory,
NSSearchPathDomainMask domainMask,
BOOL expandTilde
)

该函数本来是在Mac OS专用的,形参参数的组合在iOS中可能没有意义。而且,由于返回数组中之保存有一个路径,所以请使用objectAtIndex:方法将首部的元素取出使用。第一个形参中指定目录的种类。为了获得Documents和Caches的路径,请分别指定参数NSDocumentDirectory和NSCachesDirectory。无论哪种情况,都请指定第二个形参为NSUserDomainMask,第三个形参味YES。而且tmp目录的路径可使用下面的函数获取:NSString *NSTemporaryDirectory(void)。

用户默认

保存设定值
在Cocoa应用中,为了保存用户设定的设定值而使用了一种通用结构—用户默认(user defaults),或者称为默认数据库。类NSUserDefaults提供了访问用户默认所需的接口。详情参考官方文档。

默认域
用户默认可以被分为多个组考虑,这些组称为域或默认域(defaults domain)。在域中,有的将内容保存在文件中并在之后继续使用,而有的则仅在应用启动时存在。下面将详细说明:

  1. 应用域
    使用属性列表管理的应用固有的设定值的集合。该设定值的集合称为应用域,域名中使用应用标识名。
  2. 全局域
    使用者使用账户设定的各个应用共享的设定值。域名为NSGlobalDomain。该域在iOS中也有效,可以获得设定的语言或键盘信息。
  3. 形参域
    参数指定的设定值组就称为参数域(argument domain)。域名用NSArgumentDomain表示,且不能被保存在文件中。iOS中不使用该域。
  4. 语言域
    指定使用语言时会生成该语言名(例如ch)为域名的临时域(不保存文件)。
  5. 登陆域
    用户尚未设定应用的各种设定值时使用的默认值集合。域名为NSRegistrationDomain,且不被保存在文件中。
    当应用使用NSUserDefaults类从从键值中查找对应的设定值时,会按如下顺序查找上述5个域,并使用最先找到的值。
    参数域 应用域 全局域 语言域 登陆域
    如果各种设定值都需要默认值,可以将键和值的集合以字典方式组织,并在应用初始化时在登陆域中登陆。因为登陆域查找在最后执行,因此,当其他域都不能登录时,就只能使用默认值。

查找用户默认的工具
Mac OS 平台下,用户默认除了在各个应用中使用外,还可以通过直接编辑属性列表,或使用命令行工具defaults来进行访问。只在终端上输入defaults就可以看到使用方法,也可以使用man命令查看帮助手册。

NSUserDefaults概要

  1. 取得实例对象
  • (NSUserDefaults *)standardUserDefaults
    //最初调用时将实例初始化后返回。自第二次调用起,会返回同一个实例。
  1. 取出键对应的值
    要取出键对应的值,可在NSUserDefaults实例中使用如下方法。
  • (id)objectForKey:(NSString *)defaultName

  • (NSString *)stringForKey:(NSString *)defaultName

  • (NSArray *)stringArrayForKey:(NSString *)defaultName

  • (NSInteger)integerForKey:(NSString *)defaultName

  1. 指定键并设置删除
  • (void)setObject:(nullable id)value forKey:(NSString *)defaultName;

  • (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;

  • (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName;

  1. 设定默认值
  • (void)registerDefaults:(NSDictionary *)registrationDictionary;
  1. 取出域内容
  • (NSDictionary *)dictionaryRepresentation;
  1. 在文件中显示设定内容
  • (BOOL)synchronize;

应用的本地化

消息的本地化
使用束结构可以将程序显示的消息与各种语言相对应。为此,首先需要生成Localizable.strings文件,并将其保存到各语言对应的目录(语言.lprog)中。文件名也可以为别的名字,不特别指定时使用Localizable.strings。

Localizable.strings文件是将键字符串及键值字符串用等号连接成一对,并在结尾设置分号。键和值都要用“”括起来。也可省略值,但也要同时删除等号。值即使被省略,键本身也仍然会被当成入口值。此外,也可以用/**/的形式来书写命令。

键字符串一般使用ASCII码,值字符串使用目标语言(汉语等)。文件一定要使用UTF-16编码。如果仅仅使用ASCII码范围内的字符就可以记录全部时,保持ASCII编码方式即可。

应用本身的束(主束)的NSBundle对象,通过下面的消息,可以取得键值字符串。

  • (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName

NSLocalizedString(key, comment); //oc方式下不使用comment

NSLocalizedStringFromTable(key, tbl, comment); //oc方式下不使用comment

本地化指针
使用日语和英语不同,显示消息不能用日语编码。即使只构建日语应用时也需要Localizable.strings。该文件以及定义了GUI的nib文件,帮助等,包含日语的文件全都应该被保存在ja.lproj目录下。

如果仅仅是英语应用,则不需要担心本地化。包括英语在内,面向各语言的子目录和Localizable.strings基本上都不是必须的。但是,这样的应用被认为没有被国际化(internationalize)。相反,将这样的应用在事后进行国际化或本地化是非常困难的。因为消息是写在源程序里面的,所以必须要使用NSLocalizedString()等来修改。

本地化
本地化(locale)就是编辑文本时的各种习惯或使用单位等的集合体。例如,表示日期的方式,作为小数点使用的字符等,根据国家和语言的不同是存在差异的。为了生成在任何语言环境下都能通用的软件,就需要事先准备好这些字体,然后再在软件操作时选择适合的显示。

为了表示本地化,Cocoa环境提供了NSLocale类,当创建可以应对各种语言或地域习惯的应用时,就可以使用该类。用户根据使用环境而选择的本地化信息称为当前本地化,应用使用当前本地化进行操作。

消息内的语序:
通常,格式字符串内的格式符(%s等)可以与后面的参数依次对应。通过在格式符%后指定“数字”+“$”,就可以将数字指定顺序的参数,在该位置处发生变换。例如:下面的printf就会输出“Good:100”。
printf("%2$s: %1$d\n", 100, "Good");

模块的动态加载

该功能无法在iOS中使用。仅在Mac OS中可以使用。

沙盒(App Sandbox)
App Store中出售,发布的Mac OS应用为了强化其安全性,必须使用App SandBox来实现。沙盒就是只能在预先设定好的权限条件下运行的应用执行环境。在开发和使用App SandBox的应用时,会在Xcode中设置它应该具有什么资格。例如,是否可以读写文件,是否可以访问网络,是否可以使用系统摄像头等。由于这些被放入了代码署名中,因此即使受到恶意影响,也不能执行预先设定之外的动作。而且,与iOS相同,应用可以读写文件的场所也收到了限定。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容