绝大多数人不知道的category

作者: 饭饭男 | 来源:发表于2017-11-06 17:58 被阅读19次

    在说分类之前我们先来认识两个概念:property(属性) Ivar(成员变量)
    在xcode中我们用@property关键字生个的对象就是一个属性 为这个属性添加_它就是成员变量,例如:

    @property (nonatomic,copy)NSString *height;
    

    height现在就是一个属性 _height就是一个成员变量。当我们用@property声明了一个属性后xcode会自动帮我们生成成员变量和set,get方法,所以弱化了我们对这一块的认知容易造成两个概念的混淆。

    弄清概念后我们就来谈谈面试中经常被问到Cagetory为什么不能添加属性这个问题。
    对于了解Cagetory原理的大神来说当听到面试官这个问题,你完全可以怼回去,记住你要狠狠的怼他,因为他什么都不懂居然敢来问你这个问题。没有杀害就没有买卖,没有面试就没有伤害我们通过例子的对比看看这个面试题问题出在哪里。
    首先我们创建一个类

    @interface Student : NSObject
    @property (copy , nonatomic) NSString *name;
    @end
    

    然后我们通过runtime打印出它的属性和成员变量,方法

    #import "Student.h"
    #import <objc/runtime.h>
    
    @implementation Student
    + (void)load{
    //    打印属性
        unsigned int outCount = 0;
        objc_property_t *property_t = class_copyPropertyList([Student class], &outCount);
        for (int i=0; i<outCount; i++) {
            objc_property_t property = property_t[i];
            const char *name = property_getName(property);
            NSLog(@"property %s",name);
        }
    //  打印成员变量
        unsigned int outCount2 = 0;
        Ivar *var = class_copyIvarList([Student class], &outCount2);
        for (int i=0; i<outCount2; i++) {
            Ivar ivar = var[i];
            const char *name = ivar_getName(ivar);
            NSLog(@"ivar %s",name);
           
        }
    //  打印方法
        unsigned int outCount3 = 0;
        Method *method = class_copyMethodList([Student class], &outCount3);
        for (int i =0; i<outCount3; i++) {
            Method meth = method[i];
            SEL sel = method_getName(meth);
            NSLog(@"Sel %@",NSStringFromSelector(sel));
        }
    }
    @end
    

    log:

    2017-11-06 17:14:32.879747+0800 Demo2[2281:213110] property name
    2017-11-06 17:14:32.880340+0800 Demo2[2281:213110] ivar _name
    2017-11-06 17:14:32.880825+0800 Demo2[2281:213110] Sel .cxx_destruct
    2017-11-06 17:14:32.880990+0800 Demo2[2281:213110] Sel name
    2017-11-06 17:14:32.881178+0800 Demo2[2281:213110] Sel setName:
    

    通过Log可以看到在一个正常类中声明一个属性系统会自动的帮我们生成成员变量和get,set方法。
    我们再创建一个分类

    @interface Student (Cagetory)
    @property (nonatomic,copy)NSString *age;
    @end
    

    log:

    2017-11-06 17:23:45.765658+0800 Demo2[2394:219523] property age
    2017-11-06 17:23:45.766444+0800 Demo2[2394:219523] property name
    2017-11-06 17:23:45.766794+0800 Demo2[2394:219523] ivar _name
    2017-11-06 17:23:45.767261+0800 Demo2[2394:219523] Sel .cxx_destruct
    2017-11-06 17:23:45.767709+0800 Demo2[2394:219523] Sel name
    2017-11-06 17:23:45.767926+0800 Demo2[2394:219523] Sel setName:
    

    对比两次打印结果发现当给Student添加一个属性后,再打印Student的属性,成员变量,方法会发现多了一个age属性但是没有age对应的_age和set,get方法。到此我们就揪出了刚刚的面试题题目本身问题出在哪里。你可以告诉面试官分类是可以添加属性的,只是系统不会帮我们自动生成对应的成员变量和方法。
    我们给分类添加了一个属性但是系统没有帮我们生成成员变量和set,get方法那对分类有什么影响呢?我们再来看一个例子:

    #import "Student+Cagetory.h"
    
    @implementation Student (Cagetory)
    @dynamic age;
    
    - (void)setAge:(NSString *)age
    {
    //_age = age 是通过成员变量给属性age复制,为什么这里不能用self.age = age
    //    因为当你调用self.x = 系统默认你在调用x的set方法,你在set方法里面调用set方法这肯定是有问题的
        
    //    _age = age;  你会发现找不到_age对象
        
    //    程序会crash
        self.age = age;
    }
    
    - (NSString *)age{
    //    return _age;  同理你会发现找不到_age对象
        
    //    同理程序会crash
        return self.age;
    }
    @end
    

    当我们手动实现age的set get方法你会发现在取值和赋值的时候找不到_age对象,无法进行赋值和取值操作,如果强行调用set.x程序会crash。如果我们手动实现age属性的成员变量_age会怎么样:

    1.png

    你会发现在分类中系统提示不允许实现属性的成员变量,至于为什么不让给分类添加成员变量这要从系统对分类的定义来分析。

    Category 在 runtime 中是用一个结构体表示的:
    struct _category_t {
      const char *name;
      struct _class *cls;
      const struct _method_list_t *instance_methods;
      const struct _protocol_list_t *protocols;
      const struct _prop_list_t *properties;
    }
    

    从分类的定义中我们看到它包括了分类的名字,所属类,成员方法列表,协议列表,属性列表,但并没有定义Ivar列表。既然没有Ivar列表那自然就无法正常的生成成员变量和对方的方法。
    从另一个层面讲分类本身并不是真正意义上的类。它是runtime在动态允许时为它所属的类添加了一些成员方法而已。
    如果在项目中遇到变态需求,非要给分类添加成员变量怎么变,那就要用到runtime技巧。以刚才的Student例子为例:

    static const NSString *key = @"ageKey";
    - (void)setAge:(NSString *)age
    {
        objc_setAssociatedObject(self, &key, age, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)age{
        return objc_getAssociatedObject(self, &key);
    }
    

    只需要导入runtime 然后定义一个静态不可变字符串,在set和get方法里面关联绑定就可以了。

    参考
    https://juejin.im/entry/5a02aa546fb9a045204ba5fa?utm_medium=ios&utm_source=weixinqun
    http://blog.csdn.net/lizitao/article/details/77196620
    美团团队博客
    https://tech.meituan.com/DiveIntoCategory.html

    相关文章

      网友评论

        本文标题:绝大多数人不知道的category

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