iOS 分类(category)

一、Categories
1、简介
  • Categories: 当原有类的方法不够用时,category可在现有类的基础上添加新的方法(即使在你不知道一个类的源码情况下,也可以向这个类添加扩展的方法)。Categories只能添加方法,不能添加实例变量(为运行期决议)
    ​ 优势:

    • 类别能够将类的实现拆分到不同的文件中,即"可以将类的实现分散到不同的文件里" ----- “进行模块化设计”
    • 利用类别来调用私有方法
  • Extension: iOS中的extension就是匿名的分类,只有头文件没有实现文件,扩展的方法只能在原类中实现。 extension是类的一部分,在编译期和头文件里的@interface和实现文件构成一个完整的类。例如你扩展NSString,那么你只能在NSString的.m实现(这是不可能的)。扩展可以添加新的实例变量(编译期决议)

    • 一般用来隐藏类的私有信息
2、实例

Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
@end

Person.m

#import "Person.h"
@implementation Person
- (void)run{
    NSLog(@"本类run。。。");
}
@end

(1)下面是使用category,给Person增加一个玩的方法
Person+PlayGame.h

#import "Person.h"
@interface Person (PlayGame)
- (void)playLol;
- (void)run;
@end

Person+PlayGame.m

#import "Person+PlayGame.h"

@implementation Person (PlayGame)
- (void)playLol{
    NSLog(@"此人在玩游戏。。。");
}
- (void)run{
    NSLog(@"playgame run...");
}
@end

(2)使用类扩展
Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
@end

Person_work.h

#import "Person.h"

@interface Person ()
{
    int _height;
}
- (void)run;
- (void)work;
@end

扩展的方法实现在原类的实现文件中,即Person.m

#import "Person.h"
#import "Person_work.h"
@interface Person()

{
    int _age;
}
- (void)run;
@end

@implementation Person
- (void)work{
    NSLog(@"%d",_age);
    NSLog(@"working。。。");
    NSLog(@"height is :%d",_height);
}
- (void)run{
    NSLog(@"running ....");
    NSLog(@"%d",_age);
}
- (void)testRun{
    [self run];
    NSLog(@"height is :%d",_height);
}
@end
3、讨论

(1)什么时候使用category?

简介中已经说明了category的好处,可以将一个类的实现拆分到不同的文件中,但是什么时候使用分类呢?对于小白来说有点茫然,本人初步总结以下场景

  • 前辈已经实现好一个功能类,而你需要为该类附加额外独立功能时可以给该类提供一个分类。好处如下
    • 分类既可以使用到原功能类的信息,又保持与原功能类独立,整体结构清晰。
    • 模拟多继承

(2)为什么Categories只能添加方法,不能添加实例变量
类别中只能添加方法,不能添加实例变量。我们经常看见在类别中这样写:

@property (nonatomic, assign) CGFloat x;

在类别里面可以写属性,但是实际上类别不会自动生成实例变量的,而是添加的setter和getter方法。runtime函数中,确实有一个**class_addIvar()**函数用于给类添加成员变量,但是文档中特别说明:

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

意思是说,这个函数只能在构建一个类的过程中调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用 objc_allocateClassPair 之后,objc_registerClassPair之前才可以被使用,同样没有机会再添加成员变量。那为什么可以在类别中添加方法和属性呢?
因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。

(3)必须在类别中添加实例变量时怎么办?
方法一:
iOS中协议中和分类中是可以用@property形式声明属性的,只不过在协议、分类中声明的属性,只有对应的setter/getter方法,并没有生成对应的成员变量。因为协议中只可以声明方法,分类中只能声明方法和对应的实现。

Protocol

@protocol MyProtocol

@property (nonatomic, strong)NSString *protocolName;

@end

@interface ViewController :UIViewController

@end

如果一个类遵守这个协议的话,在该类中即可调用出self.protocolName,但是如果直接调用self.protocolName = @“就是个这个鬼”;

self.protocolName = @“这是个什么鬼啊”;

NSLog(@"%@",self.protocolName);

项目会直接出现崩溃

Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: '-[ViewController setProtocolName:]: unrecognized selector sent to instance 0x7f9fb350aa20’

实现方法1:

这是由于在协议当中的属性只有只有对应的setter/getter,但是没有成员变量,如果直接直接将其get方法写出如下:

- (NSString*)protocolName

{

​ return @“这个是个什么鬼”;

}

这样调用的话,就能够直接调用

NSLog(@"%@",self.protocolName);

输出结果为:

2018-04-10 15:30:36.373180+0800 链式编程**[36622:228189]** 这个是个什么鬼

实现方法2:

@interface ViewController ()

@end

@implementation ViewController

@synthesize protocolName =_protocolName;

上面方法中主要用到了@synthesize上面声明部分的 @synthesize protocolName =_protocolName; 意思是说,protocolName 属性为 _protocolName 成员变量合成访问器方法。 也就是说,protocolName属性生成存取方法是setprotocolName,这个setprotocolName方法就是_protocolName变量的存取方法,它操作的就是_protocolName这个变量。通过这个看似是赋值的这样一个操作,我们可以在@synthesize 中定义与变量名不相同的getter和setter的命名,籍此来保护变量不会被不恰当的访问

self.protocolName = @“啊啊啊”;

​ NSLog(@"%@",self.protocolName);

输出结果:

2018-04-10 15:30:36.373180+0800****啊啊啊

实现方法3:

不要忘记了Objective-C是动态语言,利用runtime黑魔法。一种常见的办法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。

//NSObject+IndieBandName.h
@interface NSObject (IndieBandName)
@property (nonatomic, strong) NSString *indieBandName;
@end

//上面是头文件声明,下面的实现的.m文件:
// NSObject+IndieBandName.m 
#import "NSObject+Extension.h"
#import <objc/runtime.h>
static const void *IndieBandNameKey = &IndieBandNameKey; 
@implementation NSObject (IndieBandName)
@dynamic indieBandName;
- (NSString *)indieBandName 
{ 
return objc_getAssociatedObject(self, IndieBandNameKey);
}

- (void)setIndieBandName:(NSString *)indieBandName 
{ 
objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

通过runtime的两种方法就可以为类别添加一个实例变量了。

美团技术团的一篇博文:http://tech.meituan.com/DiveIntoCategory.html

(4)分类中的方法名和原始类一样会完全替换掉原始类的方法?

引用上面文章的一段话

1)、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,**类的方法列表里会有两个methodA **

2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值