objective-c有一个feature,可以给已有的类添加方法,而无需改变类名。传统的语言可能需要通过继承或者组合实现,但是obj-c只需要用这个feature就好,这就是category。
Category:
举个例子,NSString是一个常用的类,NSString是原生支持unicode,比如NSString* str = @”感谢国家”;
要获得string的length,在大部分语言中获得的是字节数(比如python),如果文字编码是utf-8,那么得到的是12(4*3)。但是
NSString是原生支持unicode,所以当使用str.length时,获得的长度是4。
有这一特性很好,但这里不是讨论的重点,假设我们需要给NSString增加一个获得字节长度的方法,假设方法名为:byteLengthWithEncoding,使用category可以给NSString类增加如下代码:
@interface NSString (StringLength)
- (NSUInteger) byteLengthWithEncoding:(NSStringEncoding)encoding;
@end
@implementation NSString (StringLength)
- (NSUInteger) byteLengthWithEncoding:(NSStringEncoding)encoding {
if
(self == nil) {
return
0;
}
const
char
* byte = [self cStringUsingEncoding:encoding];
return
strlen
(byte);
}
@end
|
开始表明我们要扩展的类是NSString,并且把这个方法归到(StringLength)的分组中。这个category组名称随便你命名。然后增加了一个方法名:byteLengthWithEncoding;
接下来在implementation中实现这个方法,我们使用了NSString原生的方法cStringUsingEncoding,获得char*的指针,然后使用c里面的函数strlen来获得字节数。
来测试一下:
int
main(
int
argc,
char
* argv[]){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSString* str = @
"感谢国家"
;
NSLog(@
"str's length is : %d"
, [str length]);
NSLog(@
"str's byte length is: %d"
, [str byteLengthWithEncoding:NSUTF8StringEncoding]);
[pool drain];
return
0;
}
|
编译
gcc -framework Foundation main.m -o test
得到运行结果:
2010
-08
-18
21
:24
:03.605
test[
271
:903
]
str's length is : 4
2010-08-18 21:24:03.608 test[271:903] str'
s byte
length is: 12
一切都如预期运行,可以感受到category的威力了吧~
category用于给一个类增加类方法如此好用,但是对于category有两点要注意的:
- 如果使用category给类增加的方法和原来类的方法同名,则原来的类方法被覆盖,且你将访问不到原来的方法。
- 使用category只能增加类方法,不能增加类变量(ivar)
对于第二点,如果我们要增加类方法,同时也要增加类变量,该怎么办?嗯,你可能想到了使用类继承。好,那就来写个类继承NSString,不过我们先不增加类变量,然后给这个类增加上面的那个类方法byteLengthWithEncoding,我们的实现大概是这样:
@interface NSStringWithByteLength: NSString {
}
- (NSUInteger) byteLengthWithEncoding:(NSStringEncoding)encoding;
@end
@implementation NSStringWithByteLength
- (NSUInteger) byteLengthWithEncoding:(NSStringEncoding)encoding {
if
(self == nil) {
return
0;
}
const
char
* byte = [self cStringUsingEncoding:encoding];
return
strlen
(byte);
}
@end
int
main(
int
argc,
char
* argv[]){
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSStringWithByteLength* str = (NSStringWithByteLength*)[NSString stringWithString:@
"感谢国家"
];
NSLog(@
"str's length is : %d"
, [str length]);
NSLog(@
"str's byte length is: %d"
, [str byteLengthWithEncoding:NSUTF8StringEncoding]);
[pool drain];
return
0;
}
|
代码有问题的是main函数的第二行,我们强行将返回NSString*的指针转成NSStringWithByteLength类指针,这样应该在调用
类方法byteLengthWithEncoding时出问题。urh,先不管它,gcc编译,输出,运行到输出[str
length]时,运行正常,输出4;下一行,调用byteLengthWithEncoding时果然crash了,看出错提示:
-[
NSCFString byteLengthWithEncoding:]
: unrecognized selector sent to instance 0x100001058
说的是NSCFString类没有byteLengthWithEncoding方法,wait a minute,哪里来的NSCFString这个类?
Class Cluster:
原因在于NSString是个class
cluster,一个类簇。什么是一个类簇?简单的来说,NSString是个“工厂类”,然后它在外层提供了很多方法接口,但是这些方法的实现是由具体
的内部类来实现的。当使用NSString生成一个对象时,初始化方法会判断哪个“自己内部的类”最适合生成这个对象,然后这个“工厂”就会生成这个具体
的类对象返回给你。这种又外层类提供统一抽象的接口,然后具体实现让隐藏的,具体的内部类来实现,在设计模式中称为“抽象工厂”模式。
这里有一篇老外写的,更详细的介绍objective-c class cluster的文章
。
回过头来看上面的代码,实际上在使用[NSString stringWithString:]的方法时,返回的就是NSCFString* 这个具体类的指针,当然这个类没有后面我们指定的类方法 byteLengthWithEncoding,自然调用时也就出错了。
其实即使不是返回NSCFString的指针,上面的代码也有问题,假设是返回NSString的指针,直接使用
(NSStringByteWithLength)去进行强制转换也有问题,毕竟NSStringByteWithLength是子类。这样一来,可能想
到正确的写法应该是将第main函数的第2行初始对象是换为:
NSString* str = [[NSStringWithByteLength alloc] initWithBytes:
"感谢国家"
length:12 encoding:NSUTF8StringEncoding];
|
这里我们换了一个初始化string的方法,用了NSString原有的initWithBytes的方法。因为
NSStringWithByteLength继承了NSString,即使NSString是个类簇,由于我们没有在
NSStringWithByteLength里重写alloc和init的方法,这么接下来的alloc和initWithBytes的方法调用应该都
是还是NSString里的方法吧,按我们之前关于类簇的分析,这里返回的可能仍然是NSCFString*的类型,然后我们将返回值赋给
NSString,由于NSString*是NSCFString的父类,这种赋值应该没问题。ok,接下来就make & run 。
又crash了~ 看报错的信息:
-[
NSStringWithByteLength initWithBytes:length:encoding:]
: unrecognized selector sent to instance 0x10010c980
好像是说NSStringWithByteLength没有initWithBytes的方法调用。没错,我们是没有在
NSStringWithByteLength中定义这个方法,但是按我们之前的期望,第一步[NSStringWithByteLength
alloc]
这里应该调用的是NSString的alloc,然后返回一个对象后再次调用NSString的initWithBytes方法,看起来和直接使用
[[NSString alloc] initWithBytes: length: encoding]
没什么区别啊,为什么这里就说没有initWithBytes这方法了呢?为什么[[NSString alloc]
initWithBytes:length:encoding] 调用时没有问题,而用我们自己的
NSStringWithByteLength的派生类调用就出了问题呢?
其实又是NSString这个类簇在底下搞鬼,把[[NSString alloc] initWithBytes:length:encoding]拆开看,相当于:
id someClass = [NSString alloc];
[someClass initWithBytes:length:encoding];
|
先看第一行,我们把这someClass的class打出来看看:[someClass
className],又出来个新玩意:NSPlaceholderString。先不管它,不过这里我们至少知道了,这里alloc返回的一个
NSPlaceholderString类型的指针。
然后我们再看,把第一行改为:
id someClass = [NSStringWithByteLength alloc]
|
按照我们之前的分析,由于这里仍然调用的是NSString的alloc方法,那么返回的someClass的className应该仍然是
NSPlaceholderString才对,但是把这个打印出来,返回的居然是:NSStringWithByteLength,没错,还是我们自己的
类的指针。
这是怎么回事?如果是在alloc这一步已经返回不同的类型指针的话,那么刚才的报错提示没有
initWithBytes:length:encoding的方法的提示就不难理解了,因为NSPlaceholderString这个类里定义了这个
方法,而我们自己的NSStringWithByteLength的类没有这个方法。但是同样调用的是NSString的alloc,为啥两次返回不同
呢?
Under the hood
接下来就是见证奇迹的时刻,NSString
alloc时有个中间层,就是我们上面看到的NSPlaceholderString,alloc的对象先统一为这个类对象之后,在后面调用
NSPlaceholderString的类方法时,比如initWithBytes:length:encoding
才返回具体的类,即在NSPlaceholderString这一层做个“代理工厂”,根据调用的不同init方法再返回具体的类,比如
NSCFString。
那么为什么我们自己的类调用alloc时,就不返回NSPlaceholderString这个类对象了呢?关键就在于NSString
alloc方法的实现。NSString的alloc方法实现类似这样(这里只写简单的逻辑,Cocoa实际的代码实现未必和这个相同,不过逻辑应该是类
似的):
@
class
NSPlaceholderString;
@interface NSString:(NSObject)
+ (id) alloc;
@ end
@implementation NSString
+(id) alloc {
if
([self isEquals:[NSString
class
]]) {
return
[NSPlaceholderString alloc];
}
else
return
[super alloc];
}
@end
@interface NSPlaceholderString:(NSString)
@end
|
关键就在于alloc的实现,可以发现,当只用NSString调用alloc的时候,由于self == [NSString
class],所以这时返回的是NSPlaceholderString的类对象;而使用其他类(比如派生类)调用alloc时,返回的是super的
alloc,这里也就是[NSObject
alloc],而NSObject的alloc方法返回的是调用类的类对象,所以在我们用我们自己的NSStringWithByteLength类调用
时,返回的就是NSStringWithByteLength类的类对象了。
综述
只扩展类方法的时候,Category已经足够好用了,而上面也解释了Class Cluster和NSString
alloc的“怪异”实现。可见继承一个“class
cluster”类型的类是多么不容易,如果不熟悉,可能处处是陷阱。所以在有的书上就提出这样的建议:最好不要继承NSString这样的“类簇”类,
同样的还有NSArray,NSDictionary,NSNumber等等。在apple的文档中也提到,建议使用“组合”或者“catogery”来
实现这种扩展,如果你没有非要继承这种“类簇”类的理由的话。
转自: http://web2.0coder.com
分享到:
相关推荐
objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg-arm64.s源文件,objc-msg...
结合objc_class的数据结构,分析了objc_msgSend的具体执行流程
只用来下载WSDL2Objc第三方开发代码包的,用于iOS的websevrice
赠送jar包:j2objc-annotations-1.1.jar; 赠送原API文档:j2objc-annotations-1.1-javadoc.jar; 赠送源代码:j2objc-annotations-1.1-sources.jar; 赠送Maven依赖信息文件:j2objc-annotations-1.1.pom; 包含...
通过 objc_setAssociatedObject (关联) 的形式实现为Category (类别) 添加属性 如有不对敬请斧正
iOS-关联(objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects) 详解请参考:http://blog.csdn.net/u014220518/article/details/71750875
objc 本地读取网页并使用正则表达式处理
The type com.google.j2objc.annotations.ReflectionSupport$Level cannot be resolved. It is indirectly referenced from required .class files
Objc中国 APP 架构 完整版 高清
高清正版 objc中国 CoreData
PinYin4Objc 是一个流行的汉字(支持简体和繁体)转拼音的objc库,有以下特性: 1.效率高,使用数据缓存,第一次初始化以后,拼音数据存入文件缓存和内存缓存,后面转换效率大大提高; 2.支持自定义格式化,拼音大小...
Core Data objc Core Data objc Core Data objc Core Data objc Core Data objc
objc 中国 最新版本的 app架构 ,https://objccn.io/products/
如果想真正了解ios底层原理,除了阅读源码,没有捷径可走,但是没有一个可编译调试的环境,进阶无从谈起,本资源包为想进阶ios底层的小伙伴提供了可编译版本,编译的版本为苹果开源版本objc4-objc4-841.13,M1的电脑...
Objc 最新 App 架构 ePub版本,分享给大家。
objc语言源代码。阅读时可以先浏览一下所有的runtime API,就可对objc语言的实现原理有大致的了解,后续阅读才会更清晰。
objc.io app architechture 中文版 APP 架构 iOS objc.io app architechture 中文版 APP 架构 iOS
最新 objc APP 架构 pdf,
学习 iOS runtime 的源码,class protocol category method sel 等的内部实现。