2.6 类别(cIass category)

类别是一种强大的功能,它允许开发者不用子类化就可以为一个既有的类来添加方法。类别可以帮助将类的实现分散到不同文件或者多个不同的框架中。在读iOS许多控件类的源文件时可以看到很多使用类别的地方。

在Xcode中创建类别非常容易,下面的例子中将为NSString类通过类别添加一个检查E-mail地址是否合法的方法。为此,通过Xcode首先创建一个Command Line Tool类型的ClassCategory 项目,创建完以后打开新建文件向导,如图2-26所示,在创建新文件向导的左侧选择OS X下方的Cocoa,右侧的文件模板列表中就可以找到Objective-C category即类别模板。

如果是创建iOS应用,同样,也可以在图2-26所示的向导窗口左侧选择iOS下方的Cocoa Touch,右侧的文件模板列表中也可以找到同样的类别模板。

图2-26 创建类别

接下来单击“Next”按钮,在类别信息输入窗口中的Category文本框中输入类别的名字NSStringUtility,顾名思义,就是NSString类的工具方法。在Category on下拉列表框中选择NSString,表明是为NSString类创建类别,如图2-27所示。

图2-27 为NSString类创建类别

单击“Next”按钮选择保存路径,单击创建后可以在Xcode左侧的项目导航栏窗口中看到Xcode创建了两个类相关文件,类名里包含NSString及NSStringUtility用来表明这是为NSString类创建的一个类别文件,Xcode通过这种方式让类名有意义的同时,也方便进行类名的管理,如图2-28所示。

图2-28 项目导航栏窗口

打开NSString+NSStringUtility.h头文件,修改代码如下:

        1: #import <Foundation/Foundation.h>
        2:
        3: @interface NSString (NSStringUtility)
        4:
        5: -(BOOL)validateEmail;
        6:
        7: @end

头文件并不复杂,留意第3行的最后在NSString的后面添加了一对括号,括号中就是类别的名字,这是类别与一般类定义唯一不同的地方,Objective-C正是通过这种方式来识别类别的。接着打开NSString+NSStringUtility.m类别的实现文件,修改代码如下:

        1: #import "NSString+NSStringUtility.h"
        2:
        3: @implementation NSString (NSStringUtility)
        4:
        5: -(BOOL)validateEmail
        6: {
        7:   BOOL stricterFilter = YES;
        8:   NSString *stricterFilterString =
    @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}]";
        9:   NSString *laxString = @".+@.+\\.[A-Za-z]{2}[A-Za-z]*";
        10:  NSString *emailRegex = stricterFilter ? stricterFilterString : laxString;
        11:  NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",
    emailRegex];
        12:  return [emailTest evaluateWithObject:self];
        13: }
        14:
        15: @end

上面的类别代码主要从第5行开始添加了validateEmail方法的具体实现,其中使用了正则表达式及NSPredicate类,NSPredicate类通过正则表达式来验证字符串是否符合邮箱地址的格式。留意第12行调用了NSPredicate的方法evaluateWithObject:方法,该方法中将self也就是NSString字符串本身传入以做校验。不理解正则表达式或NSPredicate方法也没有关系,最重要的是理解我们为 NSString 类创建了类别,这个类别中包含一个方法validateEmail,该方法将用来验证NSString对象是否符合E-mail的格式。

接下来修改main.m文件,添加测试代码来测试validateEmail方法:

        1: #import <Foundation/Foundation.h>
        2: #import "NSString+NSStringUtility.h"
        3:
        4: int main(int argc, const char * argv[])
        5: {
        6:   @autoreleasepool
        7:   {
        8:        NSString *strEmail = @"test@host";
        9:        if([strEmail ValidateEmail])
        10:            NSLog(@"%@是一个合法的邮箱地址", strEmail);
        11:       else
        12:            NSLog(@"%@不是一个合法的邮箱地址", strEmail);
        13:  }
        14:  return 0;
        15:}

上面代码的第2行导入了我们定义的类别文件,第6行第1次保留了Xcode自动添加的@autoreleasepool,这个标记是自动释放池,意味着下面大括号以内的对象内存将自动释放。这是因为Xcode认为NSPredicate对象存在内存泄漏的风险,运行时将提示内存警告信息,加上@autoreleasepool标记可以避免内存警告信息。

代码的第8行定义了一个不合格格式的NSString对象strEmail,然后调用类别中定义的方法validateEmail来判断strEmail对象是否为合法的E-mail地址,运行结果如图2-29所示。

图2-29 非法邮箱地址错误日志

上面的例子中,如果将strEmail对象的值改为test@host.com后再运行,就会收到验证通过的提示信息,如图2-30所示。

图2-30 合法邮箱地址日志

类别的定义并不复杂,在后面iOS应用开发的代码中,将有机会利用到类别。