iOS 分类(category)、扩展(Extension)
2016-03-23 14:27
561 查看
一、简介
Categories: 当原有类的方法不够用时,category可在现有类的基础上添加新的方法(即使在你不知道一个类的源码情况下,也可以向这个类添加扩展的方法);但是,Categories只能添加方法,不能添加实例变量。
优势:
(1)类别能够保证你的实现类和其他的文件区分开,即”可以将类的实现分散到不同的文件里” —– “进行模块化设计”
(2)利用类别来调用私有方法
Extension: iOS中的extension就是匿名的分类,只有头文件没有实现文件,扩展的方法只能在原类中实现。扩展可以添加新的实例变量。例如你扩展NSString,那么你只能在NSString的.m实现(这是不可能的),所以尽量少用扩展。用分类就可以了。
二、实例
Person.h
Person.m
(1)下面是使用category,给Person增加一个玩的方法
Person+PlayGame.h
Person+PlayGame.m
(2)使用类扩展
Person.h
Person_work.h
扩展的方法实现在原类的实现文件中,即Person.m
Categories只能添加方法,不能添加实例变量的讨论
类别中只能添加方法,不能添加实例变量。我们经常看见在类别中这样写:@property (nonatomic, assign) CGFloat x;在这种情况下是不会自动生成实例变量的。这里添加的属性,其实是添加的setter和getter方法。
在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用 objc_allocateClassPair 之后,objc_registerClassPair之前才可以被使用,同样没有机会再添加成员变量。那为什么可以在类别中添加方法和属性呢?
因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。
在类别中添加实例变量
那我偏偏想要在类别中添加实例变量该怎么办呢?这时候就要用到runtime了,不要忘记了Objective-C是动态语言。一种常见的办法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。
通过runtime的两种方法就可以为类别添加一个实例变量了。
美团技术团的一篇博文:http://tech.meituan.com/DiveIntoCategory.html
Categories官方文档
Categories Add Methods to Existing Classes
如果你需要向现有的类中添加一个方法,最简单的方式是使用Category
[1]Category
1.语法
使用@interface声明一个category,不需要指明任何继承,但需要在括号中指明category的名字。
例如:
@interface ClassName (CategoryName)
@end
注意:(1)你可以为任何一个类声明一个分类,即使你没有原始实现的源代码。你在类别中声明的任何方法对于原始类的所有实例都可以使用
(2)Once you’ve declared a category and implemented the methods, you can use those methods from any instance of the class, as if they were part of the original class interface
例子:一个XYZPerson类,有一个人的first name、 last name。你需要频繁地显示他们。为了不用每次写代码获得first name、 last name。你可以为XYZPerson类增加一个分类。一个分类通常被声明在一个单独的.h文件中,而实现在另外的源代码文件中。在XYZPerson中,你在.h文件中声明的分类叫做XYZPerson+XYZPersonNameDisplayAdditions.h。(使用分类时需要导入分类的头文件)
头文件
`#import “XYZPerson.h”
@interface XYZPerson (XYZPersonNameDisplayAdditions)
(NSString *)lastNameFirstNameString;
@end
//例子中XYZPersonNameDisplayAdditions分类定义了一个返回需要的字符串的额外的方法。
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
(NSString *)lastNameFirstNameString {
return [NSString stringWithFormat:@”%@, %@”, self.lastName, self.firstName];
}
@end`
2.category使用
(1)use categories to split(分离)the implementation of a complex class across multiple source code files.
(2)to provide different implementations for the category methods, depending on whether you were writing an app for OS X or iOS.
3.限制
在分类中不能声明额外的实例变量
[2]扩展
Class Extensions Extend the Internal Implementation
1.介绍: A class extension bears some similarity to a category, but it can only be added to a class for which you have the source code at compile time (the class is compiled at the same time as the class extension). The methods declared by a class extension are implemented in the @implementation block for the original class so you can’t, for example, declare a class extension on a framework class, such as a Cocoa or Cocoa Touch class like NSString.
头文件
例1:@interface ClassName ()
@end
//因为括号中没有名字,所以extension进程称为匿名分类
例2:类扩展能增加自己的属性和实例
@interface XYZPerson ()
@property NSObject *extraProperty;
@end
2.使用使用类扩展去隐藏私有信息
例如XYZPerson中可能有一个uniqueIdentifier的属性,用来记录社保号的信息,给一个人分配一个社保号可能需要大量的工作,因此XYZPerson需要又一个readonly属性。而且提供一些方法。
@interface XYZPerson : NSObject
…
@property (readonly) NSString *uniqueIdentifier;
(void)assignUniqueIdentifier;
@end
//这意味着uniqueIdentifier是不能改变的,如果一个人没有社保号,就调用- (void)assignUniqueIdentifier方法来获得。
为了XYZPerson类能内部的改变这个属性,它需要在类扩展中重新定义这个属性,即在implementation文件上方
@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end
@implementation XYZPerson
…
@end
Categories: 当原有类的方法不够用时,category可在现有类的基础上添加新的方法(即使在你不知道一个类的源码情况下,也可以向这个类添加扩展的方法);但是,Categories只能添加方法,不能添加实例变量。
优势:
(1)类别能够保证你的实现类和其他的文件区分开,即”可以将类的实现分散到不同的文件里” —– “进行模块化设计”
(2)利用类别来调用私有方法
Extension: iOS中的extension就是匿名的分类,只有头文件没有实现文件,扩展的方法只能在原类中实现。扩展可以添加新的实例变量。例如你扩展NSString,那么你只能在NSString的.m实现(这是不可能的),所以尽量少用扩展。用分类就可以了。
二、实例
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject - (void)run; @end
Person.m
#import "Person.h" @implementation Person - (void)run{ NSLog(@"本类run。。。"); } @end
(1)下面是使用category,给Person增加一个玩的方法
Person+PlayGame.h
#import "Person.h" @interface Person (PlayGame) - (void)playLol; - (void)run; @end
Person+PlayGame.m
#import "Person+PlayGame.h" @implementation Person (PlayGame) - (void)playLol{ NSLog(@"此人在玩游戏。。。"); } - (void)run{ NSLog(@"playgame run..."); } @end
(2)使用类扩展
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject - (void)run; @end
Person_work.h
#import "Person.h" @interface Person () { int _height; } - (void)run; - (void)work; @end
扩展的方法实现在原类的实现文件中,即Person.m
#import "Person.h" #import "Person_work.h" @interface Person() { int _age; } - (void)run; @end @implementation Person - (void)work{ NSLog(@"%d",_age); NSLog(@"working。。。"); NSLog(@"height is :%d",_height); } - (void)run{ NSLog(@"running ...."); NSLog(@"%d",_age); } - (void)testRun{ [self run]; NSLog(@"height is :%d",_height); } @end
Categories只能添加方法,不能添加实例变量的讨论
类别中只能添加方法,不能添加实例变量。我们经常看见在类别中这样写:@property (nonatomic, assign) CGFloat x;在这种情况下是不会自动生成实例变量的。这里添加的属性,其实是添加的setter和getter方法。
在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用 objc_allocateClassPair 之后,objc_registerClassPair之前才可以被使用,同样没有机会再添加成员变量。那为什么可以在类别中添加方法和属性呢?
因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。
在类别中添加实例变量
那我偏偏想要在类别中添加实例变量该怎么办呢?这时候就要用到runtime了,不要忘记了Objective-C是动态语言。一种常见的办法是通过runtime.h中objc_getAssociatedObject / objc_setAssociatedObject来访问和生成关联对象。这两个方法可以让一个对象和另一个对象关联,就是说一个对象可以保持对另一个对象的引用,并获取那个对象。
//NSObject+IndieBandName.h @interface NSObject (IndieBandName) @property (nonatomic, strong) NSString *indieBandName; @end //上面是头文件声明,下面的实现的.m文件: // NSObject+IndieBandName.m #import "NSObject+Extension.h" #import <objc/runtime.h> static const void *IndieBandNameKey = &IndieBandNameKey; @implementation NSObject (IndieBandName) @dynamic indieBandName; - (NSString *)indieBandName { return objc_getAssociatedObject(self, IndieBandNameKey); } - (void)setIndieBandName:(NSString *)indieBandName { objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
通过runtime的两种方法就可以为类别添加一个实例变量了。
美团技术团的一篇博文:http://tech.meituan.com/DiveIntoCategory.html
Categories官方文档
Categories Add Methods to Existing Classes
如果你需要向现有的类中添加一个方法,最简单的方式是使用Category
[1]Category
1.语法
使用@interface声明一个category,不需要指明任何继承,但需要在括号中指明category的名字。
例如:
@interface ClassName (CategoryName)
@end
注意:(1)你可以为任何一个类声明一个分类,即使你没有原始实现的源代码。你在类别中声明的任何方法对于原始类的所有实例都可以使用
(2)Once you’ve declared a category and implemented the methods, you can use those methods from any instance of the class, as if they were part of the original class interface
例子:一个XYZPerson类,有一个人的first name、 last name。你需要频繁地显示他们。为了不用每次写代码获得first name、 last name。你可以为XYZPerson类增加一个分类。一个分类通常被声明在一个单独的.h文件中,而实现在另外的源代码文件中。在XYZPerson中,你在.h文件中声明的分类叫做XYZPerson+XYZPersonNameDisplayAdditions.h。(使用分类时需要导入分类的头文件)
头文件
`#import “XYZPerson.h”
@interface XYZPerson (XYZPersonNameDisplayAdditions)
(NSString *)lastNameFirstNameString;
@end
//例子中XYZPersonNameDisplayAdditions分类定义了一个返回需要的字符串的额外的方法。
实现:#import “XYZPerson+XYZPersonNameDisplayAdditions.h”
@implementation XYZPerson (XYZPersonNameDisplayAdditions)
(NSString *)lastNameFirstNameString {
return [NSString stringWithFormat:@”%@, %@”, self.lastName, self.firstName];
}
@end`
2.category使用
(1)use categories to split(分离)the implementation of a complex class across multiple source code files.
(2)to provide different implementations for the category methods, depending on whether you were writing an app for OS X or iOS.
3.限制
在分类中不能声明额外的实例变量
[2]扩展
Class Extensions Extend the Internal Implementation
1.介绍: A class extension bears some similarity to a category, but it can only be added to a class for which you have the source code at compile time (the class is compiled at the same time as the class extension). The methods declared by a class extension are implemented in the @implementation block for the original class so you can’t, for example, declare a class extension on a framework class, such as a Cocoa or Cocoa Touch class like NSString.
头文件
例1:@interface ClassName ()
@end
//因为括号中没有名字,所以extension进程称为匿名分类
例2:类扩展能增加自己的属性和实例
@interface XYZPerson ()
@property NSObject *extraProperty;
@end
2.使用使用类扩展去隐藏私有信息
例如XYZPerson中可能有一个uniqueIdentifier的属性,用来记录社保号的信息,给一个人分配一个社保号可能需要大量的工作,因此XYZPerson需要又一个readonly属性。而且提供一些方法。
@interface XYZPerson : NSObject
…
@property (readonly) NSString *uniqueIdentifier;
(void)assignUniqueIdentifier;
@end
//这意味着uniqueIdentifier是不能改变的,如果一个人没有社保号,就调用- (void)assignUniqueIdentifier方法来获得。
为了XYZPerson类能内部的改变这个属性,它需要在类扩展中重新定义这个属性,即在implementation文件上方
@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end
@implementation XYZPerson
…
@end
相关文章推荐
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- 讲解iOS开发中基本的定位功能实现
- iOS中定位当前位置坐标及转换为火星坐标的方法
- js判断客户端是iOS还是Android等移动终端的方法
- iOS应用中UISearchDisplayController搜索效果的用法
- IOS开发环境windows化攻略
- iOS应用中UITableView左滑自定义选项及批量删除的实现
- 浅析iOS应用开发中线程间的通信与线程安全问题
- 检测iOS设备是否越狱的方法
- Objective-C的内省(Introspection)用法小结
- .net平台推送ios消息的实现方法
- 探讨Android与iOS,我们将何去何从?
- Android、iOS和Windows Phone中的推送技术详解
- iOS推送的那些事