iOS 类别(Category)与扩展(Extension)

类别(Category)

类别能够做到的事情主要是:即使在你不知道一个类的源码情况下,向这个类添加扩展的方法。

①通过在interface中声明一个额外的方法并且在implementation 中定义相同名字的方法即可。

②分类的名字(也就是括号括起来的LYG_CategoryName)表示的是:对于声明于其他地方的这个类(ClassName),在此处添加的方法是额外的,而不是表示这是一个新的类。

③你不可以通过分类为一个类添加额外的成员变量(但是可以使用@dynamic 来弥补这种不足--即运行时Runtime)。

④Category的方法不一定非要在@implementation中实现,也可以在其他位置实现,但是当调用Category的方法时,依据继承树没有找到该方法的实现,程序则会崩溃。

类别的使用三种场景:

  1. 扩展已有的类
    类别特别适合已经存在大量子类,需要添加公用方法,但又无法修改它们父类(如系统类)的情形
  2. 引用父类未公开方法
  3. 实现简单协议

类别声明模板:

.h中

@interface ClassName (LYG_CategoryName)
- (void)lyg_addedMethod;
@end

.m中

@implementation ClassName (LYG_CategoryName)
- (void)lyg_addedMethod {
}
@end
  • LYG--是我自定义的前缀,建议都加上一个自定义的前缀。

创建过程如下:

1

2

3

4

以下实例代码:

实例1:(需要记录上次的点击的cell,特此做的分类添加的属性)--特殊

#import <UIKit/UIKit.h>

@interface UITableView (SpokenHomeWorkTable)

@property (nonatomic,strong) NSIndexPath *recordIndexPath;

@end
#import "UITableView+SpokenHomeWorkTable.h"
#import <objc/runtime.h>
@implementation UITableView (SpokenHomeWorkTable)

static NSString *indexpathKey = @"recordIndexPath";

- (NSIndexPath *)recordIndexPath{
    return objc_getAssociatedObject(self, &indexpathKey);
}

- (void)setRecordIndexPath:(NSIndexPath *)recordIndexPath{
    
    objc_setAssociatedObject(self, &indexpathKey, recordIndexPath, OBJC_ASSOCIATION_RETAIN);

}

@end

实例2:扩展已有的类(自定义tabbar上的小红点)

#import <UIKit/UIKit.h>

@interface UITabBarController (badge)
/**
 *  显示提示小红点标记
 *
 *  @param index tabbar下标位置
 */
- (void)showBadgeOnItemIndex:(int)index;

/**
 *  隐藏小红点标记
 *
 *  @param index tabbar下标位置
 */
- (void)hideBadgeOnItemIndex:(int)index;

/**
 *  移除小红点标记
 *
 *  @param index tabbar下标位置
 */
- (void)removeBadgeOnItemIndex:(int)index;

@end
#import "UITabBarController+badge.h"
//#define TabbarItemNums 3.0   //tabbar的数量

@implementation UITabBarController (badge)

- (void)showBadgeOnItemIndex:(int)index{
    
    //移除之前的小红点
   [self removeBadgeOnItemIndex:index];
    
    //新建小红点
    UIView *badgeView = [[UIView alloc]init];
    badgeView.tag = 888 + index;
    badgeView.layer.cornerRadius = 5;
    badgeView.backgroundColor = [UIColor redColor];
    CGRect tabFrame = self.tabBar.frame;
    
    //确定小红点的位置
    NSInteger tabbarItemNums = 3;
    if ([UserDefault boolForKey:KCreatEnglishTabbar]) {
        tabbarItemNums = 4;
    }
    float percentX = (index +0.6) / tabbarItemNums;
    CGFloat x = ceilf(percentX * tabFrame.size.width);
    CGFloat y = ceilf(0.1 * tabFrame.size.height);
    badgeView.frame = CGRectMake(x, y, 10, 10);
    [self.tabBar addSubview:badgeView];
    
    
}

//移除小红点
- (void)hideBadgeOnItemIndex:(int)index{
    
    [self removeBadgeOnItemIndex:index];
    
}

//按照tag值进行移除
- (void)removeBadgeOnItemIndex:(int)index{
    
    for (UIView *subView in self.tabBar.subviews) {
        
        if (subView.tag == 888+index) {
            
            [subView removeFromSuperview];
            
        }
    }
}


/*
 //显示
 [self.tabBarController.tabBar showBadgeOnItemIndex:2];
 //隐藏
 [self.tabBarController.tabBar hideBadgeOnItemIndex:2]
 
 //其它
 //    self.tabBarItem.badgeValue =@"10";
 //    UITabBarItem * item=[self.tabBarController.tabBar.items objectAtIndex:2];
 //    item.badgeValue=[NSString stringWithFormat:@"%d",10];
 */

@end

实例3: 引用父类未公开方法

(比如父类 XSDLabel)
XSDLabel.h

#import <UIKit/UIKit.h>

@interface XSDLabel : UILabel

@end

XSDLabel.m

#import "XSDLabel.h"

@implementation XSDLabel
- (void)giveTextRandomColor {
    self.textColor = [UIColor orangeColor];
}
@end

XSDLabel1继承自XSDLabel:

#import <UIKit/UIKit.h>
#import "XSDLabel.h"
@interface XSDLabel1 : XSDLabel

@end

现在需要在设置text时,同时设置文字颜色,调用父类的giveTextRandomColor:

#import "XSDLabel1.h"

@implementation XSDLabel1

- (void)setText:(NSString *)text {
    [super setText:text];
    [self giveTextRandomColor];
}

@end

直接编译会报错:编译器提示找不到父类的方法
在子类中声明父类类别后,即可通过编译:

#import "XSDLabel1.h"

@interface XSDLabel (private)
- (void)giveTextRandomColor;
@end

@implementation XSDLabel1

- (void)setText:(NSString *)text {
    [super setText:text];
    [self giveTextRandomColor];
}

@end

类别名private是任意的,但不可以缺省。

  • 请不要乱来:苹果官方会拒绝使用系统私有API的应用上架,因此即使学会了如何调用私有方法,在遇到调用其它类的私有方法时,要谨慎处理,尽量用其它方法替代。

实例4: 实现简单协议

假设我们需要在文字颜色改变时,发出一个消息,现在修改XSDLabel如下:

#import <UIKit/UIKit.h>

@interface XSDLabel : UILabel
@property(nonatomic) id delegate;
@end

@interface NSObject (XSDLabelDelegateMethods)
- (void)textColorChanged:(UIColor *)colorNow;
@end

增加delegate,声明为id,表示接受任何类。
声明NSObject的类别,声明它实现的方法。

#import "XSDLabel.h"

@implementation XSDLabel
- (void)giveTextRandomColor {
    self.textColor = [UIColor orangeColor];
    [self.delegate textColorChanged:self.textColor]; // 调用代理的方法
}
@end

调用的地方:

#import "XSDLabel1.h"
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    XSDLabel1 *label = [[XSDLabel1 alloc] initWithFrame:CGRectMake(10.0, 40.0, 100.0, 30.0)];
    [self.view addSubview:label];
    label.delegate = self;
    label.text = @"溪石iOS";
}

- (void)textColorChanged:(UIColor *)colorNow {
    NSLog(@"text color changed to %@", colorNow);
}

这里利用了任何类都是NSObject子类的特点,通过添加NSObject的类别,实现了一个“简单”的代理协议。
对比“正式的协议”,这种协议不需要实现类显示声明(如<NSCopying>),不过这里还有个缺点,当ViewController未实现textColorChanged方法时,会引发崩溃,因此在调用前,需要检查代理方法是否被实现:

#import "XSDLabel.h"

@implementation XSDLabel
- (void)giveTextRandomColor {
    self.textColor = [UIColor orangeColor];
    if ([self.delegate respondsToSelector:@selector(textColorChanged:)]) {
        [self.delegate textColorChanged:self.textColor];
    }
}
@end



扩展(Extension)

类扩展就像匿名(也就是没有那个括号里面的名字LYG_CategoryName)的分类一样,除了一样不同的是,类扩展声明必须在@implementation在实现。

// .h
@interface BaseClass : NSObject
@property (readonly) NSString *privateString;   // 该.h文件对外公开
@end
// .m
@interface BaseClass()          // 该.m文件对外是不公开的, 当然这里也可以放在专门的一个.h文件中,但同样不把这个文件进行公开。
@property (readwrite) NSString *privateString;
@end

@implementation BaseClass
@synthesize privateString;
//...
@end

声明的方法必须要实现,不然编译器会提出警告。
从上面看出,分类和类扩展的相似之处是:都可以为类添加一个额外的方法;

不同之处在于:要添加额外方法,分类必须在第一个@interface中声明方法,并且在@implementation中提供实现,不然运行时出错。而类扩展,你添加的方法是一个required API,如果不去实现,编译器会警告,而且这个方法的声明可以不在第一个@interface中去声明。
区别:

分类:是不可以声明实例变量,通常是公开的,文件名通常为:”主类类名+分类类名.h”

扩展:是可以声明实例变量,是私有的,文件名通常为:”主类类名_扩展标识.h”,注意扩展没有名的。

区别分类与扩展

1.都可以在主类中声明使用

2.通常来讲由于分类不能创建实例变化,本质上与主类有区别,所以不建议写在主类中。

3.扩展与主类紧密联系在一起,可以创建实例变量,所以通常来讲会把扩展和主类创建在一起。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容