category 是Objective-C 里面最常用到的功能之一。category 可以为已经存在的类增加方法,而不需要增加一个子类。而且,我们可以在不知道某个类内部实现的情况下,为该类增加方法。如果我们想增加某个框架(framework)中的类的方法,category 就非常有效。比如,如果想在NSString 上增加一个方法来判断它是否是有效的 URL,那么就可以这样做:
@interface NSString (extension)
- (BOOL) isURL;
@end
是不是觉得与类的定义非常像,确实,就是category 没有父类,而且后面要跟括号里面写category 的名字,名字可以随便取。下面是刚刚 isURL 的实现:
@implementation NSString(extension)
- (BOOL) isURL{
if( [self hasPrefix:@"http://"] )
return YES;
else
return NO;
}
@end
现在就可以在任何NSString类对象上调用这个方法了。下面是一个调用的例子:
NSString* str1 = @"http://www.blog.csdn.net/iukey";
NSString* str2 = @"刘伟Lewis";
if([str1 isURL])
NSLog(@"str1 is a URL");
if([str2 isURL])
NSLog(@"str2 is a URL");
通过上面的例子可以看出,通过类别所添加的新方法就成为类的一部分。我们通过为类别所添加的方法也存在于他的方法列表中,而为NSstring 子类添加的新方法,NSString是不具有的。通过类别所添加的新方法可以向这个类的其他方法一样完成任何操作。在运行时,新添加的方法和已经存在的方法在使用上没有任何区别。通过类别添加的方法和别的方法一样会被他的子类所继承。
类别接口的的定义看起来很像类接口的定义,而不同的是类别名用圆括号列出,他们位于类名后面。类别必须导入他所扩展的类的接口文件。标准语发格式如下:
#import "类名.h"
@interface 类名(类别名)
//新方法的声明
@end
和类一样类别的实现文件也要导入它的接口文件。一个常用的命名约定是,类别的基本文件名是这个类别扩展的类的名字后面跟类别名。因此一个名字为 “类名”+“类别名”+“.m”的实现文件看起来就是这样:
#import "类名类别名.h"
@interface 类名(类别名)
//新的实现方法
@end
注意:类别并不能为类声明新的实例变量,他只包含方法。然而在类作用域内所有实例变量,都能被这些类别访问。他们包括为类声明的所有的实例变量,甚至那些被@private 修饰的变量。可以为一个类添加多个类别,但每个类别名必须不同,而且每个类别都必须声明并实现一套不同的方法。
要记住,我们通过 category 来修改一个类的时候,他对应应用程序里这个类所有对象都起作用。跟子类不一样,category 不能增加成员变量。我们还可以用 category里重写类原先存在的方法(但是并不推荐这么做)。最后给出一个完整在例子(这个例子是扩展UIImage类 为其添加一个把图像变为灰度图像的方法):
// GrayScale.h
// XOGameFrame
//
// Created by song on 11-1-12.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface UIImage (grayscale)
- (UIImage *)convertToGrayscale ;
@end
//
// GrayScale.m
// XOGameFrame
//
// Created by song on 11-1-12.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "GrayScale.h"
@implementation UIImage (grayscale)
typedef enum {
ALPHA = 0,
BLUE = 1,
GREEN = 2,
RED = 3
} PIXELS;
- (UIImage *)convertToGrayscale {
CGSize size = [self size];
int width = size.width;
int height = size.height;
// the pixels will be painted to this array
uint32_t *pixels = (uint32_t *) malloc(width * height * sizeof(uint32_t));
// clear the pixels so any transparency is preserved
memset(pixels, 0, width * height * sizeof(uint32_t));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create a context with RGBA pixels
CGContextRef context = CGBitmapContextCreate(pixels, width, height, 8, width * sizeof(uint32_t), colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);
// paint the bitmap to our context which will fill in the pixels array
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [self CGImage]);
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
uint8_t *rgbaPixel = (uint8_t *) &pixels[y * width + x];
// convert to grayscale using recommended method: http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];
// set the pixels to gray
rgbaPixel[RED] = gray;
rgbaPixel[GREEN] = gray;
rgbaPixel[BLUE] = gray;
}
}
// create a new CGImageRef from our context with the modified pixels
CGImageRef image = CGBitmapContextCreateImage(context);
// we're done with the context, color space, and pixels
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
free(pixels);
// make a new UIImage to return
UIImage *resultUIImage = [UIImage imageWithCGImage:image];
// we're done with image now too
CGImageRelease(image);
return resultUIImage;
}
@end