美文网首页闻道丶iOS(大杂烩)iOS Developer
iOS 类别(Category)与扩展(Extension)

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

作者: SunnyLeong | 来源:发表于2017-03-13 16:08 被阅读136次

    类别(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.扩展与主类紧密联系在一起,可以创建实例变量,所以通常来讲会把扩展和主类创建在一起。


    相关文章

      网友评论

        本文标题:iOS 类别(Category)与扩展(Extension)

        本文链接:https://www.haomeiwen.com/subject/tiifnttx.html