作者Blueshiva (龙野南云)
看板MacDev
标题[心得] 根据不同的系统版本,采用不同的底层实作
时间Sat Jan 12 00:41:11 2013
网页版:
http://shivahuang.tumblr.com/post/40259924272
问题是这样的,在 OS X 10.8 之前,NSColor 没有办法直接转出
CGColor,所以大家都是自己写 category 补上这个功能。但是在 10.8
Apple 加入了这个 API,而且命名就跟大家习惯的一样,叫做 [aNSColor
CGColor]。所以我们可以开心的舍弃掉自己写的 category,采用系统的实
作 - 如果你不管 10.6 和 10.7 的使用者的话…
当然不行。
所以我们还是要保留这个 category,但是我们又希望如果系统有提供这功
能,就采用系统的,如果没有再用 category 里面的实作。这时候可以利用
Obj-C 的一个功能,在呼叫前先用 respondToSelector:(SEL)aSelector 检
查一下他有没有这个 method,所以你可以用像下面一样的做法:
if (aColor respondToSelector:@selector(CGColor)]) {
[aColor CGColor];
}
else {
[aColor CGColorFromCategory];
}
但是,这样在程式任何地方要呼叫的时候都要写成这样一串,太麻烦了,而
且容易忘记。而且如果是用旧版的 SDK 编译,Xcode 还会跳出警告,说没
有 CGColor 这个 method。要避免这个 warning 有两个方法,一个是呼叫
的方式改成 [aColor performSelector:@selector(CGColor)],但是直接呼
叫 performSelector: 其实不太好,因为这样 compiler 就完全不会检查有
没有错误…另一个方式是,在 category 里面检查,如果用的是 10.8 之前
的 SDK 编译,就宣告 CGColor 这个 method 让编译器检查。
感觉都很丑…
比较漂亮的做法是,利用 Obj-C 一个比较诡异的功能,叫做 method
swizzling。这个功能可以让你在 runtime 的时候,抽换某个类别的底层实
作。我们先看实际的 code 要怎麽做:
[ gist:
https://gist.github.com/4511790 ]
//
// NSColor+CGColor.m
//
//
http://stackoverflow.com/questions/11950173/conditional-categories
// -in-mountain-lion
//
#import <objc/runtime.h>
static CGColorRef _NSColor_CGColor_(Class self, SEL cmd) {
const NSInteger numberOfComponents = [(id)self numberOfComponents];
CGFloat components[numberOfComponents];
CGColorSpaceRef colorSpace = [[(id)self colorSpace] CGColorSpace];
[(id)self getComponents:(CGFloat *)&components];
return (CGColorRef)[(id)CGColorCreate(colorSpace, components) autorelease];
}
static NSColor* _NSColor_colorWithCGColor_(Class self, SEL cmd, CGColorRef
CGColor) {
if (CGColor == NULL) return nil;
return [NSColor colorWithCIColor:[CIColor colorWithCGColor:CGColor]];
}
__attribute__((constructor))
static void initialize_NSColor_CGColorAdditions() {
if (![[NSColor class] respondsToSelector:@selector(colorWithCGColor:)]) {
class_addMethod(objc_getMetaClass("NSColor"),
@selector(colorWithCGColor:),
(IMP)_NSColor_colorWithCGColor_,
"@@:@");
}
if (![[NSColor class] instancesRespondToSelector:@selector(CGColor)]) {
class_addMethod(objc_getClass("NSColor"),
@selector(CGColor),
(IMP)_NSColor_CGColor_,
"@@:");
}
}
其中,if (![[NSColor class] instancesRespondToSelector:@selector
(CGColor)]) 这行就是在检查 NSColor 有没有实作 CGColor 这个
method,如果没有,就用 class_addMethod(objc_getClass(“NSColor”),
@selector(CGColor), (IMP)_NSColor_CGColor_, ”@@:”);把我们自己的
实做加入 NSColor 中。
要加入的实作会放在一个 function 中,至少要接受两个参数 self 和 _
cmd,例如 static CGColorRef _NSColor_CGColor_(Class self, SEL cmd)
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char
*types) 这个 function 则接受四个参数:
1. cls:要加入 method 的 class。
2. name:要加入的 method 的名字。
3. imp:要加入的实作的 function。
4. types:一个用来代表这个 method 参数的字串,第一个是回传值,第二个
是 self,第三个是 cmd,因为第二第三个是固定的,所以字串的第
二第三个一定是 “@:”。以我们 code 中设定的 “@@:” 代表的
是,这个 method 会回传一个 object (id),接收的第一个参数也
是一个 object,第二个参数是一个 selector 的名称。
样就会在系统没有实作 CGColor 这个 method 的时候,把我们自己的实作
插入 NSColor 物件,让我们不管在整个程式的何处都可以放心的直接呼叫
[aColor CGColor]。
--
Luna quieres ser madre
y no encuentras querer
que te haga mujer
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 112.104.95.143
1F:推 Piceman:赞,学了超强的一招 01/12 00:55
2F:推 wfgh:不能先判断respondToSelector:@selector(CGColor) 01/12 01:21
3F:→ wfgh:再performSelector:@selector(CGColor)吗 01/12 01:22
4F:→ Blueshiva:先判断再呼叫会有几个情形要分别,SDK版本和runtime版本 01/12 01:33