View controllers are an essential part of managing your app’s resources. View controllers allow you to break your app up into multiple parts and instantiate only the parts that are needed. But more than that, a view controller itself manages different resources and instantiates them at different times. For example, a view controller’s view hierarchy is instantiated only when the view is accessed; typically, this occurs only when the view is displayed on screen. If multiple view controllers are pushed onto a navigation stack at the same time, only the topmost view controller’s contents are visible, which means only its views are accessed. Similarly, if a view controller is not presented by a navigation controller, it does not need to instantiate its navigation item. By deferring most resource allocation until it is needed, view controllers use less resources.
视图控制器是管理应用程序资源的一个必不可少的部分。视图控制器让你把应用程序分成多个部分并只需要实例化需要的部分。但是不仅如此,一个视图控制器本身管理着不同的资源,并在不同时候实例化它们。比如,一个视图控制器的视图层次结构只在视图被访问时才被实例化;通常,这只在视图被显示到屏幕上时才发生。 如果多个视图控制器被同时推送到一个导航栈,只有最顶端的视图控制器的内容是可见的,就是说只有该视图控制器的视图是可以被访问的。 类似地,如果一个视图控制器不是由一个导航控制器呈现,它不需要实例化它的导航项。通过推迟大多数资源的分配时间直到它被需要,视图控制器使用更少的资源。
When memory available to the app runs low, all view controllers are automatically notified by the system. This allows the view controller to purge caches and other objects that can be easily recreated later when memory is more plentiful. The exact behavior varies depending on which version of iOS your app is running on, and this has implications for your view controller design.
当内存允许应用程序以低内存运行时,所有的视图控制器都会自动接收到系统的提醒。 这样视图控制器就可以清除那些当内存富余时能很容易被重新创建的缓存和其它对象。确切的行为会根据应用程序运行的iOS版本不同而不同,而这些对你的视图控制器设计都有影响(implications).
Carefully managing the resources associated with your view controllers is critical to making your app run efficiently. You should also prefer lazy allocation; objects that are expensive to create or maintain should be allocated later and only when needed. For this reason, your view controllers should separate objects needed throughout the lifetime of the view controller from objects that are only necessary some of the time. When your view controller receives a low-memory warning, it should be prepared to reduce its memory usage if it is not visible onscreen.
仔细管理跟视图控制器有关的各种资源是让应用程序有效运行的关键。 你应该还喜欢延迟分配(lazy allocation); 那些创建或者保持都昂贵的对象应该在以后并且只在需要时才被分配。 因此,你的视图控制器应该让那些整个视图控制器生存期(lifetime)都需要的对象和只在必要时才需要的对象相分离。 当你的视图控制器接收到一个低内存警告,它应该在它不可见时随时准备缩减它的内存使用。
When a view controller is first instantiated, it creates or loads objects it needs through its lifetime. It should not create its view hierarchy or objects associated with displaying content. It should focus on data objects and objects needed to implement its critical behaviors.
当一个视图控制器被第一次实例化时,它在生存期内创建或加载它需要的各种对象。 它不应该创建它的视图层次或跟显示内容相关的各种对象。 它应该聚焦于数据对象以及需要用来实现其关键行为的各种对象。
When you create a view controller in a storyboard, the attributes you configure in Interface Builder are serialized into an archive. Later, when the view controller is instantiated, this archive is loaded into memory and processed. The result is a set of objects whose attributes match those you set in Interface Builder. The archive is loaded by calling the view controller’s initWithCoder:
method. Then, the awakeFromNib
method is called on any object that implements that method. You use this method to perform any configuration steps that require other objects to already be instantiated.
当你在故事板中创建了一个视图控制器,你在界面生成器(Interface Builder)里设置各种属性被序列化到一个压缩包(archive). 稍候,当视图控制器被初始化后,该压缩包被加载到内存并运行。结果是一个对象集,其属性符合你在界面生成器里设置的属性。 压缩包通过调用视图控制器的initWithCoder: 方法来加载。 然后,在任何实现该方法的对象上调用awakeFromNib 方法。 你可以使用该方法来执行任何配置步骤,这些步骤要求其它对象已经被初始化。
For more on archiving and archiving, see Archives and Serializations Programming Guide.
更多关于压缩和解压缩内容,请看 Archives and Serializations Programming Guide.
If a view controller allocates its resources programmatically, create a custom initialization method that is specific to your view controller. This method should call the super class’s init
method and then perform any class specific initialization.
如果视图控制器通过程序分配其各种资源,创建一个专门用于该视图控制器的自定义初始化方法。该方法应该调用超类的init 方法,然后执行任何类的特定初始化。
In general, do not write complex initialization methods. Instead, implement a simple initialization method and then provide properties for clients of your view controller to configure its behaviors.
一般情况下,别编写复杂的初始化方法。相反,实现一个简单的初始化方法,然后给视图控制器的客户端(clients)提供各种特性(properties)来配置其行为。
Whenever some part of your app asks the view controller for its view object and that object is not currently in memory, the view controller loads the view hierarchy into memory and stores it in its view
property for future reference. The steps that occur during the load cycle are:
任何时候,当应用程序中一个部分向视图控制器请求它的视图对象,但那个对象并不在当前的内存中时,视图控制器把视图层次加载进内存并把它存入其view 特性以供将来引用。 加载过程中发生的步骤如下:
The view controller calls its loadView
method. The default implementation of the loadView
method does one of two things:视图控制器调用其loadView 方法。 loadView方法的默认实现完成以下事情之一:
The view controller calls its viewDidLoad
method, which enables your subclass to perform any additional load-time tasks.视图控制器器调用其viewDidLoad 方法,它让你的子类可以执行任何额外的加载期间任务。
Figure 4-1 shows a visual representation of the load cycle, including several of the methods that are called. Your app can override both the loadView
and the viewDidLoad
methods as needed to facilitate the behavior you want for your view controller. For example, if your app does not use storyboards but you want additional views to be added to the view hierarchy, you override the loadView
method to instantiate these views programatically.
图4-1 显示了加载过程的一个流程图,它包含了一些被调用的方法。 你的应用程序可以根据需要同时重写(override)loadView 和 viewDidLoad方法来实现(facilitate)你的视图控制器需要的各种行为。比如,如果应用程序没有使用故事板,但是你想要把额外的视图加入视图层次,你可以通过程序重写loadView方法来实例化这些视图。
Most view controllers load their view from an associated storyboard. The advantage of using storyboards is that they allow you to lay out and configure your views graphically, making it easier and faster to adjust your layout. You can iterate quickly through different versions of your user interface to end up with a polished and refined design.
大多数视图控制器从一个相关联的故事板载入视图。使用故事板的优势是可以可视的布局和配置视图,让布局变得更加简单和快捷。 你可以快速地遍历(iterate)用户界面的不同版本,以一个优雅精美的设计而结束。
Interface Builder is part of Xcode and provides an intuitive way to create and configure the views for your view controllers. Using Interface Builder, you assemble views and controls by manipulating them directly, dragging them into the workspace, positioning them, sizing them, and modifying their attributes using an inspector window. The results are then saved in a storyboard file, which stores the collection of objects you assembled along with information about all the customizations you made.
界面生成器是Xcode的一部分,它提供了一种直观的(intuitive)方法来创建和配置视图控制器的视图。使用界面生成器,你集合(assemble)所有视图,并可以直接操纵控制它们,拖拉它们进入工作区,定位它们,重新定义它们的尺寸以及使用一个属性(inspector)窗口来修改它们的属性。结果稍后被保存到一个故事板文件中,该文件存储了你集合的所有对象集,以及所有你做的自定义信息。
To help you layout the contents of your view properly, Interface Builder provides controls that let you specify whether the view has a navigation bar, a toolbar, or other objects that might affect the position of your custom content. If the controller is connected to container controllers in the storyboard, it can infer these settings from the container, making it easier to see exactly how it should appear at runtime.
为了让你能够正确地布局视图的内容,界面生成器提供了各种控件来让你指定视图是否有一个导航栏,工具栏或其它可能影响你自定义内容位置的各种对象。 如果控制器被链接到故事板中的容器控制器上,它可以从容器控制器推断(infer)这些设置,使其更容易看到它究竟应该如何在运行时出现。
Using Interface Builder, you create connections between the views in your interface and your view controller.
使用界面生成器,你可以在界面的视图和视图控制器之间创建各种链接。
Listing 4-1 shows the declaration of a custom MyViewController
class’s two custom outlets (designated by the IBOutlet
keyword) and a single action method (designated by the IBAction
return type). The declarations are made in a category inside the implementation file. The outlets store references to a button and a text field in the storyboard, while the action method responds to taps in the button.
列表4-1 显示了一个自定义MyViewController类的两个自定义输出口(用关键字IBOutlet表明
)的声明,以及一个单个动作方法(由返回类型IBAction表示)。该声明在一个实现文件中的category中定义。 插座变量把引用存入故事板中一个按钮和一个文本框中,动作方法响应按钮的点击。
@interface MyViewController() @property (nonatomic) IBOutlet id myButton; @property (nonatomic) IBOutlet id myTextField; - (IBAction)myAction:(id)sender; @end
Figure 4-2 shows the connections you would create among the objects in such a MyViewController
class. 图4-2 显示了你在MyViewController类中各个对象之间的你将要创建的各种链接。
When the previously configured MyViewController
class is created and presented, the view controller infrastructure automatically loads the views from the storyboard and reconfigures any outlets or actions. Thus, by the time the view is presented to the user, the outlets and actions of your view controller are set and ready to be used. This ability to bridge between your runtime code and your design-time resource files is one of the things that makes storyboards so powerful.
当先前配置的MyViewController类被创建和呈现后,视图控制器基础设施(infrastructure)自动从故事板加载视图并重新配置所有的插座变量和动作。 因此,当视图被呈现给用户时,视图控制器的插座变量和动作都已经被设置完成并可以使用。 在运行时代码和设计时(design-time)资源文件之间建立桥梁是让故事板如此强大的手段之一。
If you prefer to create views programmatically, instead of using a storyboard, you do so by overriding your view controller’s loadView
method. Your implementation of this method should do the following:
如果你更喜欢通过程序创建视图,而不使用故事板,你可以继承视图控制器的loadView 方法来实现。 实现该方法需要完成以下步骤:
Create a root view object. 创建一个根视图对象
The root view contains all other views associated with your view controller. You typically define the frame for this view to match the size of the app window, which itself should fill the screen. However, the frame is adjusted based on how your view controller is displayed. See Resizing the View Controller’s Views. 根视图包含了所有跟视图控制器相关的各种其它视图。你通常需要为该视图定义frame使其符合应用窗口的尺寸,应用程序窗口本身应该充满整个屏幕。 然而,调整frame应该基于视图控制器是如何被显示的。 参见
“Resizing the View Controller’s Views.”
You can use a generic UIView
object, a custom view you define, or any other view that can scale to fill the screen.你可以使用一个通用的 UIView 对象, 你定义的自定义视图,或任何其它视图都可以缩放到充满屏幕。
Create additional subviews and add them to the root view.创建各种额外的子视图并把它们添加到根视图。
For each view, you should: 每个视图,你都应该:
Create and initialize the view.创建并初始化视图。
Add the view to a parent view using the addSubview:
method. 使用addSubview: 方法来把它添加到父视图。
If you are using auto layout, assign sufficient constraints to each of the views you just created to control the position and size of your views. Otherwise, implement the viewWillLayoutSubviews
and viewDidLayoutSubviews
methods to adjust the frames of the subviews in the view hierarchy. See Resizing the View Controller’s Views. 如果你使用自动布局,给每个你刚创建的视图分配足够的限制(constaints)来控制视图的位置和尺寸。 或者,实现viewWillLayoutSubviews
和 viewDidLayoutSubviews 方法来调整子视图在视图层次中的frames. 参见“Resizing the View Controller’s Views.”
Assign the root view to the view
property of your view controller.把根视图绑定到视图控制器的view 属性。
Listing 4-2 shows an example implementation of the loadView
method. This method creates a pair of custom views in a view hierarchy and assigns them to the view controller.
列表4-2 显示了一个实现loadView方法的示例。 该方法在视图层次里创建了一对自定义视图,并把它们绑定(assign)到视图控制器上。
- (void)loadView { CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame]; UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame]; contentView.backgroundColor = [UIColor blackColor]; self.view = contentView; levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self]; [self.view addSubview:levelView]; }
Note: When overriding the loadView
method to create your views programmatically, you should not call super
. Doing so initiates the default view-loading behavior and usually just wastes CPU cycles. Your own implementation of the loadView
method should do all the work that is needed to create a root view and subviews for your view controller. For more information on the view loading process, see A View Controller Instantiates Its View Hierarchy When Its View is Accessed.
提示:当你继承loadView方法来创建视图时,你不应该调用super. 这样做会触发(initiates)默认视图加载行为,而这通常只会浪费CPU周期(cycles). 你定义的loadView方法应该实现所有的工作,包括为你的视图控制器创建一个根视图和各个子视图。 关于视图载入进程的更多信息,参见“A View Controller Instantiates Its View Hierarchy When Its View is Accessed.”
When it comes to view controllers and memory management, there are two issues to consider:当你遇到视图控制器和内存管理时,你需要考虑以下两点:
How to allocate memory efficiently 如何有效地分配内存
When and how to release memory 何时以及如何释放内存
Although some aspects of memory allocation are strictly yours to decide, the UIViewController
class provides some methods that usually have some connection to memory management tasks. Table 4-1 lists the places in your view controller object where you are likely to allocate or deallocate memory, along with information about what you should be doing in each place.
尽管内存分配的一些方面是需要你严格决定,但是UIViewController 类提供了一些方法,它们通常跟内存管理任务有一些关联。 表格4-1 列出了一些在视图控制器对象中,你可能需要分配和回收内存的地方,以及一些你在每个地方应该做什么的信息。
Task |
Methods |
Discussion |
---|---|---|
Allocating critical data structures required by your view controller 分配视图控制器需要的各个关键数据结构 |
Initialization methods 各种初始化方法 |
Your custom initialization method (whether it is named 自定义初始化方法(方法名叫init 或其它都可以)经常负责把视图控制器对象放入一个已知的好状态。 该状态包含分配任何需要确保正确操作的数据结构。 |
Creating your view objects 创建视图对象 |
Overriding the 继承loadView方法只在你打算通过程序创建你的视图时需要。 如果你使用故事板,视图将自动从故事板文件上加载。 |
|
Creating custom objects 创建自定义对象 |
Custom properties and methods |
Although you are free to use other designs, consider using a pattern similar the 尽管你可以自由的使用其它设计,但是得考虑使用一个跟loadView方法相似的模式。 创建一个保存对象的属性以及一个相应的方法来初始化它。 当属性被读取而它的值为nil时,调用相关的加载方法。 |
Allocating or loading data to be displayed in your view 分配或加载显示到视图上的数据 |
Data objects are typically provided by configuring your view controller’s properties. Any additional data objects your view controller wants to create should be done by overriding the 数据对象通常通过配置视图控制器的各种属性来提供。 视图控制器想要创建的任何额外的数据对象都应该通过继承viewDidLoad 方法来实现。 当该方法被调用时,你的视图对象都保证存在并在一个已知的良好状态。 |
|
Responding to low-memory notifications 响应低内存通知 |
Use this method to deallocate all noncritical objects associated with your view controller. On iOS 6, you can also use this method to release references to view objects. 使用该方法来回收跟你的视图控制器关联的不太关键的对象的内存。在iOS6中,你还可以使用该方法来释放对视图对象的各个引用。 |
|
Releasing critical data structures required by your view controller 释放视图控制器要求的关键数据结构 |
Override this method only to perform any last-minute cleanup of your view controller class. Objects stored in instance variables and properties are automatically released; you do not need to release them explicitly. 只在执行视图控制器类的最后一分钟清理时重写(override)该方法。存储在事例变量和属性中的对象都会自动被释放;你不需要明确地释放它们。 |
The default behavior for a view controller is to load its view hierarchy when the view
property is first accessed and thereafter keep it in memory until the view controller is disposed of. The memory used by a view to draw itself onscreen is potentially quite large. However, the system automatically releases these expensive resources when the view is not attached to a window. The remaining memory used by most views is small enough that it is not worth it for the system to automatically purge and recreate the view hierarchy.
视图控制器的默认行为是当其view 特性被第一次访问时,加载其视图层次,然后一直把它保存在内存直到视图控制器被销毁。 视图用来在屏幕上绘制自身所需的内存有可能会比较大。 但是系统会在视图不显示在窗口时自动释放这些昂贵的资源。 保留下来供大多数视图使用的内存已经足够小,而不值得系统自动清除它们并重新创建视图层次。
You can explicitly release the view hierarchy if that additional memory is necessary for your app. Listing 4-3 overrides the didReceiveMemoryWarning
method to accomplish this. First, is calls the superclass’s implementation to get any required default behavior. Then, it cleans up the view controller’s resources. Finally, it tests to see if the view controller’s view is not onscreen. If the view is associated with a window, then it cleans up any of the view controller’s strong references to the view and its subviews. If the views stored data that needs to be recreated, the implementation of this method should save that data before releasing any of the references to those views.
你可以明确地释放视图层次,如果那个额外内存对于应用程序很必要。 列表4-3 重写了didReceiveMemoryWarning 方法来完成(accomplish)它。 首先,是调用超类的实现来获取任何需要的默认行为。 然后,清除视图控制器的资源。最后,测试视图控制器的视图是否在屏幕上可见。 如果视图跟窗口相关联, 则清除任何跟视图及其子视图之间的强引用。 如果视图存储了需要被重新创建的数据, 该方法的实现应该在释放任何跟那些视图之间的引用之前保存数据。
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Add code to clean up any of your own resources that are no longer necessary. if ([self.view window] == nil) { // Add code to preserve data stored in the views that might be // needed later. // Add code to clean up other strong references to the view in // the view hierarchy. self.view = nil; }
The next time the view
property is accessed, the view is reloaded exactly as it was the first time.
当view 特性第二次被访问时,会重新加载第一次访问时的视图。
In earlier versions of iOS, the system automatically attempts to unload a view controller’s views when memory is low. The steps that occur during the unload cycle are as follows:
在iOS的早期版本,当内存不足时,系统自动尝试卸载视图控制器中的视图。卸载周期发生了以下步骤:
The app receives a low-memory warning from the system.应用程序从系统接收了一个低内存的警告。
Each view controller calls its didReceiveMemoryWarning
method. If you override this method, you should use it to release any memory or objects that your view controller object no longer needs. You must call super
at some point in your implementation to ensure that the default implementation runs. On iOS 5 and earlier, the default implementation attempts to release the view. On iOS 6 and later, the default implementation exits.每个视图控制器调用其 didReceiveMemoryWarning 方法。 如果你重写了该方法,你应该使用它来释放视图控制器不使用的任何内存或对象。你必须在实现文件中的一些关键点调用super来确保默认实现已经运行。 在iOS5 以及早先版本中,默认实现尝试释放视图。 在iOS6以及以后版本中,默认实现则保留这些视图。
If the view cannot be safely released (for example, it is visible onscreen), the default implementation exits.如果视图不能被安全地释放(比如,它在屏幕上可见), 默认实现保留该视图。
The view controller calls its viewWillUnload
method. A subclass typically overrides this method when it needs to save any view properties before the views are destroyed.视图控制器调用viewWillUnload 方法。 子类通常重写该方法, 当它需要在视图被销毁钱保留任何视图特性时。
The view controller calls its viewDidUnload
method. A subclass typically overrides this method to release any strong references it has to those views.视图控制器调用其 viewDidUnload。子类通常重写该方法来释放任何对那些视图的强引用。
Figure 4-3 shows a visual representation of the unload cycle for a view controller. 显示了一个视图控制器卸载周期的流程图。