今天学iOS开发(二)——实现一款App之编写自定义类
当开发iOS应用程序时,你会发现在许多场景下,你需要编写自己的自定义类。当你需要数据和自定义行为一起打包时,自定义类就很有用了。在一个自定义的类中,你可以为存储、操纵和显示数据定义你自己的行为。
例如,考虑iOS Clock app中的全球时钟标签(World Clock
tab)。这个表视图中的单元格需要显示比标准表视图单元格更多的内容。这是一个不错的机会实现一个扩展UITableViewCell行为的子类,从而在给定的表视图单元格中显示额外的自定义数据。如果你在设计这个自定义类,您可以为标签添加Outlets来显示时差信息,而单元格的右边用image
view显示自定义时钟。
本章将教你关于Objective-C语法和类结构的必要知识,从而实现你的ToDoList应用程序的行为。它讨论了XYZToDoItem的设计,这个自定义类代表to-do
list上的单个项目。在第三个教程中,你将真正地实现这个类,并把它添加到应用程序中。
类的声明与实现
规范的Objective-C类由两部分组成:接口(interface)与实现(implementation)。接口准确地指定了一个给定的对象类型如何被其他对象使用。换句话说,它定义了类实例和外界之间的公共接口。该实现包括接口中声明的每个方法的可执行代码。
一个对象的设计应该隐藏其内部的实现细节。在Objective-C中,接口和实现通常放置在单独的文件里,这样你需要做的就只是公共接口了。与C代码类似,定义头文件和源文件,从而把公开声明从代码的实现细节中分离出来。接口文件具有.h扩展名,实现文件具有.m的扩展名。(在Tutorial:
Add Data中,你要实际为XYZToDoItem类创建这些文件。现在,只要跟着介绍的步骤来就行了。)
接口(Interface)
用于声明一个类接口的Objective-C语法如下:
- @interface XYZToDoItem : NSObject
- @end
这个例子声明了一个名为XYZToDoItem类,它继承自NSObject。
公共属性和行为在@interface声明中定义。在这个例子里,没有任何指定超出父类,所以可以想到XYZToDoItem实例唯一可用的行为是从NSObject继承而来。所有对象有望有一个最低的行为,因此默认情况下,他们必须继承自NSObject(或它的子类)。
实现(Implemenation)
用于声明一个类实现的Objective-C语法如下:
- #import "XYZToDoItem.h"
- @implementation XYZToDoItem
- @end
如果在类接口中声明任何方法,你需要在这个文件中去实现它们。
用属性存储对象的数据
考虑一下待办事项需要持有什么样的信息。你可能需要知道它的名称,创建时间,以及是否已经完成。在你的自定义XYZToDoItem类中,你将会用properties来存储这些信息。
在所在的接口文件(XYZToDoItem.h)内声明这些属性。如下面所示:
- @interface XYZToDoItem : NSObject
- @property NSString *itemName;
- @property BOOL completed;
- @property NSDate *creationDate;
- @end
在这例子当中,XYZToDoItem类声明了三个公共属性。这些属性公众可以全面访问。有了公共访问权限,其他对象就可以读取和改变其属性的值。
你可能会决定声明一个不能被改变的属性(即它应该是只读的)。当指示一个属性打算设定为只读时,只需要Objective-C的属性声明中包含属性参数。举例来说,如果你不想要一个XYZToDoItem的创建的日期是不可变的,你只需更新XYZToDoItem类接口看起来像这样:
- @interface XYZToDoItem : NSObject
- @property NSString *itemName;
- @property BOOL completed;
- @property (readonly) NSDate *creationDate;
- @end
属性可以公有或私有。有时私有属性还是有意义的,这样其他类就不能查看或访问到它。举个例子,如果你想跟踪一个代表项目被标记为完成的日期的属性,而不给其他类访问这些信息的权限,只需在实现文件的顶部(XYZToDoItem.m)扩展你的私有类。
- #import "XYZToDoItem.h"
- @interface XYZToDoItem ()
- @property NSDate *completionDate;
- @end
- @implementation XYZToDoItem
- @end
您可以使用getter和setter访问属性。getter返回一个属性值,
setter改变一个属性值。一个常见的语法速记访问getter和setter方法是使用点符号。对于属性的读取和写入访问,您可以使用点符号获取和设置属性的值。如果你有类XYZToDoItem的一个对象TodoItem,您可以执行以下操作:
- toDoItem.itemName = @"Buy milk"; //Sets the value of itemName
- NSString *selectedItemName = toDoItem.itemName; //Gets the value of itemName
用方法定义对象的行为
方法定义了一个对象的行为。方法是用来定义在一个类中执行任务或子程序的一段代码。方法可以访问存储在类中的数据,并且可以使用该信息来执行某种操作。
例如,为让一个待办事项(XYZToDoItem)有能力被标记为已完成,你可以在类的接口中添加一个markAsCompleted方法。稍后,你将在类实现中实现此方法的行为,即Implementing
Methods中的描述。
@interface XYZToDoItem :
NSObject
- @interface XYZToDoItem : NSObject
- @property NSString *itemName;
- @property BOOL completed;
- @property (readonly) NSDate *creationDate;
- - (void)markAsCompleted;
- @end
在方法名的前面加上减号(-)表明它是一个实例方法,它可以被该类的一个对象调用。减号的实例方法是为了与用加号(+)表示的类的方法区分开来。类方法可以被类本身调用。类方法的一个常见例子是类的工厂方法,你可以在Working
with Foundation(中文)一节中了解它。您还可以使用类的方法来访问一些与类共享关联的信息片段。
用于在声明开头的括号里的void关键字,表明该方法没有返回值。在这个例子中,markAsCompleted方法不接受参数。你可以在“ Method
Parameters”中查看对参数的讨论。
方法参数
有参数的方法声明可以在你调用一个方法时传递一些必要信息。
举例来说,你可以从上面的代码片段修改markAsCompleted方法使之拥有一个单一的参数,它将决定该项目是否被标记为完成或者未完成。通过这种方式,你可以完成状态的项目,而不是将它设置为只完成了该项目的状态。
- @interface XYZToDoItem : NSObject
- @property NSString *itemName;
- @property BOOL completed;
- @property (readonly) NSDate *creationDate;
- - (void)markAsCompleted:(BOOL)isComplete;
- @end
现在,你声明的方法中有了一个BOOL类型的参数:isComplete。
当你引用一个方法参数的名字时,冒号将作为方法名称的一部分,所以更新之后的方法名称现为markAsCompleted:。如果一个方法有多个参数,方法名称被分解并穿插着参数名。如果你想另添加一个参数到这个方法,它的声明是这样的:
- - (void)markAsCompleted:(BOOL)isComplete onDate:(NSDate *)date;
这里,所述方法的名称就应该写为markAsCompleted:onDate:。implementation中使用的isComplete和date被用来访问调用方法时提供的值,这些名字仿佛是变量。
实现方法
方法实现使用大括号来包含相关的代码。该方法的名称必须与它在接口文件中相对应,参数和返回类型也必须完全匹配。下面是你添加到XYZToDoItem类接口中markAsCompleted:方法一个简单实现:
@implementation
XYZToDoItem
- @implementation XYZToDoItem
- - (void)markAsCompleted:(BOOL)isComplete {
- self.completed = isComplete;
- }
- @end
跟属性一样,方法也可以是公有或私有。公共方法在公共接口中声明,所以可以看到,并可以通过其他对象调用。其相应的实现只体现在实现文件中,对其他对象而言并不可见。私有方法只有一个实现,是类的内部方法,这意味着他们只在类实现的内部调用。这是一个向类添加内部行为的强有力的机制,而不允许其他对象访问它。
例如,假设你要保留一个待办事项的completionDate更新。如果待办事项被标记为已完成,设置completionDate为当前日期。如果它被标记为未完成,completionDate设置为nil,因为它尚未完成。因为更新待办项目的completionDate是一个自包含的任务,最佳实践是编写自己的方法。然而,值得注意的是你要确保其他对象不能调用该方法,否则,其他对象可以在任何时间把to-do项目的completionDate设置为任何值。出于这个考虑,你可以把函数设为私有。
现在,更新XYZToDoItem的实现,以包含在 markAsCompleted:内调用的私有方法setCompletionDate
,来更新to-do项目的completionDate,无论何时当它被标记为完成或者未完成。注意,你没有在口文件中添加任何代码,因为你不想其他对象看到这个方法。
- @implementation XYZToDoItem
- - (void)markAsCompleted:(BOOL)isComplete {
- self.completed = isComplete;
- [self setCompletionDate];
- }
- - (void)setCompletionDate {
- if (self.completed) {
- self.completionDate = [NSDate date];
- } else {
- self.completionDate = nil;
- }
- }
- @end
此时,你已经使用了XYZToDoItem类定义了to-do列表项的基本表示。XYZToDoItem以属性的形式存储了与自身有关的信息,比如名称,创建日期,完成状态等,并且它定义了它能做什么—标记为已完成或未完成—使用一个方法。在接下来的教程中,你需要在你的ToDoList
app中实现这些特性。但是,您可以尝试为类添加自己的属性和方法,从而把新的行为整合到你的app中