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运行时的核心之一就是objc_class,它代表了Objective-C中的类。objc_class定义了类的结构和方法,以及如何通过类找到其对应的实例方法。objc_class包含以下几个关键参数: - isa:这个参数是指向类自身或meta...
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_setAssociatedObject`为Category添加属性,以及相关知识点。 首先,我们需要了解Category的基本概念。Category是一种分类、划分已存在类的方法和协议的方式,它不涉及新的实例变量...
@objc class MyClass { // ... } ``` `@objc`也用于暴露Swift中的协议给Objective-C,使得Objective-C可以遵循这些协议。同时,协议中的要求(即方法)也需要被`@objc`修饰,这样Objective-C才能实现它们。 ```...
在Objective-C中,类别(Category)是一种强大的机制,可以为已有的类添加方法,但默认情况下,类别不能添加实例变量或属性。然而,通过objc/runtime,我们仍然可以实现这个功能。 首先,我们需要引入`objc/runtime...
此外,分类(Category)和协议(Protocol)在objc中的实现也离不开runtime的支持。分类允许向已有的类添加方法,而协议则提供了多继承的替代方案,它们的实现都涉及到runtime的动态加载和方法解析机制。 总而言之,...
同时,它可能利用Category来扩展已有类,或者通过Category添加方法来处理特定的IB对象。 5. **XML解析**: Interface Builder文件是XML格式的,因此nib2objc需要包含XML解析库,如libxml2,来解析这些文件。解析...
在博客中的实战示例`setAssociatedDemo`中,开发者可能会创建一个Category来扩展某个类的功能,比如`UIView+Extension`,在Category中使用`objc_setAssociatedObject`为`UIView`添加一个自定义的属性,如`tagValue`...
标题中的“WSDL2Objc下载工具”是一个专门为iOS开发者设计的实用程序,它的主要功能是自动生成Objective-C客户端代码,从而简化与Web服务的集成过程。这个工具可以解析WSDL文件,并生成对应的Objective-C类,使得...
源码中,`objc_class`结构体定义了类的内部结构,而`objc_object`表示对象的基本结构。 3. **动态加载与类别**:Objective-C支持运行时添加方法和属性,这涉及到动态加载机制。`objc_loadClass`和`objc_...
ObjC的核心是类(Class)和对象(Object)。类是一种抽象的数据类型,定义了对象的行为和属性。对象是类的实例,具有类定义的特性和功能。在ObjC中,通过`@interface`和`@implementation`来声明和实现类。 ```objc ...
赠送jar包:j2objc-annotations-1.3.jar; 赠送原API文档:j2objc-annotations-1.3-javadoc.jar; 赠送源代码:j2objc-annotations-1.3-sources.jar; 赠送Maven依赖信息文件:j2objc-annotations-1.3.pom; 包含...
赠送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; 包含...
nib2objc工具是一款针对iOS开发的实用小工具,它的主要功能是将.xib或.storyboard文件转换为Objective-C代码,方便开发者快速地将界面设计与代码实现相结合。在这个过程中,nib2objc能够帮助开发者节省大量的手动...
iOS-关联(objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects) 详解请参考:http://blog.csdn.net/u014220518/article/details/71750875
**J2OBJC简介** J2OBJC是一个由Google开发的开源工具,它的主要功能是将Java源代码转换成Objective-C代码,使得Java程序能够运行在iOS平台上。这个工具为Java开发者提供了一条途径,无需学习Objective-C或Swift,就...
- 如果没有,继续在接收者的元类(meta-class)中查找。 - 若仍无,沿着继承链向上搜索,直到找到方法或者到达根类(NSObject或其基类)。 - 如果找到了方法,就执行;若未找到,会触发“未定义的选择器”异常。 **...
Objc4--750 是一个与iOS开发相关的项目,主要关注的是Objective-C运行时(runtime)的源码分析。Objective-C runtime是苹果的Objective-C编程语言的核心部分,它负责对象的创建、方法的调用以及类的动态行为。在这个...
在iOS开发中,Objective-C(简称objc)是主要的编程语言之一。本篇文章将深入探讨如何在objc中本地读取网页内容以及利用正则表达式进行数据处理。这两个技术是移动应用开发中不可或缺的部分,特别是在涉及到网络数据...
The type com.google.j2objc.annotations.ReflectionSupport$Level cannot be resolved. It is indirectly referenced from required .class files