版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.05.04 |
前言
iOS中的视图加载可以有两种方式,一种是通过xib加载,另外一种就是通过纯代码加载。它们各有优点和好处,xib比较直观简单,代码比较灵活但是看着很多很乱,上一家公司主要风格就是用纯代码,这一家用的就是xib用的比较多。这几篇我们就详细的介绍一个xib相关知识。感兴趣的可以看上面写的几篇。
1. xib相关(一) —— 基本知识(一)
2. xib相关(二) —— 文件冲突问题(一)
3. xib相关(三) —— xib右侧标签介绍(一)
4. xib相关(四) —— 连线问题(一)
5. xib相关(五) —— 利用layout进行约束之界面(一)
6. xib相关(六) —— 利用layout进行约束之说明和注意事项(二)
7. xib相关(七) —— Storyboard中的segue (一)
8. xib相关(八) —— Size Classes(一)
9. xib相关(九) —— 几个IB修饰符(一)
10. xib相关(十) —— xib的国际化(一)
11. xib相关(十一) —— xib的高冷用法之修改视图的圆角半径、边框宽度和颜色(一)
12. xib相关(十二) —— UIStackView之基本介绍(一)
13. xib相关(十三) —— UIStackView之枚举UIStackViewDistribution使用(二)
14. xib相关(十四) —— UIStackView之UIStackViewAlignment使用(三)
15. xib相关(十五) —— UIStackView之工程实践(四)
16. xib相关(十六) —— UINib之基本介绍(一)
17. xib相关(十七) —— UINib之Introduction(二)
18. xib相关(十八) —— UINib之Nib文件(三)
回顾
上一篇主要讲述了UINib之Nib文件,这一篇继续讲述Nib文件。
Managing the Lifetimes of Objects from Nib Files - 管理从Nib文件加载的对象的生命周期
每次您要求NSBundle或NSNib类加载nib文件时,底层代码都会在该文件中创建对象的新副本并将它们返回给您。 (nib加载代码不会从之前的加载尝试中回收nib文件对象)。您需要确保您只需要维护新的对象图,并在完成时将其忽略。 您通常需要对顶级对象的强引用以确保它们不会被释放; 您不需要强烈参考图表中较低的对象,因为它们是由父类拥有的,您应该尽量减少创建强引用的风险。
从实际角度来看,iOS和OS X应该将outlet定义为已声明的属性。 除了从文件所有者到应用程序强大的nib文件中的顶级对象(或者在iOS中,故事板场景),outlet通常应该用weak。 因此,您创建的outlet通常应该weak,因为:
例如,您为视图控制器的视图或窗口控制器的窗口的子视图创建的outlet是在不隐式表示所有权的对象之间的任意引用。
强大的outlet通常由框架类指定(例如,UIViewController的视图outlet或NSWindowController的窗口outlet)。
@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;
注意:在OS X中,并非所有类都支持弱引用 - 请参阅Transitioning to ARC Release Notes。 如果你不能指定weak,你应该使用assign:
@property (assign) IBOutlet NSTextView *textView;
Outlets一般被认为是定义类的private,除非有公开property的理由,否则将属性声明隐藏为类扩展。 例如:
// MyClass.h
@interface MyClass : MySuperclass
@end
// MyClass.m
@interface MyClass ()
@property (weak) IBOutlet MyView *viewContainerSubview;
@property (strong) IBOutlet MyOtherClass *topLevelObject;
@end
这些模式扩展到从容器视图到其子视图的引用,您必须考虑对象图的内部一致性。 例如,对于table view cell,特定子视图的outlet通常应该为weak。 如果table view包含图像视图和文本视图,那么只要它们是table view cell的子视图,它们就保持有效。
Outlet应被视为拥有参考对象时,outlet应更改为strong:
正如前面所指出的,这个是File's Owner 的情况- 通常认为nib文件中的顶级对象由文件的所有者拥有。
您可能在某些情况下需要来自nib文件的对象存在于其原始容器之外。 例如,您可能有一个可从临时视图层次暂时移除的outlet视图,因此必须单独维护。
您希望被子类化的类(特别是抽象类)公开地公开这些子类(例如UIViewController的view outlet)。 如果期望该类的使用者需要与该属性互动,那么outlet也可能会暴露出来; 例如一个table view cell可能会暴露子视图。 在后一种情况下,可能适宜将公开只读的outlet重新定义为一个读写,例如:
// MyClass.h
@interface MyClass : UITableViewCell
@property (weak, readonly) MyType *outletName;
@end
// MyClass.m
@interface MyClass ()
@property (weak, readwrite) IBOutlet MyType *outletName;
@end
1. Top-level Objects in OS X May Need Special Handling - OSX中的顶层对象需要特殊处理
由于历史原因,在OS X中,nib文件中的顶级对象是使用额外引用计数创建的。 Application Kit提供了一些有助于确保nib对象正确释放的功能:
NSWindow
对象(包括面板)具有isReleasedWhenClosed
属性,该属性如果设置为YES,则指示窗口在关闭时自行释放(并因此在视图层次结构中释放所有依赖对象)。在nib文件中,您可以通过Xcode检查器的“属性”窗格中的Release when closed
复选框来设置此选项。如果nib文件的
File’s Owner
是NSWindowController
对象(基于文档的应用程序中的文档nib中的默认值 - 回想起NSDocument
管理NSWindowController
的实例)或NSViewController
对象,它会自动处理其管理的窗口。
如果File’s Owner
不是NSWindowController
或NSViewController
的实例,那么您需要自己减少顶级对象的引用计数。您必须将对顶级对象的引用转换为Core Foundation
类型并使用CFRelease
。 (如果您不想让所有顶层对象具有outlets ,则可以使用NSNib
类的instantiateNibWithOwner:topLevelObjects:方法来获取一个包含nib文件顶级对象的数组。
Action Methods - Action方法
一般来说,action方法(请参阅OS X中的Target-Action或iOS中的Target-Action)是通常由nib文件中的另一个对象调用的方法。 Action方法使用类型限定符IBAction(代替void返回类型)将所声明的方法标记为一个操作,以便Xcode知道它。
@interface MyClass
- (IBAction)myActionMethod:(id)sender;
@end
您可以选择将action方法视为你的类所私有,因此不会在public @interface中声明它们。 (因为Xcode解析实现文件,所以不需要在头文件中声明它们。)
/ 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 - Nib文件的内在支持
AppKit和UIKit框架都提供了一定数量的自动化行为来加载和管理应用程序中的nib文件。 这两个框架都提供了加载应用程序的主要nib文件的基础结构。 另外,AppKit框架还支持通过NSDocument和NSWindowController类加载其他的nib文件。 以下各节介绍了对nib文件的内置支持,如何利用它以及如何在自己的应用程序中修改该支持。
1. The Application Loads the Main Nib File - 程序加载主Nib文件
大多数用于应用程序的Xcode项目模板都预先配置了一个主要的nib文件。 你所要做的就是在nib文件中修改这个默认的nib文件并构建你的应用程序。 在启动时,应用程序的默认配置数据告诉应用程序对象在哪里找到这个nib文件,以便它可以加载它。 在基于AppKit和UIKit的应用程序中,此配置数据位于应用程序的Info.plist文件中。 首次加载应用程序时,默认的应用程序启动代码将在Info.plist文件中查找NSMainNibFile
项。 如果发现它,它会在应用程序包中查找一个nib文件,该文件的名称(带或不带文件扩展名)与该键的值匹配并加载它。
2. Each View Controller Manages its Own Nib File - 每一个View Controller管理自己的Nib文件
UIViewController(iOS)
和NSViewController(OS X)
类支持自动加载关联的nib文件。 如果您在创建视图控制器时指定了一个nib文件,那么当您尝试访问视图控制器的视图时,该nib文件会自动加载。 视图控制器和nib文件对象之间的任何连接都会自动创建,并且在iOS中,UIViewController对象在视图最终加载并显示在屏幕上时也会收到其他通知。 为了更好地管理内存,UIViewController类还处理在内存不足情况下卸载其nib文件(如适用)。
有关如何使用UIViewController类以及如何配置它的更多信息,请参阅View Controller Programming Guide for iOS。
3. Document and Window Controllers Load Their Associated Nib File - 文档和窗口控制器管理它们关联的Nib文件
在AppKit框架中,NSDocument类使用默认的窗口控制器来加载包含文档窗口的nib文件。 NSDocument
的windowNibName
方法是一种方便的方法,您可以使用它来指定包含相应文档窗口的nib文件。在创建新文档时,文档对象将您指定的nib文件名传递给默认的窗口控制器对象,该对象将加载并管理nib文件的内容。如果您使用Xcode提供的标准模板,您唯一需要做的就是将文档窗口的内容添加到nib文件中。
NSWindowController
类还提供了加载nib文件的自动支持。如果以编程方式创建自定义窗口控制器,则可以选择使用NSWindow对象或nib文件的名称对它们进行初始化。如果选择后一个选项,则NSWindowController类会在客户端第一次尝试访问该窗口时自动加载指定的nib文件。之后,窗口控制器将窗口保持在内存中;即使窗口的Release when closed
属性设置,它也不会从nib文件重新加载它。
重要提示:使用
NSWindowController
或NSDocument
自动加载窗口时,正确配置nib文件非常重要。 这两个类都包含一个窗口,您必须连接到您希望他们管理的窗口。 如果您没有将此outlet连接到窗口对象,则会加载nib文件,但文档或窗口控制器不会显示该窗口。 有关Cocoa文档体系结构的更多信息,请参阅Document-Based App Programming Guide for Mac。
Loading Nib Files Programmatically - 以编程方式加载Nib文件
OS X和iOS都提供了将nib文件加载到应用程序的便利方法。 AppKit和UIKit框架都在NSBundle类中定义了支持加载nib文件的附加方法。 另外,AppKit框架还提供了NSNib类,它提供了与NSBundle类似的nib加载行为,但提供了一些在特定情况下可能有用的额外优点。
在规划应用程序时,请确保您打算手动加载的任何nib文件都以简化加载过程的方式进行配置。 为文件所有者选择合适的对象并保持较小的nib文件可以大大提高其易用性和内存效率。 有关配置nib文件的更多提示,请参阅Nib File Design Guidelines。
1. Loading Nib Files Using NSBundle - 使用NSBundle加载Nib文件
AppKit和UIKit框架定义了NSBundle类的其他方法(使用Objective-C类别)来支持加载nib文件资源。 与两种方法的语法一样,使用这些方法的语义在两个平台之间也是不同的。 在AppKit中,通常有更多的访问包的选项,所以相应有更多的方法来从这些包中加载nib文件。 在UIKit中,应用程序只能从其主包中加载nib文件,因此需要更少的选项。 两种平台上可用的方法如下:
-
AppKit
loadNibNamed:owner: class method
loadNibFile:externalNameTable:withZone: class method
loadNibFile:externalNameTable:withZone: instance method
-
UIKit
- loadNibNamed:owner:options: instance method
无论何时加载nib文件,都应该指定一个对象作为该nib文件的文件所有者。文件所有者的角色是一个重要的角色。它是运行代码和即将在内存中创建的新对象之间的主要接口。所有的nib加载方法提供了一种方法来指定文件的所有者,直接或作为选项字典中的参数。
AppKit和UIKit框架处理nib加载的方式之间的语义差异之一就是顶级nib对象返回到应用程序的方式。在AppKit框架中,您必须使用loadNibFile:externalNameTable:withZone:
方法之一显式请求它们。在UIKit中,loadNibNamed:owner:options:
方法直接返回这些对象的数组。在这两种情况下避免担心顶级对象的最简单方法是将它们存储在File's Owner对象的outlet中(请参阅 Managing the Lifetimes of Objects from Nib Files)。
Listing 1-1
显示了如何在基于AppKit的应用程序中使用NSBundle类加载nib文件的简单示例。只要loadNibNamed:owner:
方法返回,您就可以开始使用任何引用nib文件对象的outlet。换句话说,整个Nib加载过程发生在该单个调用的范围内。 AppKit框架中的nib加载方法返回一个布尔值,以指示加载操作是否成功。
Listing 1-1 Loading a nib file from the current bundle
- (BOOL)loadMyNibFile
{
// The myNib file must be in the bundle that defines self's class.
if (![NSBundle loadNibNamed:@"myNib" owner:self])
{
NSLog(@"Warning! Could not load myNib file.\n");
return NO;
}
return YES;
}
Listing 1-2
显示了如何在基于UIKit的应用程序中加载nib文件的示例。 在这种情况下,该方法检查返回的数组以查看nib对象是否已成功加载。 (每个nib文件应该至少有一个表示nib文件内容的顶级对象。)本示例显示了nib文件不包含文件所有者对象以外的占位符对象的简单情况。 有关如何指定其他占位符对象的示例,请参阅Replacing Proxy Objects at Load Time。
Listing 1-2 Loading a nib in an iPhone application
- (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;
}
注意:如果您正在为iOS开发Universal应用程序,则可以使用特定于设备的命名约定自动为基础设备加载正确的nib文件。 有关如何命名nib文件的更多信息,请参阅iOS Supports Device-Specific Resources。
2.Getting a Nib File’s Top-Level Objects - 获取Nib文件的顶级对象
获取nib文件顶级对象的最简单方法是在文件所有者对象中定义outlet以及用于访问这些对象的setter方法(或更好的属性)。 这种方法可确保顶级对象由你的对象引用,并且始终有对它们的引用。
Listing 1-3
显示了简化的Cocoa类的接口和实现,该类使用outlet保留nib文件唯一的顶级对象。 在这种情况下,nib文件中唯一的顶级对象是NSWindow对象。 由于Cocoa中的顶级对象的初始保留计数为1,因此会包含额外的释放消息。 这很好,因为在进行调用release时,该属性已被保留在窗口中。 您不希望在iPhone应用程序中以这种方式释放顶级对象
Listing 1-3 Using outlets to get the top-level objects
// Class interface.
@interface MyController : NSObject
- (void)loadMyWindow;
@end
// Private class extension.
@interface MyController ()
@property (strong) IBOutlet NSWindow *window;
@end
// Class implementation
@implementation MyController
- (void)loadMyWindow {
[NSBundle loadNibNamed:@"myNib" owner:self];
// The window starts off with a retain count of 1
// and is then retained by the property, so add an extra release.
NSWindow *window = self.window;
CFRelease(__bridge window);
}
@end
如果您不想使用outlet存储对您的nib文件顶级对象的引用,则必须在代码中手动检索这些对象。 获取顶级对象的技术因目标平台而异。 在OS X中,您必须明确询问对象,而在iOS中它们会自动返回给您。
Listing 1-4
显示了在OS X中获取nib文件的顶级对象的过程。该方法将一个可变数组放入nameTable
字典中,并将其与NSNibTopLevelObjects
关联。 nib加载代码查找此数组对象,如果存在,则将顶级对象放入其中。 由于每个对象在添加到数组之前以保留计数1开始,因此单单释放数组并不足以释放数组中的对象。 因此,此方法会向每个对象发送一条释放消息,以确保该数组是唯一持有对它们的引用的实体。
Listing 1-4 Getting the top-level objects from a nib file at runtime
- (NSArray*)loadMyNibFile
{
NSBundle* aBundle = [NSBundle mainBundle];
NSMutableArray* topLevelObjs = [NSMutableArray array];
NSDictionary* nameTable = [NSDictionary dictionaryWithObjectsAndKeys:
self, NSNibOwner,
topLevelObjs, NSNibTopLevelObjects,
nil];
if (![aBundle loadNibFile:@"myNib" externalNameTable:nameTable withZone:nil])
{
NSLog(@"Warning! Could not load myNib file.\n");
return nil;
}
// Release the objects so that they are just owned by the array.
[topLevelObjs makeObjectsPerformSelector:@selector(release)];
return topLevelObjs;
}
在iPhone应用程序中获取顶级对象要简单得多,如Listing 1-2所示。 在UIKit框架中,NSBundle
的loadNibNamed:owner:options:
方法自动返回一个包含顶级对象的数组。 另外,在返回数组时,调整对象的保留计数,以便不需要为每个对象发送额外的释放消息。 返回的数组是对象的唯一所有者。
3. Loading Nib Files Using UINib and NSNib - 使用UINib和NSNib加载Nib文件
UINib(iOS)和NSNib(OS X)类在您想要创建nib文件内容的多个副本的情况下提供更好的性能。正常的nib加载过程涉及从磁盘读取nib文件,然后实例化其包含的对象。但是,使用UINib和NSNib类时,只需从磁盘读取一次nib文件,并将内容存储在内存中。由于它们在内存中,创建连续的对象集花费的时间更少,因为它不需要访问磁盘。
使用UINib和NSNib类始终是一个两步过程。首先,创建类的一个实例,并用nib文件的位置信息初始化它。其次,您将实例化nib文件的内容以将对象加载到内存中。每次您实例化nib文件时,都会指定一个不同的文件所有者对象并接收一组新的顶级对象。
Listing 1-5
显示了一种在OS X中使用NSNib类加载nib文件内容的方法。instantiateNibWithOwner:topLevelObjects:方法返回给您的数组已自动释放。如果你打算在一段时间内使用该数组,你应该复制它。
Listing 1-5 Loading a nib file using 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;
}
4. Replacing Proxy Objects at Load Time - 在加载时替换代理对象
在iOS中,可以创建包含File’s Owner之外的占位符对象的nib文件。 代理对象表示创建在nib文件外的对象,但它们与nib文件的内容有一些连接。 代理通常用于在iPhone应用程序中支持导航控制器。 在使用导航控制器时,通常将文件所有者对象连接到某个通用对象,如应用程序代理。 代理对象因此代表已经加载到内存中的导航控制器对象层次结构的部分,因为它们是以编程方式创建的或从不同的nib文件加载的。
注意:OS X nib文件不支持自定义占位符对象(文件所有者除外)。
添加到nib文件的每个占位符对象都必须具有唯一的名称。要为对象指定名称,请在Xcode中选择对象并打开检查器窗口。检查器的“属性”窗格包含一个Name字段,您可以使用该字段指定占位符对象的名称。您指定的名称应该描述对象的行为或类型,但实际上它可以是任何您想要的。
当准备加载包含占位符对象的nib文件时,当您调用loadNibNamed:owner:options:方法时,您必须为任何代理指定替换对象。此方法的options参数接受附加信息的字典。您可以使用此字典来传递有关占位符对象的信息。字典必须包含UINibExternalObjects键,其值是包含每个占位符替换的名称和对象的另一个字典。
Listing 1-6
显示了手动加载应用程序的主要nib文件的applicationDidFinishLaunching:
方法的示例代码。由于应用程序的代理对象是由UIApplicationMain
函数创建的,因此此方法在主nib文件中使用占位符(名称为“AppDelegate”)来表示该对象。代理字典存储占位符对象信息和选项字典包装该字典。
Listing 1-6 Replacing placeholder objects in a nib file
- (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];
}
有关loadNibNamed:owner:options:
方法的选项字典的更多信息,请参阅NSBundle UIKit Additions Reference
。
5. Accessing the Contents of a Nib File - 访问Nib文件的内容
成功加载nib文件后,其内容即可供您立即使用。 如果您将文件所有者中的outlet配置为指向nib文件对象,则现在可以使用这些outlet。 如果您没有使用任何outlet插座配置文件所有者,则应确保以某种方式获取对顶级对象的引用,以便稍后可以释放它们。
由于在加载nib文件时,outlet会填充真实对象,因此随后可以像使用编程创建的其他对象那样使用outlet。 例如,如果您有一个指向窗口的outlet,您可以向该窗口发送一个makeKeyAndOrderFront:
消息以在用户的屏幕上显示该窗口。 当您完成在nib文件中使用对象时,必须像其他任何对象一样释放它们。
重要提示:您负责释放当您完成这些对象时加载的所有nib文件的顶层对象。 不这样做是许多应用程序内存泄漏的原因。 释放顶层对象后,最好通过将指向nib文件中的对象的outlet设置为nil来清除所有outlet。 您应该清除与所有nib文件对象关联的outlet,而不仅仅是顶层对象。
Connecting Menu Items Across Nib Files - 连接Nib文件中的菜单项
OS X应用程序菜单栏中的项目通常需要与许多不同的对象进行交互,包括应用程序的文档和窗口。问题是许多这些对象不能(或不应该)从主要的nib文件直接访问。主nib文件的文件所有者始终设置为NSApplication
类的实例。尽管您可能能够在主nib文件中实例化一些自定义对象,但这样做实际上并不必要。对于文档对象,直接连接到特定的文档对象是不可能的,因为文档对象的数量可以动态改变,甚至可以为零。
大多数菜单项将action消息发送到以下其中一项:
- 始终处理命令的固定对象
- 动态对象,如文档或窗口
消息传递固定对象是一个相对直接的过程,通常通过应用程序委托来处理。应用程序委托对象在运行应用程序时协助NSApplication对象,并且是在主要nib文件中属于的少数几个对象之一。如果菜单项引用了应用程序级别的命令,则可以直接在应用程序委托中实现该命令,或者仅让委托将消息转发到应用程序中其他位置的相应对象。
如果您有一个菜单项作用于最前面窗口的内容,则需要将该菜单项链接到First Responder占位符对象。如果与菜单项关联的action方法特定于您的某个对象(并且未由Cocoa定义),则必须在创建连接之前将该操作添加到First Responder。
创建连接后,您需要在自定义类中实现操作方法。该对象还应实现validateMenuItem:validateMenuItem:方法以在适当的时间启用菜单项。有关响应者链如何处理命令的更多信息,请参阅Cocoa Event Handling Guide。
后记
本篇主要讲述了UINib之Nib文件,感兴趣的给个赞或者关注~~~