1.方法交换
根据不同的系统版本给予不同的图
@implementation UIImage (exchangeAct)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method one = class_getClassMethod([self class], @selector(imageNamed:));
Method two = class_getClassMethod([self class], @selector(pg_imageNamed:));
method_exchangeImplementations(one, two);
});
}
+(UIImage *)pg_imageNamed:(NSString *)imageName
{
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
imageName = [imageName stringByAppendingString:@"_aft7"];
}
return [UIImage pg_imageNamed:imageName];
}
@end
下面是使用method swizzling应该注意的点:
1.1 +load vs. +initialize
Swizzling应该只在load方法中使用
oc会在运行时自动调用每个类的两个方法,+load 会在类初始化加载的时候调用;+initialize方法会在程序调用类的第一个实例或者类方法的时候调用。这两个方法都是可选的,只会在实现的时候才去调用。由于method swizzling会影响到全局的状态,因此最小化竞争条件的出现变得很重要,+load方法能够确保在类的初始化时候调用,这能够保证改变应用行为的一致性,而+initialize在执行时并不提供这种保证,实际上,如果没有直接给这个类发送消息,该方法可能都不会调用到。
1.2 dispatch_once
Swizzling应该只在dispatch_once中完成
如上,由于swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是其中的一种预防措施,因为它能保证不管有多少个线程,代码只会执行一次。GCD的dispatch_once 能够满足这种需求,因此在method swizzling应该将其作为最佳的实践方式。
1.4 调用 _cmd
看起来下面的代码可能导致无限循环:
+(UIImage *)pg_imageNamed:(NSString *)imageName
{
double version = [[UIDevice currentDevice].systemVersion doubleValue];
if (version >= 7.0) {
imageName = [imageName stringByAppendingString:@"_aft7"];
}
return [UIImage pg_imageNamed:imageName];
}
可奇怪的是,它并不会。在swizzling的过程中,pg_imageNamed:已经被重新指向UIImage的原始实现-imageNamed:,但是如果我们在这个方法中调用imageNamed:则会导致无限循环。「别怕,调用_cmd,其实是调用原函数」
2.关联属性
@implementation UITextField (limitLength)
static const void * PGLimitLengthKey = @"PGLimitLengthKey";
-(void)setLimitLength:(NSInteger)limitLength
{
objc_setAssociatedObject(self, &PGLimitLengthKey, @(limitLength), OBJC_ASSOCIATION_ASSIGN);
}
-(NSInteger)limitLength
{
return [objc_getAssociatedObject(self, &PGLimitLengthKey) integerValue];
}
@end
这里涉及到了3个函数:
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
Behavior | @property Equivalent | Description |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) / @property (unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | 强引用关联对象,且为非原子操 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | 复制关联对象,且为原子操作 |
3.字典转模型
@implementation NSObject (createByDic)
static NSSet * _foundationClasses;
+(NSDictionary *)pg_customKeyDic
{
return nil;
}
+(NSDictionary *)pg_modelInArray;
{
return nil;
}
+ (void)load
{
_foundationClasses = [NSSet setWithObjects:
[NSObject class],
[NSURL class],
[NSDate class],
[NSNumber class],
[NSDecimalNumber class],
[NSData class],
[NSMutableData class],
[NSArray class],
[NSMutableArray class],
[NSDictionary class],
[NSMutableDictionary class],
[NSString class],
[NSMutableString class], nil];
}
+ (BOOL)isClassFromFoundation:(Class)c
{
return [_foundationClasses containsObject:c];
}
+(id)pg_objectWithDic:(NSDictionary *)dic
{
id thing = [self new];
Class class = self;
while (class && [NSObject isClassFromFoundation:class] == NO) {
unsigned int outCount = 0;
Ivar * ivars = class_copyIvarList(class, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString * propertyName = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
NSDictionary * customKeyDic = [class pg_customKeyDic];
NSString * key = @"";
if (customKeyDic[propertyName]) {
key = customKeyDic[propertyName];
}else{
key = propertyName;
}
id value = dic[key];
if (value == nil) continue;
NSString * type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSRange range = [type rangeOfString:@"@"];
if (range.location != NSNotFound) {
NSLog(@"-%@-%@-",key,type);
type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
if (![type hasPrefix:@"NS"]) {
Class class = NSClassFromString(type);
value = [class pg_objectWithDic:value];
}else if ([type isEqualToString:@"NSArray"]) {
NSArray * array = (NSArray *)value;
NSDictionary * modelInArray = [class pg_modelInArray];
NSString * className = modelInArray[propertyName];
if (className.length <= 0) {
value = @[];
}else{
Class class = NSClassFromString(modelInArray[propertyName]);
NSMutableArray * muArr = [NSMutableArray array];
for (int i = 0; i < array.count; i++) {
[muArr addObject:[class pg_objectWithDic:value[i]]];
}
value = [muArr copy];
}
}
}
[thing setValue:value forKeyPath:propertyName];
}
free(ivars);
class = [class superclass];
}
return thing;
}
@end
NSString * path = [[NSBundle mainBundle] pathForResource:@"model" ofType:@"json"];
NSData * jsonData = [NSData dataWithContentsOfFile:path];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
User * zc = [User pg_objectWithDic:json];
在真实应用的时候,可能出现如下状况:
1.服务端给我的键名,我们并不喜欢
2.还有就是字典内有数组,数组内的内容要转成另外一个模型的数组
这要求我们向外暴露:自定义属性名的方法+指点数组内容转成什么模型的数组的方法
被转的模型也要实现这两个方法
@implementation User
+(NSDictionary *)pg_customKeyDic
{
return @{@"userID":@"id"};
}
+(NSDictionary *)pg_modelInArray
{
return @{@"friends":@"Friend"};
}
@end
4.快速归解档
@implementation NSObject (quickINandOUT)
static NSSet * _foundationClasses;
+ (void)load
{
_foundationClasses = [NSSet setWithObjects:
[NSObject class],
[NSURL class],
[NSDate class],
[NSNumber class],
[NSDecimalNumber class],
[NSData class],
[NSMutableData class],
[NSArray class],
[NSMutableArray class],
[NSDictionary class],
[NSMutableDictionary class],
[NSString class],
[NSMutableString class], nil];
}
+ (BOOL)isClassFromFoundation:(Class)c
{
return [_foundationClasses containsObject:c];
}
-(void)quickINWithCoder:(NSCoder *)coder
{
Class class = [self class];
while (class && [NSObject isClassFromFoundation:class] == NO) {
unsigned int outCount = 0;
Ivar * ivars = class_copyIvarList(class, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
if ([ignorePropertyNameArray containsObject:key]) continue;
id value = [self valueForKeyPath:key];
[coder encodeObject:value forKey:key];
}
free(ivars);
class = [class superclass];
}
}
-(void)quickOUTWithCoder:(NSCoder *)coder
{
Class class = [self class];
while (class && [NSObject isClassFromFoundation:class] == NO) {
unsigned int outCount = 0;
Ivar * ivars = class_copyIvarList(class, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString * key = [[NSString stringWithUTF8String:ivar_getName(ivar)] substringFromIndex:1];
NSArray * ignorePropertyNameArray = [class ignorePropertyNameArray];
if ([ignorePropertyNameArray containsObject:key]) continue;
id value = [coder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
class = [class superclass];
}
}
在真实应用的时候,可能出现如下状况:
有的属性我们并不想参与归档解档
这要求我们的分类向外暴露:忽略的个别属性名称的方法
被操作的模型也要实现这个方法
+(NSArray *)ignorePropertyNameArray
{
return @[@"gemo"];
}
5.KVO 元类切换
_people = [[People alloc]init];
NSLog(@"%@,%@",[_people class],object_getClass(_people));
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@,%@",[_people class],object_getClass(_people));
打印
2017-03-17 18:04:05.698 ZCRuntimeTrain[96168:1645690] People,People
2017-03-17 18:04:05.698 ZCRuntimeTrain[96168:1645690] People,NSKVONotifying_People
这已经看出,object_getClass(_people)
和[_people class]
是不一样的
除了isa被切换之外,
在这个新类里面重写被观察的对象四个方法。class,setter,dealloc,_isKVOA
_user = [[People alloc]init];
NSLog(@"%@",object_getClass(_people));
[self logMethods];
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];NSLog(@"%@",object_getClass(_people));
[self logMethods];
-(void)logMethods{
unsigned int outCount = 0;
Method * methodList = class_copyMethodList(object_getClass(_user), &outCount);
for(int i = 0; i < outCount; i++) {
NSLog(@"Method-%@",NSStringFromSelector(method_getName(methodList[i])));
}
}
People
Method-.cxx_destruct
Method-name
Method-setName:
NSKVONotifying_People
Method-setName:
Method-class
Method-dealloc
Method-_isKVOA
5.1 重写class方法
重写class方法是为了我们调用它的时候返回跟重写继承类之前同样的内容
5.2. 重写setter方法
在新的类中会重写对应的set方法,是为了在set方法中增加另外两个方法的调用:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey:(NSString *)key
当然就是不用set方法,而直接用KVC,willChangeValueForKey
+didChangeValueForKey
也是会被调用的
5.3 重写dealloc方法
销毁新生成的NSKVONotifying_类
5.4 重写_isKVOA方法
这个私有方法估计可能是用来标示该类是一个KVO机制声称的类
这当然也给我以警醒,不要用class的方法来判断实例是什么类,而要用
->isa
来判断
文章参考: