iOS 中 Category 的一些事

前几天遇到了一个由于分类引起的 Bug, 折腾了好久才找到根源点,过程甚是纠结。于是对分类这块有一些想法啦,想想平常是否对这块遗漏了什么?特此总结下。

  • 分类 和 继承 的区别
  • 分类常用的写法
  • 使用分类时遇到的BUG

一、分类 和 继承 的区别

昨天在写一个类的扩展的时候,我第一反应是用了继承,然后事后被小伙伴说此处已经有了写好的分类了,然后我就在想为什么我第一反应会用继承呢?为什么不是用分类的呢?也就自然想到了我们面试经典题: 分类 和 继承 的区别。

网上的答案:
  • 分类:分类是对一个功能完备的类的一种补充,就像是一个东西的主要基本功能都完成了,可以用类别为这个类添加不同的组件,使得这个类能够适应不同情况的需求。比如animal这个类,具有 eat 和 run 等方法,想给这个类添加一个 bark 的方法,可以用分类。
  • 继承:多个类具有相同的实例变量和方法时,考虑用继承。即子类可以继承父类的相同特性。如 animal 具有年龄和体重两个属性,dog 也具有年龄和体重两 个属性,dog 可以继承 animal的这两个属性,即为继承。
  • 区别:
    1、分类是对方法的扩展,不能添加成员变量。继承可以在原来父类的成员变量的基础上,添加新的成员变量
    2、分类只能添加新的方法,不能修改和删除原来的方法。继承可以增加、修改和删除方法。
    3、分类不提倡对原有的方法进行重载。继承可以通过使用super对原来方法进行重载。
    4、分类可以被继承,如果一个父类中定义了分类,那么其子类中也会继承此分类。

而我个人感觉用时的简单感受就是: ** 继承修改老方法,分类添加新方法**。

#import <UIKit/UIKit.h>

@interface PQTextField : UITextField

@property (nonatomic, assign) CGFloat leftPadding; // 有左边View 离边框的距离
@property (nonatomic, assign) CGFloat rightPadding; // 有右边View 离边框的距离
@property (nonatomic, assign) CGFloat leftPlaceholderNormalPadding; //代表(没有View)离左边的距离

@end
#import "PQTextField.h"

@implementation PQTextField

- (instancetype)init {
    if (self = [super init]) {
        // 设置默认的一些情况
        _leftPadding = 8;
        _rightPadding = 8;
        _leftPlaceholderNormalPadding = 10;
    }
    return self;
}

// 左边View 距离边界的距离
- (CGRect)leftViewRectForBounds:(CGRect)bounds {
    CGRect leftRect = [super leftViewRectForBounds:bounds];
    leftRect.origin.x += _leftPadding;
    return leftRect;
}

// 右边View 距离边界的距离
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
    CGRect rightRect = [super rightViewRectForBounds:bounds];
    rightRect.origin.x -= _rightPadding;
    return rightRect;
}

//UITextField 文字与输入框的距离
- (CGRect)textRectForBounds:(CGRect)bounds{
    if (self.leftView) {
        return CGRectInset(bounds, _leftPadding * 2 + self.leftView.frame.size.width, 0);
    }
    return CGRectInset(bounds, _leftPlaceholderNormalPadding, 0);
    
}

//控制编辑文本的位置
- (CGRect)editingRectForBounds:(CGRect)bounds{
    if (self.leftView) {
        return CGRectInset(bounds, _leftPadding * 2 + self.leftView.frame.size.width, 0);
    }
    return CGRectInset(bounds, _leftPlaceholderNormalPadding, 0);
}

@end

上面这个就是我用到的一个继承的例子,对 UITextField 进行扩展,重写该类的方法。

二、分类常用的写法

  • 例子一:对 UIButton 点击方法扩展 (直接增加方法)
#import <UIKit/UIKit.h>

typedef void (^PQClickButtonHandler)(UIButton *button);

@interface UIButton (PQHandler)

- (void)pq_addClickHandler:(PQClickButtonHandler)handler;

@end

#import "UIButton+PQHandler.h"
#import <objc/runtime.h>

NSString const *pq_button_handler = @"pq_button_handler";

@implementation UIButton (PQHandler)

- (void)pq_addClickHandler:(PQClickButtonHandler)handler {
    objc_setAssociatedObject(self, &pq_button_handler, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self addTarget:self action:@selector(clickAction:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)clickAction:(UIButton *)button {
    PQClickButtonHandler handler = objc_getAssociatedObject(self, &pq_button_handler);
    if (handler) {
        handler(button);
    }
}

@end
  • 例子二:对 UIButton 点击热区域扩大 的扩展 (直接增加属性)
#import <UIKit/UIKit.h>

@interface UIButton (PQTouchExtraInsets)

@property (nonatomic, assign) UIEdgeInsets pq_touchExtraInsets;

@end
#import "UIButton+PQTouchExtraInsets.h"
#import <objc/runtime.h>

@implementation UIButton (PQTouchExtraInsets)

- (void)setPq_touchExtraInsets:(UIEdgeInsets)pq_touchExtraInsets {
    objc_setAssociatedObject(self, @selector(pq_touchExtraInsets), [NSValue valueWithUIEdgeInsets:pq_touchExtraInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIEdgeInsets)pq_touchExtraInsets {
    return [objc_getAssociatedObject(self, _cmd) UIEdgeInsetsValue];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    UIEdgeInsets touchAreaInsets = self.pq_touchExtraInsets;
    CGRect bounds = self.bounds;
    bounds = CGRectMake(bounds.origin.x - touchAreaInsets.left,
                        bounds.origin.y - touchAreaInsets.top,
                        bounds.size.width + touchAreaInsets.left + touchAreaInsets.right,
                        bounds.size.height + touchAreaInsets.top + touchAreaInsets.bottom);
    return CGRectContainsPoint(bounds, point);
}

@end

  • 例子三:对 UIButton 背景颜色添加状态 的扩展 (扩展方法)
#import <UIKit/UIKit.h>

@interface UIButton (PQBackgroundColor)

- (void)pq_setBackgroundColor:(UIColor *)color state:(UIControlState)state;

@end
#import "UIButton+PQBackgroundColor.h"

@implementation UIButton (PQBackgroundColor)

- (void)pq_setBackgroundColor:(UIColor *)color state:(UIControlState)state {
    [self setBackgroundImage:[self pq_imageWithColor:color] forState:state];
}

- (UIImage *)pq_imageWithColor:(UIColor *)color {
    CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

@end
  • 例子四:对 UIView 父UIViewController 的扩展
#import "UIView+PQViewController.h"

@implementation UIView (PQViewController)

- (UIViewController *)viewController {
    //通过响应者链,取得此视图所在的视图控制器
    UIResponder *next = self.nextResponder;
    do {//判断响应者对象是否是视图控制器类型
        if ([next isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)next;
        }
        next = next.nextResponder;
    }while(next != nil);
    return nil;
}

@end
  • 例子五:对 UIViewController dealloc 的扩展
#import "UIViewController+PQDealloc.h"
#import <objc/runtime.h>

@implementation UIViewController (PQDealloc)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        Method setTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"dealloc"));
        Method replaceSetTextMethod = class_getInstanceMethod(class, NSSelectorFromString(@"pq_dealloc"));
        method_exchangeImplementations(setTextMethod, replaceSetTextMethod);
    });
}

- (void)pq_dealloc {
    NSLog(@"%@--->>>>已经释放了",[self class]);
    [self pq_dealloc];
}

@end

上述需要注意的确实 Runtime。

  • 注意 例子一 和 例子二处 _cmd 和 const void *key 的区别
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_COPY_NONATOMIC 的区别
  • Method_exchangeImplementations 和 AssociatedObject 的运用场景
  • 给自己的方法添加上特殊名字(pq_), 避免覆写系统类的方法

在此特别推荐一下 JKCategories,里面中的分类真的超多,上述有几个例子就是从此处学习的。另外,Objective-C 基础类的一些实用 Category 这里面推荐的也可以看看。

三、使用分类时遇到的BUG

  • 使用 method_exchangeImplementations 时 replaceSetTextMethod 的设置不当
    一般这种设置不当是由于某些特殊的场景导致的, replaceSetTextMethod 里面的参数和判断有变化而成的。

  • 分类中 dealloc 设置错了
    当然我遇到的这个是处于iOS8 某个版本中,这个 BUG:UITextField textInputView: message sent to deallocated instance, 就是 dealloc 方法中写的有问题,当初可也是莫名其妙的。

总的来说,一些莫名其妙的问题,例如根本不知道 崩 在什么地方情况下,就得考虑下是否是分类中有问题,或者说哪里运用到了一些 Runtime 的方案。

PS: Category 进一步理解
备注参考:

http://www.cnblogs.com/williamliuwen/p/5370155.html
https://github.com/shaojiankui/JKCategories
http://www.jianshu.com/p/1c7d34dbf671

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

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,112评论 29 470
  • (一)Category category是Objective-C 2.0之后添加的语言特性,别人口中的分类、类别其...
    小李龍彪阅读 2,937评论 0 10
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 4,962评论 0 13
  • 1.Difference between shallow copy and deep copy? 浅复制和深复制的...
    用心在飞阅读 986评论 0 9
  • 我坐在高高的屋顶上,手里一壶桂花酿,看着天上又大又圆,像个饼的月亮。过一会儿是该练功了,我这境界,是该提升一下了,...
    肖梦瑶阅读 1,178评论 5 20