`
bluepeer
  • 浏览: 74462 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

objc’s category and class cluster 详解

 
阅读更多

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有两点要注意的:

  1. 如果使用category给类增加的方法和原来类的方法同名,则原来的类方法被覆盖,且你将访问不到原来的方法。
  2. 使用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运行时的核心之一就是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-arm64.s源文件,objc-msg...

    通过 objc_setAssociatedObject (关联) 的形式实现为Category (类别) 添加属性

    下面我们将深入探讨如何通过`objc_setAssociatedObject`为Category添加属性,以及相关知识点。 首先,我们需要了解Category的基本概念。Category是一种分类、划分已存在类的方法和协议的方式,它不涉及新的实例变量...

    @objc小结

    @objc class MyClass { // ... } ``` `@objc`也用于暴露Swift中的协议给Objective-C,使得Objective-C可以遵循这些协议。同时,协议中的要求(即方法)也需要被`@objc`修饰,这样Objective-C才能实现它们。 ```...

    用objc/runtime给类别扩展属性代码

    在Objective-C中,类别(Category)是一种强大的机制,可以为已有的类添加方法,但默认情况下,类别不能添加实例变量或属性。然而,通过objc/runtime,我们仍然可以实现这个功能。 首先,我们需要引入`objc/runtime...

    objc4源代码

    此外,分类(Category)和协议(Protocol)在objc中的实现也离不开runtime的支持。分类允许向已有的类添加方法,而协议则提供了多继承的替代方案,它们的实现都涉及到runtime的动态加载和方法解析机制。 总而言之,...

    nib2objc源码

    同时,它可能利用Category来扩展已有类,或者通过Category添加方法来处理特定的IB对象。 5. **XML解析**: Interface Builder文件是XML格式的,因此nib2objc需要包含XML解析库,如libxml2,来解析这些文件。解析...

    实战 objc_setAssociatedObject

    在博客中的实战示例`setAssociatedDemo`中,开发者可能会创建一个Category来扩展某个类的功能,比如`UIView+Extension`,在Category中使用`objc_setAssociatedObject`为`UIView`添加一个自定义的属性,如`tagValue`...

    WSDL2Objc下载工具

    标题中的“WSDL2Objc下载工具”是一个专门为iOS开发者设计的实用程序,它的主要功能是自动生成Objective-C客户端代码,从而简化与Web服务的集成过程。这个工具可以解析WSDL文件,并生成对应的Objective-C类,使得...

    objc4-750.1.zip

    源码中,`objc_class`结构体定义了类的内部结构,而`objc_object`表示对象的基本结构。 3. **动态加载与类别**:Objective-C支持运行时添加方法和属性,这涉及到动态加载机制。`objc_loadClass`和`objc_...

    objc 中文+ 英文,快速上手

    ObjC的核心是类(Class)和对象(Object)。类是一种抽象的数据类型,定义了对象的行为和属性。对象是类的实例,具有类定义的特性和功能。在ObjC中,通过`@interface`和`@implementation`来声明和实现类。 ```objc ...

    j2objc-annotations-1.3-API文档-中文版.zip

    赠送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; 包含...

    j2objc-annotations-1.1-API文档-中文版.zip

    赠送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工具

    nib2objc工具是一款针对iOS开发的实用小工具,它的主要功能是将.xib或.storyboard文件转换为Objective-C代码,方便开发者快速地将界面设计与代码实现相结合。在这个过程中,nib2objc能够帮助开发者节省大量的手动...

    iOS关联objc_setAssociatedObject

    iOS-关联(objc_setAssociatedObject、objc_getAssociatedObject、objc_removeAssociatedObjects) 详解请参考:http://blog.csdn.net/u014220518/article/details/71750875

    J2OBJC的demo

    **J2OBJC简介** J2OBJC是一个由Google开发的开源工具,它的主要功能是将Java源代码转换成Objective-C代码,使得Java程序能够运行在iOS平台上。这个工具为Java开发者提供了一条途径,无需学习Objective-C或Swift,就...

    objective c 消息 objc_msgSend

    - 如果没有,继续在接收者的元类(meta-class)中查找。 - 若仍无,沿着继承链向上搜索,直到找到方法或者到达根类(NSObject或其基类)。 - 如果找到了方法,就执行;若未找到,会触发“未定义的选择器”异常。 **...

    Objc4--750

    Objc4--750 是一个与iOS开发相关的项目,主要关注的是Objective-C运行时(runtime)的源码分析。Objective-C runtime是苹果的Objective-C编程语言的核心部分,它负责对象的创建、方法的调用以及类的动态行为。在这个...

    objc使用正则表达式

    在iOS开发中,Objective-C(简称objc)是主要的编程语言之一。本篇文章将深入探讨如何在objc中本地读取网页内容以及利用正则表达式进行数据处理。这两个技术是移动应用开发中不可或缺的部分,特别是在涉及到网络数据...

    j2objc-annotations-1.1.jar

    The type com.google.j2objc.annotations.ReflectionSupport$Level cannot be resolved. It is indirectly referenced from required .class files

Global site tag (gtag.js) - Google Analytics