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會影響到全局的狀態(tài)迂卢,因此最小化競爭條件的出現變得很重要恢着,+load方法能夠確保在類的初始化時候調用,這能夠保證改變應用行為的一致性危纫,而+initialize在執(zhí)行時并不提供這種保證宗挥,實際上,如果沒有直接給這個類發(fā)送消息种蝶,該方法可能都不會調用到契耿。
1.2 dispatch_once
Swizzling應該只在dispatch_once中完成
如上,由于swizzling會改變全局狀態(tài)螃征,所以我們需要在運行時采取一些預防措施搪桂。原子性就是其中的一種預防措施,因為它能保證不管有多少個線程盯滚,代碼只會執(zhí)行一次踢械。GCD的dispatch_once 能夠滿足這種需求,因此在method swizzling應該將其作為最佳的實踐方式魄藕。
1.4 調用 _cmd
看起來下面的代碼可能導致無限循環(huán):
+(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:則會導致無限循環(huán)。「別怕,調用_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
來判斷
文章參考: