您的位置:首页 > 编程语言 > Go语言

Category的进阶问题

2016-07-02 20:46 302 查看
先看看官方文档对Category的解释:

You use categories to define additional methods of an existing class—even one whose source code is unavailable to you—without subclassing. You typically use a category to add methods to an existing class, such as one defined in the Cocoa frameworks. The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class. You can also use categories of your own classes to:

你使用类别定义额外的方法,那些源码是不提供给你的。你通常使用一个类别将方法添加到一个现有的类,如可可中定义一个框架。添加方法被子类继承,并在运行时无法区分从原始类的方法。您还可以使用类别的自己的类:

Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.分发自己的类的实现到单独的源文件的例子,你可以组织一个大的方法类分成几个类别,每个类别在不同的文件中。

Declare private methods.声明私有方法。

You add methods to a class by declaring them in an interface file under a category name and defining them in an implementation file under the same name. The category name indicates that the methods are an extension to a class declared elsewhere, not a new class.

你给一个类添加方法通过声明他们下一个接口文件类别名称和定义它们实现文件相同的名称。类别名称表明方法是扩展一个类声明在其他地方,不是一个新类。

下面聊聊我们自己的问题:

Category添加属性的问题

都说Category不能添加属性,让我们来细究一下,

话说我建立了一个Category,如下:

然后在category中添加name属性,我们看到,同时修改ViewDidLoad中的内容,具体见下面的代码:

@interface Person (Category1)
- (void)run;
- (void)eat;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *name;
@end


Person *p = [[Person alloc]init];
NSLog(@"%p", p);
[p eat];
[p run];
[p find];
[p eat];
p.age = 10;
p.name = @"hello world";

NSLog(@"%ld", p.age);
NSLog(@"%@", p.name);


执行代码之后会出现如下的问题:

2016-07-02 14:57:57.633 引用计数[64983:845994] 0x7fa901671770
2016-07-02 14:57:57.633 引用计数[64983:845994] category1 -[Person(Category1) eat]
2016-07-02 14:57:57.633 引用计数[64983:845994] category1 -[Person(Category1) run]
2016-07-02 14:57:57.634 引用计数[64983:845994] category2 -[Person(Category2) find]
2016-07-02 14:57:57.634 引用计数[64983:845994] category1 -[Person(Category1) eat]
2016-07-02 14:57:57.634 引用计数[64983:845994] -[Person setName:]: unrecognized selector sent to instance 0x7fa901671770
2016-07-02 14:57:57.688 引用计数[64983:845994] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x7fa901671770'


因为我用了p.name,所以提出了这个问题。这就是当我们给category添加属性的时候必须重写getter方法和setter方法。

修改category的.m文件如下:

- (void)setName:(NSString *)name {
_name = name;
}

- (NSString *)name {
return _name;
}


但是发现_name一直有问题,始终提示我_name这个属性找不到。然后我就

所以改用runtime来实现这个功能。

- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &strNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
return objc_getAssociatedObject(self, &strNameKey);
}


需要添加头文件:

#import <objc/runtime.h>


和定义strNameKey:

static char *strNameKey = "name";


看看修改完之后的结果是什么:

2016-07-02 15:18:56.247 引用计数[66248:867126] 0x7f92fa4bd700
2016-07-02 15:18:56.248 引用计数[66248:867126] category1 -[Person(Category1) eat]
2016-07-02 15:18:56.248 引用计数[66248:867126] category1 -[Person(Category1) run]
2016-07-02 15:18:56.248 引用计数[66248:867126] category2 -[Person(Category2) find]
2016-07-02 15:18:56.248 引用计数[66248:867126] category1 -[Person(Category1) eat]
2016-07-02 15:18:56.249 引用计数[66248:867126] 10
2016-07-02 15:18:56.249 引用计数[66248:867126] hello world
2016-07-02 15:18:56.249 引用计数[66248:867126] Person release


可以看到我们定义的name属性起作用了。

一个类有两个Category,然后这两个Category有两个同样的方法的时候,到底先执行哪一个?

在category1和category2中分别添加以下的方法:

category1:

- (void)run;
- (void)eat;


category2:

- (void)eat;
- (void)find;


然后在添加如下代码:

Person *p = [[Person alloc]init];
NSLog(@"%p", p);
[p eat];
[p run];
[p find];
[p eat];


打印结果如下:

2016-07-02 15:26:14.268 引用计数[66911:882140] category1 -[Person(Category1) eat]
2016-07-02 15:26:14.268 引用计数[66911:882140] category1 -[Person(Category1) run]
2016-07-02 15:26:14.268 引用计数[66911:882140] category2 -[Person(Category2) find]
2016-07-02 15:26:14.269 引用计数[66911:882140] category1 -[Person(Category1) eat]


说明了两个category中的eat只执行了第一个的category中的eat.

是根据buildPhases->Compile Sources里面的从上至下顺序编译的,即后编译的会被调用。就是Xcode中的buildPaese的顺序,从上到下编译,后编译的会覆盖先编译的,所以,查看buildPhases时看到category2是在前面就有了上面的结果。

说说Category和Extension的区别:

Category:

- 用于给class及其subclass添加新的方法

- 有自己单独的 .h 和 .m 文件

- 用于添加新方法,而不能添加新属性(property)

Extension:

- Extension常被称为是匿名的Category

- 用于给类添加新方法,但只作用于原始类,不作用于subclass

- 只能对有implementation源代码的类写Extension,对于没有implementation源代码的类,比如framework class,是不可以的

- Extension可以给原始类添加新方法,以及新属性

extension是编译器决议,和类的头文件里的@interface以及实现文件里面的@implement一起形成一个完整的类。你必须用一个类的源码才能为它extension,比如你无法为NSString添加extension。

category是运行期决议的。所以extension可以添加实例变量,而category作为运行期决议是无法添加实例变量,因为会破坏类的内存布局。

为什么Category的方法可以覆盖原类的方法

运行时,OC几乎所有的内容都是以结构体的形式存在的。catrgory也不例外。

typedef struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
} category_t;


1)、类的名字(name)

2)、类(cls)

3)、category中所有给类添加的实例方法的列表(instanceMethods)

4)、category中所有添加的类方法的列表(classMethods)

5)、category实现的所有协议的列表(protocols)

6)、category中添加的所有属性(instanceProperties)

category并不是绝对的覆盖了类的同名方法,而是catrgory的方法被排在了类的同名方法之前,而方法的检索方式是顺序检索,所以在调用方法时,调用到的同名方法是category的,进而产生了覆盖效果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: