OC分类(类目/类别) 和 类扩展 - 全解析
 
具体见: oschina -> MyDemo -> 011.FoundationLog-OC分类剖析
 
 
关于OC分类,非常重要的知识点:
 
>>分类能够对实例方法,类方法进行扩展,但不能够添加类的属性及实例变量。
>>分类能够像类本身一样,调用self来访问类的方法,属性。 但对于通过新建.h与.m来写的分类来说,分类对于原始类的了解(即知道其中存在哪些方法,属性),仅通过原始类的接口文件(.h文件),这与在类外部方法类定义的一些内容是一样的,仅仅能够看到.h中的定义。此时,分类对于原始类的属性的调用,仅能够通过存取方法,若直接访问_属性名称的话(属性名称仅在原始类的.m中可见),则会提示未定义错误。
>>在使用分类中声明的方法时,需要导入分类的.h文件,才能让执行代码知道分类扩展方法的存在。实际上,由于在分类文件中我们已经导入了原始类的头文件,所以在使用分类时,仅仅导入分类头文件即可。
>>特别注意的是,如果新建一个分类,并且重写了覆盖原类中已有的方法,此种分类只要引入项目,就会替换原类中的同名方法,这种分类不需要在任何地方导入.h就可生效,即使删除掉分类.h只保留分类.m也会生效。将重写原类方法的 多个分类.m集中到一个自己创建的FoundationLog.m文件中,此种分类仍会生效!
>>因为分类是会被子类继承的,所以只对NSObjec添加分类,那么所有的OC类均可以调用我们的扩展方法!
>>对于分类中声明的方法,我们可以不去实现,可以交由子类在需要时进行实现,这点和协议很像。区别是协议是<协议名>,而分类是(分类名)
>>对于类的私有方法,我们可以新建一个原类的分类,在将其声明为可见的(而不实现),这样在类外部就可以调用该类的私有函数了。
>>在分类中重写原类的方法,只要将该分类.m拖入项目中,分类就会覆盖原类该方法实现,即使不再任何地方导入分类.h也会覆盖
 
 
关于OC类扩展,非常重要的知识点:
 
>> 类扩展仅能够在原始类中声明(.h或.m中均可,在.m中声明的类扩展其定义的属性和方法均是私有的)
>> 类扩展的实现仅能够在原始类的.m中编写
>> 类扩展中声明的方法,必须在原类.m中进行实现,否则Xcode会编译警告。
 
 
类扩展与分类的区别如下:
>> OC分类属于Runtime运行时特性,是OC语言独有的创新,其他编程语言所不具备这样的特性! 类扩展属于编译器特性,在编译阶段就会被添加合并到原类中!
 
 
OC运行时 分类/类目的实现原理
 
typedef struct objc_class *Class;  //Class其实就是一个objc_class结构体指针
 
struct objc_class { //struct objc_class结构体定义
    Class isa  OBJC_ISA_AVAILABILITY;
   
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
   
} OBJC2_UNAVAILABLE;
 
对以上OC类的具体分析如下:
 
>> isa表示一个Class对象的Class,也就是Meta Class。在面向对象设计中,一切都是对象,Class在设计中本身也是一个对象。我们会在objc-runtime-new.h文件找到证据,发现objc_class有以下定义:
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    ......
}
由此可见,结构体objc_class也是继承objc_object,说明Class在设计中本身也是一个对象。
 
>> super_class表示实例对象对应的父类
 
>> name表示类名
 
>> ivars表示多个成员变量,它指向objc_ivar_list结构体。在runtime.h可以看到它的定义:
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}
objc_ivar_list其实就是一个链表,存储多个objc_ivar,而objc_ivar结构体存储类的单个成员变量信息。
 
>> methodLists表示方法列表,它指向objc_method_list结构体的二级指针,可以动态修改*methodLists的值来添加成员方法,也是Category实现原理,同样也解释Category不能添加实例变量的原因。
 
在runtime.h可以看到它的定义:
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
   
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}
同理,objc_method_list也是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息。
 
>> cache用来缓存经常访问的方法,它指向objc_cache结构体,后面会重点讲到。
 
>> protocols表示类遵循哪些协议
 
 
以下为转载内容:

iOS分类(category),类扩展(extension)—史上最全攻略

背景:

在大型项目,企业级开发中多人同时维护同一个类,此时程序员A因为某项需求只想给当前类currentClass添加一个方法newMethod,那该怎么办呢?
最简单粗暴的方式是把newMethod添加到currentClass中,然后直接实现该方法就OK了。
但考虑到OC是单继承的,子类可以拥有父类的方法和属性。
如果把newMethod写到currentClass中,那么currentClass的子类也会拥有newMethod。但真正的需求是只需要currentClass拥有newMethod,而currentClass的子类不会拥有。
苹果为了解决这个问题,就引入了分类(Category)的概念。

分类(Category):

概念

分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。具体原因看源码组成:

Category源码:

Category
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name OBJC2_UNAVAILABLE; // 分类名
char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}
通过上面我们可以发现,这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods 列表是 objc_class 中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。
但这个结构体里面 根本没有属性列表,
根本没有属性列表,
根本没有属性列表。

那么问题来了:

为什么在分类中声明属性时,运行不会出错呢?
既然分类不让添加属性,那为什么我写了@property仍然还以编译通过呢?

接下来我们探究下分类不能添加属性的实质原因:

我们知道在一个类中用@property声明属性,编译器会自动帮我们生成_成员变量setter/getter,但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成_成员变量也无法生成setter/getter
因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。但如果调用了_成员变量setter/getter方法,报错就在所难免了。

报错原因如下:
// programmer.nameWithoutSetterGetter = @"无setter/getter"; //调用setter,编译成功,运行报错为:(-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7f9de358fd70')
// NSLog(@"%@",programmer.nameWithoutSetterGetter); //调用getter,编译成功,运行报错为-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7fe22be11ea0'
// NSLog(@"%@",_nameWithoutSetterGetter);         //这是调用_成员变量,错误提示为:(Use of undeclared identifier '_nameWithoutSetterGetter')

那接下来我们继续思考:
既然报错的根本原因是使用了系统没有生成的setter/getter方法,可不可以在手动添加setter/getter来避免崩溃,完成调用呢?
其实是可以的。由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法。那具体怎么实现呢?

代码实现如下:

按照这个思路,我们通过运行时手动添加这个方法。

#import <objc/runtime.h>

static NSString *nameWithSetterGetterKey = @"nameWithSetterGetterKey";   //定义一个key值
@implementation Programmer (Category) //运行时实现setter方法
- (void)setNameWithSetterGetter:(NSString *)nameWithSetterGetter {
objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
} //运行时实现getter方法
- (NSString *)nameWithSetterGetter {
return objc_getAssociatedObject(self, &nameWithSetterGetterKey);
} @end

实际使用效果

  //通过runtime实现了setter/getter
programmer.nameWithSetterGetter = @"Baby";        //调用setter,成功
NSLog(@"%@",programmer.nameWithSetterGetter); //调用getter,成功
  //NSLog(@"%@",_nameWithSetterGetter);             //这是调用_成员变量,错误提示为:(Use of undeclared identifier '_nameWithSetterGetter')
问题解决。但是注意,以上代码仅仅是手动实现了setter/getter方法,但调用_成员变量依然报错。

类扩展(Class Extension)

Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
其实开发当中,我们几乎天天在使用。对于有些人来说像是最熟悉的陌生人。

类扩展格式:

@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end

作用:

为一个类添加额外的原来没有变量,方法和属性
一般的类扩展写到.m文件中
一般的私有属性写到.m文件中的类扩展中

类别与类扩展的区别:

①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(

用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加合并到原类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现.m文件,因为类扩展所声明的方法必须依托对应类的实现部分来实现。非要独立创建类扩展,只会生成一个类似protocol那样的.h文件,不会有.m文件。一般我们都是在原类的.m上声明类扩展,新建一个控制器,Apple默认就会在.m中提供类扩展!

⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

注意:
1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ;
2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
3.可以在分类中访问原有类中.h中的属性;
4.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;

5.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
 
 

OC分类(类目/类别) 和 类扩展 - 全解析的更多相关文章

  1. OC学习篇之—写类别(类的扩展)

    首先我们来看一下场景,如果我们现在想对一个类进行功能的扩充,我们该怎么做? 对于面向对象编程的话,首先会想到继承,但是继承有两个问题: 第一个问题:继承的前提是这个类可以被继承,因为在Java中有些类 ...

  2. Objective-C中的类目(Category),延展(Extension)

    类目和延展的作用都是为了扩展一个类. Objective-C中的类目(Category) 一.类目的定义和作用 类目也叫分类,英文Category,在没有原类.m文件的基础上,给该类添加方法. 比如, ...

  3. ECshop网点程序优化-自动生成类目页Keywords、Desciption Meta

    ECshop支持针对每个新建的类目自定义Keywords.Description Meta信息,好处就不用说了,帮助SEO或者让浏览者了解这是什么页面,但如果有几百个类目的时候,人工去写这些类目又有点 ...

  4. 微信小程序个人/企业开放服务类目一览表

    微信小程序个人/企业开放服务类目一览表   微信小程序个人开放服务类目表 服务类目 类目分类一 类目分类二 引导描述 出行与交通 代驾 / / 生活服务 家政.丽人.摄影/扩印.婚庆服务.环保回收/废 ...

  5. Java生鲜电商平台-生鲜电商中商品类目、属性、品牌、单位架构设计与实战

    Java生鲜电商平台-生鲜电商中商品类目.属性.品牌.单位架构设计与实战 说明:Java生鲜电商平台-生鲜电商中商品类目.属性.品牌.单位架构设计与实战经验分享 凡是涉及到购物,必然是建立在商品的基础 ...

  6. Object-C类目(Category)

    类目是Object-C中最有用的一个特性.实质上,类目允许你为一个已存在的类添加一些方法而不用子类化该类,也不需要你了解该类的实现细节. 这是特别有用的,因为你可以给一个内建的对象添加方法.当你想在你 ...

  7. sell 项目 类目表 设计 及 创建

    1.数据库设计 2.类目表 创建 /** * 类目表 */ create table `product_category` ( `category_id` int not null auto_incr ...

  8. OC语言中类目,延展,协议

    一.类目 指向已知的类中添加新方法,不破坏封装性.已知类可以是自定义的类和系统的类. 1.类目的实现和声明 建一个学生类,并增加类目 (1).声明(是在Student+Working.h中) 必须引入 ...

  9. iOS中的分类(category)和类扩展(extension)

    今天在研究swift的时候看到了分类和扩展.这是两个十分重要有用的功能,但是之前用的不多,没有深入了解过,在今天就从头理一遍. 一.分类(Category): 概念: 分类(Category)是OC中 ...

随机推荐

  1. python 递归和二分法

    一 内置函数 1. revserd 翻转,返回的是迭代器 # 将 s 倒置 s = '不是上海自来水来自海上' # 方法一 print(s[::-1]) # 方法二 s1 = reversed(s) ...

  2. 设计模式(15)--Interpreter(解释器模式)--行为型

    作者QQ:1095737364    QQ群:123300273     欢迎加入! 1.模式定义: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解 ...

  3. CentOS7下安装caffe(包括ffmpeg\boost\opencv)

    因为有项目想采用深度学习,而caffe是深度学习框架中比较理想的一款,并且跨平台,以及可以采用python/matlab的方式进行调用等优势,所以想在服务器上安装,下面就开始了血泪史... 服务器是阿 ...

  4. ActiveReports 报表控件V12新特性 -- 新增矩表的RepeatToFill属性

    ActiveReports是一款专注于 .NET 平台的报表控件,全面满足 HTML5 / WinForms / ASP.NET / ASP.NET MVC / WPF 等平台下报表设计和开发工作需求 ...

  5. LeetCode题解之Linked List Cycle

    1.题目描述 2.问题分析 使用快慢指针方法,一个快指针,一个慢指针,如果到某个时候,快指针追上了慢指针,则说明有环存在. 3.代码 bool hasCycle(ListNode *head) { i ...

  6. DevOps之基础设施-电力

    唠叨话 关于噢屁事的知识点,仅提供精华汇总,具体知识点细节,参考教程网址,如需帮助,请留言. <基础设施-电力> 关于基础设施的电力部分,知识与技能的层次(知道.理解.运用),理论与实践的 ...

  7. CAC的Debian-8-64bit安装BBR正确打开方式

    装过三台debian 64 bit, CAC, 2欧, KVM虚拟机 做法都一样---下面说下正确安装方式   0. 有装锐速记得先删除,免得换核心后,锐速在扯后腿.   1.换4.9版kernel ...

  8. 利用percona-toolkit定位数据库性能问题

    当你的性能瓶颈卡在数据库这块的时候,可以通过percona-toolkit来进行问题定位. 那么,首先,介绍下percona-toolkit.percona-toolkit是一组高级命令行工具的集合, ...

  9. Operating System Error Codes

    How To Fix Windows Errors Click here follow the steps to fix Windows and related errors. Instruction ...

  10. ZooKeeper 管理脚本

    0. 说明 编写 xzk.sh 脚本,是为了方便在 s101 节点上启动所有的 Zookeeper 进程 1. xzk.sh 脚本 #!/bin/bash ; i<=; i++)) ; do t ...