1、_cmd
_cmd
表示当前方法的selector
的一个指针,如同self表示指向当前对象的一个指针
- 比如,我们要打印当前要调用的方法,可以这样来写:
注意:
NSString *NSStringFromSelector(SEL aSelector)
的参数就是一个SEL
, 也就是说_cmd
就是SEL
类型。
- (void)viewDidLoad{
[super viewDidLoad];
NSLog(@"Current method: %@ %@",[self class],NSStringFromSelector(_cmd));}
输出结果如下:
Current method: FirstViewController viewDidLoad
2、objc_setAssociatedObject
来把一个对象与另外一个对象进行关联。该函数需要四个参数:源对象,关键字,关联的对象和一个关联策略。
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
-
OBJC_EXPORT: 打包lib时,用来说明该函数是暴露给外界调用的。
-
id object: 表示关联者,是一个对象,变量名理所当然也是object
-
key : 关键字是一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字。
-
id value: 表示被关联者,变量名是value,它要关联到object上的。
-
policy: 关联策略, 表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。
这里的关联策略和声明属性时的很类似。通常这种关联策略是通过使用预先定义好的常量来表示的。
static char myKey;
objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
self对象将获取一个新的关联的对象anObject,且内存管理策略是自动retain关联对象,当self对象释放时,会自动release关联对象。
另外,如果我们使用同一个key来关联另外一个对象时,也会自动释放之前关联的对象,这种情况下,先前的关联对象会被妥善地处理掉,并且新的对象会使用它的内存。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
3. 动态添加方法及属性
objc_setAssociatedObject 添加的变量不会存在类的IVars中,而是存储在对象的
Associative References
的dictionary
中;也就是说我们这样添加的变量并不改变类对象的大小。
-
每一个对象都有一个可选的
dictionary
字典,我们可以向其添加key/value
对。 -
NSObject+CategoryTT.h
#import <Foundation/Foundation.h>
@interface NSObject (CategoryTT)
// 添加属性, 不过不会生成成员变量, 需要手动实现getter和setter
@property(nonatomic, copy) NSString *name;
// 添加方法
- (NSString *)des;
@end
- NSObject+CategoryTT.m
#import "NSObject+CategoryTT.h"
#import <objc/runtime.h>
@implementation NSObject (CategoryTT)
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)des {
return @"这个NSObject的分类CategoryTT";
}
@end
- 在ExtensionsAndCategory类中测试如下:
// 测试Category的属性
NSObject *obj= [[NSObject alloc] init];
obj.name = @"lili";
NSLog(@"obj name: %@", [obj name]); // obj name: lili
// 分类中实现了des方法, ExtensionsAndCategory->UIViewController->NSObject(继承关系)
NSLog(@"obj des: %@", [self des]); // obj des: 这个NSObject的分类CategoryTT
也可直接关联对象
//** 将一个对象直接关联到NSArray对象上 */
NSArray *array = [NSArray arrayWithObjects:@"111", @"222", @"333", nil];
NSString *string = @"JAM";
// 将string关联到array上
objc_setAssociatedObject(array, &associatedKey, string, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 从array中获取被关联的对象string
NSString *getAssociatedObject = objc_getAssociatedObject(array, &associatedKey);//string已经变成array的一个属性了
NSLog(@"%@", getAssociatedObject);//输出结果:JAM
4.断开关联
-
断开关联是使用
objc_setAssociatedObject
函数,传入nil
值即可。 -
使用函数
objc_removeAssociatedObjects
可以断开所有关联
5.category
Category
模式用于向已经存在的类添加方法从而达到扩展已有类的目的,在很多情形下Category也是比创建子类更优的选择。新添加的方法同样也会被被扩展的类的所有子类自动继承。当知道已有类中某个方法有BUG,但是这个类是以库的形式存在的,我们无法直接修改源代码的时候,
-
Category
也可以用于替代这个已有类中某个方法的实体,从而达到修复BUG的目的。然而却没有什么便捷的途径可以去调用已有类中原有的那个被替换掉方法实体了。需要注意的是,当准备有Category来替换某一个方法的时候,一定要保证实现原来方法的所有功能,否则这种替代就是没有意义而且会引起新的BUG。 -
和子类不同的是,
Category
不能用于向被扩展类添加实例变量。Category
通常作为一种组织框架代码的工具来使用。
Category的用途
-
在不创建继承类的情况下实现对已有类的扩展。
-
简化类的开发工作(当一个类需要多个程序员协同开发的时候,Category可以将同一个类根据用途分别放在不同的源文件中,从而便于程序员独立开发相应的方法集合)。
-
将常用的相关的方法分组。
-
在没有源代码的情况下可以用来修复BUG。
代码组织
Category用于大型类有效分解。通常一个大型类的方法可以根据某种逻辑或是相关性分解为不同的组,一个类的代码量越大,将这个类分解到不同的文件中就显得越有用,每个文件中分别是这个类的某些相关方法的集合。
当有多个开发者共同完成一个项目时,每个人所承担的是单独的cagegory的开发和维护。这样就版本控制就更加简单了,因为开发人员之间的工作冲突更少了。
Category VS 添加子类
并没有什么界限分明的判定标准来作为何时用Category何时用添加子类的方法的指导。但是有以下几个指导性的建议:
- 如果需要添加一个新的变量,则需添加子类。
- 如果只是添加一个新的方法,用Category是比较好的选择。
- 在分类中增加的方法,会被子类所继承,而且在运行时跟其他方法没有区别。
- 一般不要在分类中覆盖现有类中的方法。
Category 不添加成员变量
- @property 声明的属性只会自动生成get,set方法,并不能生成下划线的成员属性
扩展(Extension)
- 类的延展就如同时“匿名”的分类,延展中声明的方法在类本身的@implementation和它对应的@end之间实现。
- 类又是需要方法只有自己所见,我们可以通过延展的方式定义类的私有方法。
总结
-
extension
看起来很像一个匿名的category
,但是extension
和有名字的category
几乎完全是两个东西。 -
extension
在编译期决议,它就是类的一部分,在编译期和头文件里的@interface
以及实现文件里的@implement
一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。 -
extension
一般用来隐藏类的私有信息
,你必须有一个类的源码
才能为一个类添加extension
,所以你无法为系统的类
比如NSString
添加extension
。但是category
则完全不一样,它是在运行期决议的。 - 就
category
和extension的
区别来看,我们可以推导出一个明显的事实,extension
可以添加实例变量,而category
是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
网友评论