Resource Programming Guide

About Resource

OS X和iOS直接支持的类型:

  • Nib 存放用户界面对象
  • Strings 语言本地化文档
  • Image, Sound, Movies 预渲染文件
  • Property List 一般数据文件格式

Nib Files

AppKit和UIKit都支持nib,在Xcode下使用Interface Builder设计用户界面。

一个app里面的nib文件不是必要的,也可以使用代码取缔。

Anatomy of a Nib File

nib file描述了app中的用户界面,包括windows,views,controls等。它还能描述其他非可视化(non-visual)的object。

nib文件完全重现你在xcode中设计的用户界面。在runtime中nib文件被nib-loading code读入然后重新创建其描述的已经设计好的visual/non-visual object,然后根据配置创建它们之间的内部联系。

About Your Interface Ojbects

nib文件除了可以描述visual objects(windows, views, controls, menus等),interface objects还可以描述non-visual object。通常情况下是用来管理visual objects的额外的controller。虽然你也可以在代码中创建这些object,但是在nib文件中设置会更加方便。

About the File's Owner & First Responder

文档解释的看不明白,以下内容来源于stackoverflow1

The File Owner is an instantiated, runtime object that owns the contents of your nib and its outlets/actions when the nib is loaded. It can be an instance of any class you like - take a look at the identity tab of the tool palette.

For example, consider you have a UIViewController subclass with an IBOutlet for a UILabel. In interface builder the File's owner will be set to the same class as your UIViewController. When your nib is loaded at runtime, the bindings of outlets and actions defined in your nib are bound to the instance of your view controller, as your view controller is the owner.

Nibs are loaded using:

[[NSBundle mainBundle] loadNibNamed:@"NibName" owner:nil options:nil];

The owner parameter is particularly important. That's the runtime instance of a class that owns the contents (outlets, actions and objects) of the nib being loaded.

Hopefully that's clear. To see this at work create a brand new iPhone project with a view controller. Open the Nib file and take a look at the identity tab.

First responder is simply the first object in the responder chain that can respond to events. The responder chain is a runtime collection (or more accurately a hierarchy) of objects that can respond to an event. For example, consider you have a window with a view and on that view is a text field.

If that text field has focus it's known as the first responder in the chain. So if you send a message to the first responder it'll be sent to the text field first. If the text field can't handle the message it'll be sent to the next responder. And the next. And the next, until you get to the end of the responder chain or something has consumed the event (iirc).

The responder chain is worth reading about - hit apple's documentation for more information.

还有另一篇文章可以帮助理解:《Cocoa: What is "File's Owner" in a nib?》2

About the Top-Level Objects

nib文件被加载的时候,除了File’s Owner, First Responder, 和 Application 等 placeholder objects外,windows, menubars, 和 custom controller objects等木有parent的都算是 Top-Level Objects。

一般情况下,你通过nib的File‘s Owner object来保存这些Top-Level objects的指针。如果你不使用outlet,也可以用过nib文件加载函数返回的objects来获得nib文件中的Top-Level objects。

About Image and Sound Resources

nib文件中的View通常需要一些Image和Sound素材,在加载nib文件的时候,iOS会把image方法named caches里面。你可以通过UIImage的imageNamed来获得。

Nib File Design Guidelines

一些设计建议:

  • 除非你的app用户界面非常简单,否则尽量根据逻辑划分nib文件。让一个时刻需要加载的view数量尽可能的少。
  • 重复出现的subview单独放在一个nib文件里面。
  • 很少用到的view单独列出到一个nib文件里面,要用的时候再加载。
  • 让File’s Owner称为nib文件唯一的出口。

Nib Ojbect Life Cycle

The Object Loading Process

nib文件加载步骤:

  1. 加载nib文件和该nib文件(unarchived)引用的所有Resource files(包括image和sound)到内存。
  2. unarchive nib文件,实例化objects。iOS向实现NSCoding protocal的object发送initWithCoder: ,其他发送init消息。
  3. 创建outlet(setValue:forKey:)、action(addTarget:action:forControlEvents:)等连接(包含内部object和object和File's Owner的连接)。设置outlet和action都会发送KVO通知,而且有可能在nib内部所有connection创建完成并调用awakeFromNib方法前发送。
  4. 发送awakeFromNib消息给nib-loading code加载进来的interface object。
  5. 显示所有需要“Visible at Launch time”的view。

Managing the Lifetimes of Objects from Nib Files

NSbundle每次加载nib文件都会创建一个新的copy给你,而不是使用之前同一个nib文件出来的object。

需要指向Top-Level object的强引用。

outlet一般情况使用weak关键字声明,因为outlet大部分时候并不代表ownership关系。Framework class一般都声明好了自己的strong outlets。

//一般情况,单纯是为了传递内容给view
@property (weak) IBOutlet MyView *viewContainerSubview;
//声明ownership,例如File's Owner声明nib文件中的Top-Level object,又或者是你要把nib文件里面一个object拿到nib外的其他地方使用而防止被释放。
@property (strong) IBOutlet MyOtherClass *topLevelObject;

outlet一般私有的,放在Extension里面:

// MyClass.h
  @interface MyClass : MySuperclass
  @end

// MyClass.m
  @interface MyClass ()
  @property (weak) IBOutlet MyView *viewContainerSubview;
  @property (strong) IBOutlet MyOtherClass *topLevelObject;
  @end

希望被subclass的类就要把需要暴露的写到public:

// MyClass.h
@interface MyClass : UITableViewCell
@property (weak, readonly) MyType *outletName;
@end
// MyClass.m
@interface MyClass ()
@property (weak, readwrite) IBOutlet MyType *outletName;
@end

Action Methods

通过IBAction实现

@interface MyClass
- (IBAction)myActionMethod:(id)sender;
@end
// MyClass.h
@interface MyClass
@end

// MyClass.m
  @implementation MyClass
  - (IBAction)myActionMethod:(id)sender {
      // Implementation.
  }
@end

一个规划比较好的例子:

// MyClass.h
  @interface MyClass
  @end
// MyClass.m
  @interface MyClass (PrivateMethods)
  - (void)doSomething;
  - (void)doWorkThatRequiresMeToDoSomething;
  @end
  @implementation MyClass
  - (IBAction)myActionMethod:(id)sender {
      [self doSomething];
  }
  - (void)doSomething {
      // Implementation.
  }
  - (void)doWorkThatRequiresMeToDoSomething {

      // Pre-processing.
      [self doSomething];
      // Post-processing.
    } 

@end

Built-In Support For Nib Files

The Application Loads the Main Nib File

Info.plist 中 NSMainNibFile 关键字指明了app的main nib,在首次打开app时加载。

Each View Controller Manages its Own Nib File

创建Cocoa class的时候可以同时声明是否需要创建对应的nib/xib,勾选的话会自动创建并关联到viewcontroller。

详情见 View Controller Programming Guide for iOS

Loading Nib Files Programmatically

Loading Nib Files Using NSBundle

loadNibNamed:owner:options:

记得传入File's Owner object。

UIKit的 loadNibNamed:owner:options: 返回Nib文件所有Top-Level objects。你可以在File's Owner object中保存好这些Top-Level object的outlet 指针。

- (BOOL)loadMyNibFile
{
    NSArray*    topLevelObjs = nil;
    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"myNib" owner:self options:nil];
    if (topLevelObjs == nil)
    {
        NSLog(@"Error! Could not load myNib file.\n");
        return NO; 
    }
    return YES; 
}

nib文件也可以根据device自动选择,见iOS Supports Device-Specific Resources

Getting a Nib File's Top-Level Objects

UIKit Framework的loadNibNamed:owner:options:方法直接返回所有Top-Level objects的同时,把结果传给一个array(一般情况下),它们会被自动retain/release,省去了ownership管理的麻烦。

Loading Nib Files Using UINib/NSNib

如果你想使用一个nib文件创建多个copy object,UINib/NSNib可以提供更好的性能。

一般的nib loading流程包括从磁盘读入nib文件然后实例化内部所有objects。

UINib/NSNib可以只从磁盘读入一次并把内容原原本本的保存在内存,然后再根据加载到内存的内容实例化所有objects。

分两步

  1. 根据location information创建整个nib文件的实例object。
  2. 实例化nib文件内所有objects,加载到内存。

只有一个NSNib的例子:

- (NSArray*)loadMyNibFile
{
    NSNib*      aNib = [[NSNib alloc] initWithNibNamed:@"MyPanel" bundle:nil];
    NSArray*    topLevelObjs = nil;
    if (![aNib instantiateNibWithOwner:self topLevelObjects:&topLevelObjs])
    {
        NSLog(@"Warning! Could not load nib file.\n");
return nil; }
    // Release the raw nib data.
    [aNib release];
    // Release the top-level objects so that they are just owned by the array.
    [topLevelObjs makeObjectsPerformSelector:@selector(release)];
    // Do not autorelease topLevelObjs.
    return topLevelObjs;
}

Replacing Proxy Ojbects at Load Time

只在iOS平台适用。

在load nib的时候就传入proxy objects(如File‘s Owner)。

Proxy objects represent objects created outside of the nib file but which have some connection to the nib file’s contents.

Proxy objects在nib外部创建但是和nib内object有关系的object,例如File's Owner。

常用在navigation controllers。

placeholder object必须有一个nib内唯一的unique name。可以在inspector window设置。

在加载nib的时候,replacement objects通过options参数传入:

- (void)applicationDidFinishLaunching:(UIApplication *)application
  {
      NSArray*    topLevelObjs = nil;
      NSDictionary*    proxies = [NSDictionary dictionaryWithObject:self   forKey:@"AppDelegate"];
      NSDictionary*    options = [NSDictionary dictionaryWithObject:proxies   forKey:UINibExternalObjects];
      topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"Main" owner:self   options:options];
        if ([topLevelObjs count] == 0)
      {
          NSLog(@"Warning! Could not load myNib file.\n");
            return; 
        }
      // Show window
      [window makeKeyAndVisible];
  }

String Resources

.strings文件例子:

/* Insert Element menu item */
"Insert Element" = "Insert Element";
/* Error string used for unknown error types. */
"ErrorString_1" = "An unknown error occurred.";

该文件一般放在对应语言/地区的.lproj目录下,UTF-16。

localized特性依赖于Bundle实现。

Creating Strings Resource Files

可以手动创建,也可以使用genstrings工具。

Choosing Which Strings to Localize

这些string需要localized:

  • 通过代码的方式添加到window, panel, view, or control然后显示给用户看的,包括提示窗。
  • Menu item title。
  • Error Message
  • 模板文件
  • 来源于Info.plist的strings
  • 用户可见的新文件/文档的名字

About the String-Loading Macros

  • Core Foundation macros:
    • CFCopyLocalizedString
    • CFCopyLocalizedStringFromTable
    • CFCopyLocalizedStringFromTableInBundle
    • CFCopyLocalizedStringWithDefaultValue
  • Foundation macros:
    • NSLocalizedString
    • NSLocalizedStringFromTable
    • NSLocalizedStringFromTableInBundle
    • NSLocalizedStringWithDefaultValue

以上的方法会根据当前用户的区域语言设置读取尽可能匹配的本地化语言文件。

Using the genstrings Tool to Create Strings Files

如果你使用了Core Foundation /Foundation macros,可以使用genstrings来创建strings文件。

genstrings支持.c .m 和 .java。

参数:

  • source files 列表
  • 输出路径(可选)
genstrings -o en.lproj *.m

如果key在一个source file中重复出现,genstrings会输出warning,可以使用- q参数忽略。如果一个key在两个文件之间重复则不会产生warning。

关于genstrings详情可以查看genstrings man page。

Creating Strings Files Manually

编码UTF-16,格式如下

/* comment */
“key”=“value”;
/* Menu item to make the current document plain text */
"Make Plain Text" = "Make Plain Text";
/* Menu item to make the current document rich text */
"Make Rich Text" = "Make Rich Text";

Loading String Resources Into Your Code

macro其中一个作用是用来通过key-value查找对应的localized string,还有一个作用是给genstrings工具提供生成.strings文件的参数。

参数列表及对应解释:

Parameter Description
Key 用来查找对应localized string的key string。必须是ASCII编码。可也以是extended ASCII编码。
Table name 指定.strings文件名。如果不提供的话生成的key将被放入Localizable.strings文件。(在提供这个参数的时候不需要后面的.strings后缀)。如果table name结尾是.nocache(如Error-Names.nocache.strings),生成的strings文件内容将不会被bundle cache。
Default value 和key对应的默认value。如果不传入该参数,默认value将会和key一样。default value可以是extended ASCII编码。
Comment 就是注释罗。
Bundle 从bundle中读入.string

localized string读取顺序如下:

  1. 根据当前用户区域语言查找对应的 language_region.lproj 目录并读取里面对应的 *.string 文件然后根据key查找对应的value。
  2. 如果木有找到对应的language_region.lproj则找language.lproj。
  3. 如果还是没有就从默认甚至localized strings文件读取。

详见 《 Internationalization and Localization Guide》 和 《Bundle Programming Guide》。

Using the Core Foundation Framework

CFCopyLocalizedString(key, comment)
CFCopyLocalizedStringFromTable(key, tableName, comment)
CFCopyLocalizedStringFromTableInBundle(key, tableName, bundle, comment)
CFCopyLocalizedStringWithDefaultValue(key, tableName, bundle, value, comment)

也可以使用CFBundleCopyLocalizedString但是该函数不能被genstrings识别也不能传入comment。

Using the Foundation Framework

NSLocalizedString(key, comment)
NSLocalizedStringFromTable(key, tableName, comment)
NSLocalizedStringFromTableInBundle(key, tableName, bundle, comment)
NSLocalizedStringWithDefaultValue(key, tableName, bundle, value, comment)

也可以使用localizedStringForKey:value:table:但是该函数不能被genstrings识别也不能传入comment。

Examples of Getting Strings

strings文件:

/* A comment */
"Yes" = "Oui";

"The same text in English" = "Le même texte en anglais";

Foundation:

NSString* theString;
theString = NSLocalizedStringFromTable (@"Yes", @"Custom", @"A comment");

对于以上例子,genstrings工具将会生成 Custom.strings 文件。

Core Foundation:

CFStringRef theString;
theString = CFCopyLocalizedStringFromTable(CFSTR("Yes"), CFSTR("Custom"), "A comment");

Advance Strings File Tips

Searching for Custom Functions With genstrings

使用 -s 参数可以自定义macro:

如果生命custom macro为MyStringFunction,则可用macro为MyStringFunctionFromTable, MyStringFunctionFromTableInBundle, and MyStringFunctionWithDefaultValue。

Formatting String Resources

使用格式化字符串:

"Windows must have at least %d columns and %d rows." = "Les fenêtres doivent être composes au minimum de %d colonnes et %d lignes.";
"File %@ not found." = "Le fichier %@ n’existe pas.";

然后你就可以在代码中使用NSString的stringWithFormat: 或者是 CFStringCreateWithFormat 使用他们。

可以重新排序转义字符的位置,格式如%n$@(object):

/* Message in alert dialog when something fails */
"%@ Error! %@ failed!" = "%2$@ blah blah, %1$@ blah!";

Using Special Characters in String Resources

注意转义,也可以使用Unicode字符:

"File \"%@\" cannot be opened" = " ... ";
"Type \"OK\" when done" = " ... ";

使用Unicode字符要记得确认文件编码是UTF-16。

Debugging String Files

使用/usr/bin/plutil工具可以测试strings文件的正确性。

Image, Sound, and Video Resources

Xcode的Interface Builder中可以设置一些view的image、sound、video等素材。这些素材必须存放在bundle里面。系统会在加载nib文件的时候读入这些素材并cache起来。手动使用这些素材可以使用UIImage的imageNamed:方法。

Loading Image Resources

可以使用objective-c/Quartz读取。

Loading Images in Objective-C

OS X使用NSKit的NSImage object。
iOS下使用UIKit的UIImage object读入。

NSString* imageName = [[NSBundle mainBundle] pathForResource:@"image1"
ofType:@"png"];
NSImage* imageObj = [[NSImage alloc] initWithContentsOfFile:imageName];

详见 UIImage Class ReferenceCocoa Drawing Guide

Loading Images Using Quartz

iOS下可以使用Quartz的Data providers。

CGImageRef MyCreateJPEGImageRef (const char *imageName);
  {
      CGImageRef image;
      CGDataProviderRef provider;
      CFStringRef name;
      CFURLRef url;
      CFBundleRef mainBundle = CFBundleGetMainBundle();
      // Get the URL to the bundle resource.
      name = CFStringCreateWithCString (NULL, imageName, kCFStringEncodingUTF8);
      url = CFBundleCopyResourceURL(mainBundle, name, CFSTR("jpg"), NULL);
      CFRelease(name);
      // Create the data provider object
      provider = CGDataProviderCreateWithURL (url);
      CFRelease (url);
      // Create the image object from that provider.
      image = CGImageCreateWithJPEGDataProvider (provider, NULL, true, kCGRenderingIntentDefault);
      CGDataProviderRelease (provider);
      return (image);
  }

Specifying High-Resolution Images in iOS

也是bundle特性,不需要增加额外的代码。文件名后加上@2x就可以,包括icon和launch images都可以。

栗子:

MyImage.png
MyImage@2x.png
MyImage~iphone.png
MyImage@2x~iphone.png

Data Resource Files

见《Property List Programming Guide》。

标签:ios, object-c, file's-owner