create
This commit is contained in:
33
ShenQi/YYModel/ConfigData.h
Normal file
33
ShenQi/YYModel/ConfigData.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ConfigData.h
|
||||
//
|
||||
// Created by Yao on 2024/12/19
|
||||
// Copyright (c) 2024 zL. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ConfigData : NSObject <NSCoding, NSCopying>
|
||||
|
||||
@property (nonatomic, strong) NSString *description;
|
||||
@property (nonatomic, strong) NSString *iosVersionCode;
|
||||
@property (nonatomic, assign) NSInteger forceUpdate;
|
||||
@property (nonatomic, strong) NSString *fbUrl;
|
||||
@property (nonatomic, strong) NSString *credentials;
|
||||
@property (nonatomic, strong) NSString *url;
|
||||
@property (nonatomic, assign) CGFloat inviteFee;
|
||||
@property (nonatomic, assign) NSInteger userId;
|
||||
@property (nonatomic, assign) NSInteger contactApplyMode;
|
||||
@property (nonatomic, strong) NSString *versionCode;
|
||||
@property (nonatomic, strong) NSString *tgUrl;
|
||||
@property (nonatomic, assign) NSInteger noticeApplyMode;
|
||||
@property (nonatomic, strong) NSString *wsUrl;
|
||||
@property (nonatomic, strong) NSString *apkUrl;
|
||||
@property (nonatomic, strong) NSString *downloadUrl;
|
||||
@property (nonatomic, assign) NSInteger isUse;
|
||||
|
||||
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict;
|
||||
- (NSDictionary *)dictionaryRepresentation;
|
||||
|
||||
@end
|
||||
178
ShenQi/YYModel/ConfigData.m
Normal file
178
ShenQi/YYModel/ConfigData.m
Normal file
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// ConfigData.m
|
||||
//
|
||||
// Created by Yao on 2024/12/19
|
||||
// Copyright (c) 2024 zL. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConfigData.h"
|
||||
|
||||
NSString *const kConfigDataDescription = @"description";
|
||||
NSString *const kConfigDataIosVersionCode = @"iosVersionCode";
|
||||
NSString *const kConfigDataForceUpdate = @"forceUpdate";
|
||||
NSString *const kConfigDataFbUrl = @"fbUrl";
|
||||
NSString *const kConfigDataCredentials = @"credentials";
|
||||
NSString *const kConfigDataUrl = @"url";
|
||||
NSString *const kConfigDataInviteFee = @"inviteFee";
|
||||
NSString *const kConfigDataUserId = @"userId";
|
||||
NSString *const kConfigDataContactApplyMode = @"contactApplyMode";
|
||||
NSString *const kConfigDataVersionCode = @"versionCode";
|
||||
NSString *const kConfigDataTgUrl = @"tgUrl";
|
||||
NSString *const kConfigDataNoticeApplyMode = @"noticeApplyMode";
|
||||
NSString *const kConfigDataWsUrl = @"wsUrl";
|
||||
NSString *const kConfigDataApkUrl = @"apkUrl";
|
||||
NSString *const kConfigDataDownloadUrl = @"downloadUrl";
|
||||
NSString *const kConfigDataIsUse = @"isUse";
|
||||
|
||||
@interface ConfigData ()
|
||||
|
||||
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ConfigData
|
||||
|
||||
@synthesize description = _description;
|
||||
@synthesize iosVersionCode = _iosVersionCode;
|
||||
@synthesize forceUpdate = _forceUpdate;
|
||||
@synthesize fbUrl = _fbUrl;
|
||||
@synthesize credentials = _credentials;
|
||||
@synthesize url = _url;
|
||||
@synthesize inviteFee = _inviteFee;
|
||||
@synthesize userId = _userId;
|
||||
@synthesize contactApplyMode = _contactApplyMode;
|
||||
@synthesize versionCode = _versionCode;
|
||||
@synthesize tgUrl = _tgUrl;
|
||||
@synthesize noticeApplyMode = _noticeApplyMode;
|
||||
@synthesize wsUrl = _wsUrl;
|
||||
@synthesize apkUrl = _apkUrl;
|
||||
@synthesize downloadUrl = _downloadUrl;
|
||||
@synthesize isUse = _isUse;
|
||||
|
||||
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict {
|
||||
return [[self alloc] initWithDictionary:dict];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict {
|
||||
self = [super init];
|
||||
// This check serves to make sure that a non-NSDictionary object
|
||||
// passed into the model class doesn't break the parsing.
|
||||
if (self && [dict isKindOfClass:[NSDictionary class]]) {
|
||||
self.description = [self objectOrNilForKey:kConfigDataDescription fromDictionary:dict];
|
||||
self.iosVersionCode = [self objectOrNilForKey:kConfigDataIosVersionCode fromDictionary:dict];
|
||||
self.forceUpdate = [[self objectOrNilForKey:kConfigDataForceUpdate fromDictionary:dict] intValue];
|
||||
self.fbUrl = [self objectOrNilForKey:kConfigDataFbUrl fromDictionary:dict];
|
||||
self.credentials = [self objectOrNilForKey:kConfigDataCredentials fromDictionary:dict];
|
||||
self.url = [self objectOrNilForKey:kConfigDataUrl fromDictionary:dict];
|
||||
self.inviteFee = [[self objectOrNilForKey:kConfigDataInviteFee fromDictionary:dict] doubleValue];
|
||||
self.userId = [[self objectOrNilForKey:kConfigDataUserId fromDictionary:dict] intValue];
|
||||
self.contactApplyMode = [[self objectOrNilForKey:kConfigDataContactApplyMode fromDictionary:dict] intValue];
|
||||
self.versionCode = [self objectOrNilForKey:kConfigDataVersionCode fromDictionary:dict];
|
||||
self.tgUrl = [self objectOrNilForKey:kConfigDataTgUrl fromDictionary:dict];
|
||||
self.noticeApplyMode = [[self objectOrNilForKey:kConfigDataNoticeApplyMode fromDictionary:dict] intValue];
|
||||
self.wsUrl = [self objectOrNilForKey:kConfigDataWsUrl fromDictionary:dict];
|
||||
self.apkUrl = [self objectOrNilForKey:kConfigDataApkUrl fromDictionary:dict];
|
||||
self.downloadUrl = [self objectOrNilForKey:kConfigDataDownloadUrl fromDictionary:dict];
|
||||
self.isUse = [[self objectOrNilForKey:kConfigDataIsUse fromDictionary:dict] intValue];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryRepresentation {
|
||||
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
|
||||
[mutableDict setValue:self.description forKey:kConfigDataDescription];
|
||||
[mutableDict setValue:self.iosVersionCode forKey:kConfigDataIosVersionCode];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.forceUpdate] forKey:kConfigDataForceUpdate];
|
||||
[mutableDict setValue:self.fbUrl forKey:kConfigDataFbUrl];
|
||||
[mutableDict setValue:self.credentials forKey:kConfigDataCredentials];
|
||||
[mutableDict setValue:self.url forKey:kConfigDataUrl];
|
||||
[mutableDict setValue:[NSNumber numberWithDouble:self.inviteFee] forKey:kConfigDataInviteFee];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.userId] forKey:kConfigDataUserId];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.contactApplyMode] forKey:kConfigDataContactApplyMode];
|
||||
[mutableDict setValue:self.versionCode forKey:kConfigDataVersionCode];
|
||||
[mutableDict setValue:self.tgUrl forKey:kConfigDataTgUrl];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.noticeApplyMode] forKey:kConfigDataNoticeApplyMode];
|
||||
[mutableDict setValue:self.wsUrl forKey:kConfigDataWsUrl];
|
||||
[mutableDict setValue:self.apkUrl forKey:kConfigDataApkUrl];
|
||||
[mutableDict setValue:self.downloadUrl forKey:kConfigDataDownloadUrl];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.isUse] forKey:kConfigDataIsUse];
|
||||
return [NSDictionary dictionaryWithDictionary:mutableDict];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
|
||||
}
|
||||
|
||||
#pragma mark - Helper Method
|
||||
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict {
|
||||
id object = [dict objectForKey:aKey];
|
||||
return [object isEqual:[NSNull null]] ? nil : object;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding Methods
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
self.description = [aDecoder decodeObjectForKey:kConfigDataDescription];
|
||||
self.iosVersionCode = [aDecoder decodeObjectForKey:kConfigDataIosVersionCode];
|
||||
self.forceUpdate = [aDecoder decodeIntegerForKey:kConfigDataForceUpdate];
|
||||
self.fbUrl = [aDecoder decodeObjectForKey:kConfigDataFbUrl];
|
||||
self.credentials = [aDecoder decodeObjectForKey:kConfigDataCredentials];
|
||||
self.url = [aDecoder decodeObjectForKey:kConfigDataUrl];
|
||||
self.inviteFee = [aDecoder decodeDoubleForKey:kConfigDataInviteFee];
|
||||
self.userId = [aDecoder decodeIntegerForKey:kConfigDataUserId];
|
||||
self.contactApplyMode = [aDecoder decodeIntegerForKey:kConfigDataContactApplyMode];
|
||||
self.versionCode = [aDecoder decodeObjectForKey:kConfigDataVersionCode];
|
||||
self.tgUrl = [aDecoder decodeObjectForKey:kConfigDataTgUrl];
|
||||
self.noticeApplyMode = [aDecoder decodeIntegerForKey:kConfigDataNoticeApplyMode];
|
||||
self.wsUrl = [aDecoder decodeObjectForKey:kConfigDataWsUrl];
|
||||
self.apkUrl = [aDecoder decodeObjectForKey:kConfigDataApkUrl];
|
||||
self.downloadUrl = [aDecoder decodeObjectForKey:kConfigDataDownloadUrl];
|
||||
self.isUse = [aDecoder decodeIntegerForKey:kConfigDataIsUse];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:_description forKey:kConfigDataDescription];
|
||||
[aCoder encodeObject:_iosVersionCode forKey:kConfigDataIosVersionCode];
|
||||
[aCoder encodeInteger:_forceUpdate forKey:kConfigDataForceUpdate];
|
||||
[aCoder encodeObject:_fbUrl forKey:kConfigDataFbUrl];
|
||||
[aCoder encodeObject:_credentials forKey:kConfigDataCredentials];
|
||||
[aCoder encodeObject:_url forKey:kConfigDataUrl];
|
||||
[aCoder encodeDouble:_inviteFee forKey:kConfigDataInviteFee];
|
||||
[aCoder encodeInteger:_userId forKey:kConfigDataUserId];
|
||||
[aCoder encodeInteger:_contactApplyMode forKey:kConfigDataContactApplyMode];
|
||||
[aCoder encodeObject:_versionCode forKey:kConfigDataVersionCode];
|
||||
[aCoder encodeObject:_tgUrl forKey:kConfigDataTgUrl];
|
||||
[aCoder encodeInteger:_noticeApplyMode forKey:kConfigDataNoticeApplyMode];
|
||||
[aCoder encodeObject:_wsUrl forKey:kConfigDataWsUrl];
|
||||
[aCoder encodeObject:_apkUrl forKey:kConfigDataApkUrl];
|
||||
[aCoder encodeObject:_downloadUrl forKey:kConfigDataDownloadUrl];
|
||||
[aCoder encodeInteger:_isUse forKey:kConfigDataIsUse];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
ConfigData *copy = [[ConfigData alloc] init];
|
||||
if (copy) {
|
||||
copy.description = [self.description copyWithZone:zone];
|
||||
copy.iosVersionCode = [self.iosVersionCode copyWithZone:zone];
|
||||
copy.forceUpdate = self.forceUpdate;
|
||||
copy.fbUrl = [self.fbUrl copyWithZone:zone];
|
||||
copy.credentials = [self.credentials copyWithZone:zone];
|
||||
copy.url = [self.url copyWithZone:zone];
|
||||
copy.inviteFee = self.inviteFee;
|
||||
copy.userId = self.userId;
|
||||
copy.contactApplyMode = self.contactApplyMode;
|
||||
copy.versionCode = [self.versionCode copyWithZone:zone];
|
||||
copy.tgUrl = [self.tgUrl copyWithZone:zone];
|
||||
copy.noticeApplyMode = self.noticeApplyMode;
|
||||
copy.wsUrl = [self.wsUrl copyWithZone:zone];
|
||||
copy.apkUrl = [self.apkUrl copyWithZone:zone];
|
||||
copy.downloadUrl = [self.downloadUrl copyWithZone:zone];
|
||||
copy.isUse = self.isUse;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
||||
19
ShenQi/YYModel/ConfigDataModel.h
Normal file
19
ShenQi/YYModel/ConfigDataModel.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// ConfigDataModel.h
|
||||
//
|
||||
// Created by Yao on 2024/12/19
|
||||
// Copyright (c) 2024 zL. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class ConfigData;
|
||||
@interface ConfigDataModel : NSObject <NSCoding, NSCopying>
|
||||
|
||||
@property (nonatomic, strong) ConfigData *data;
|
||||
@property (nonatomic, assign) NSInteger code;
|
||||
|
||||
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict;
|
||||
- (NSDictionary *)dictionaryRepresentation;
|
||||
|
||||
@end
|
||||
81
ShenQi/YYModel/ConfigDataModel.m
Normal file
81
ShenQi/YYModel/ConfigDataModel.m
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// ConfigDataModel.m
|
||||
//
|
||||
// Created by Yao on 2024/12/19
|
||||
// Copyright (c) 2024 zL. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConfigDataModel.h"
|
||||
#import "ConfigData.h"
|
||||
|
||||
NSString *const kConfigDataModelData = @"data";
|
||||
NSString *const kConfigDataModelCode = @"code";
|
||||
|
||||
@interface ConfigDataModel ()
|
||||
|
||||
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ConfigDataModel
|
||||
|
||||
@synthesize data = _data;
|
||||
@synthesize code = _code;
|
||||
|
||||
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict {
|
||||
return [[self alloc] initWithDictionary:dict];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dict {
|
||||
self = [super init];
|
||||
// This check serves to make sure that a non-NSDictionary object
|
||||
// passed into the model class doesn't break the parsing.
|
||||
if (self && [dict isKindOfClass:[NSDictionary class]]) {
|
||||
self.data = [ConfigData modelObjectWithDictionary:[dict objectForKey:kConfigDataModelData]];
|
||||
self.code = [[self objectOrNilForKey:kConfigDataModelCode fromDictionary:dict] intValue];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryRepresentation {
|
||||
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
|
||||
[mutableDict setValue:[self.data dictionaryRepresentation] forKey:kConfigDataModelData];
|
||||
[mutableDict setValue:[NSNumber numberWithInteger:self.code] forKey:kConfigDataModelCode];
|
||||
return [NSDictionary dictionaryWithDictionary:mutableDict];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
|
||||
}
|
||||
|
||||
#pragma mark - Helper Method
|
||||
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict {
|
||||
id object = [dict objectForKey:aKey];
|
||||
return [object isEqual:[NSNull null]] ? nil : object;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding Methods
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
self.data = [aDecoder decodeObjectForKey:kConfigDataModelData];
|
||||
self.code = [aDecoder decodeIntegerForKey:kConfigDataModelCode];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:_data forKey:kConfigDataModelData];
|
||||
[aCoder encodeInteger:_code forKey:kConfigDataModelCode];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
ConfigDataModel *copy = [[ConfigDataModel alloc] init];
|
||||
if (copy) {
|
||||
copy.data = [self.data copyWithZone:zone];
|
||||
copy.code = self.code;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
||||
430
ShenQi/YYModel/NSObject+YYModel.h
Normal file
430
ShenQi/YYModel/NSObject+YYModel.h
Normal file
@@ -0,0 +1,430 @@
|
||||
//
|
||||
// NSObject+YYModel.h
|
||||
// YYKit <https://github.com/ibireme/YYKit>
|
||||
//
|
||||
// Created by ibireme on 15/5/10.
|
||||
// Copyright (c) 2015 ibireme.
|
||||
//
|
||||
// This source code is licensed under the MIT-style license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Provide some data-model method:
|
||||
|
||||
* Convert json to any object, or convert any object to json.
|
||||
* Set object properties with a key-value dictionary (like KVC).
|
||||
* Implementations of `NSCoding`, `NSCopying`, `-hash` and `-isEqual:`.
|
||||
|
||||
See `YYModel` protocol for custom methods.
|
||||
|
||||
|
||||
Sample Code:
|
||||
|
||||
********************** json convertor *********************
|
||||
@interface YYAuthor : NSObject
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
@property (nonatomic, assign) NSDate *birthday;
|
||||
@end
|
||||
@implementation YYAuthor
|
||||
@end
|
||||
|
||||
@interface YYBook : NSObject
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, assign) NSUInteger pages;
|
||||
@property (nonatomic, strong) YYAuthor *author;
|
||||
@end
|
||||
@implementation YYBook
|
||||
@end
|
||||
|
||||
int main() {
|
||||
// create model from json
|
||||
YYBook *book = [YYBook modelWithJSON:@"{\"name\": \"Harry Potter\", \"pages\": 256, \"author\": {\"name\": \"J.K.Rowling\", \"birthday\": \"1965-07-31\" }}"];
|
||||
|
||||
// convert model to json
|
||||
NSString *json = [book modelToJSONString];
|
||||
// {"author":{"name":"J.K.Rowling","birthday":"1965-07-31T00:00:00+0000"},"name":"Harry Potter","pages":256}
|
||||
}
|
||||
|
||||
********************** Coding/Copying/hash/equal *********************
|
||||
@interface YYShadow :NSObject <NSCoding, NSCopying>
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, assign) CGSize size;
|
||||
@end
|
||||
|
||||
@implementation YYShadow
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder { [self modelEncodeWithCoder:aCoder]; }
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; return [self modelInitWithCoder:aDecoder]; }
|
||||
- (id)copyWithZone:(NSZone *)zone { return [self modelCopy]; }
|
||||
- (NSUInteger)hash { return [self modelHash]; }
|
||||
- (BOOL)isEqual:(id)object { return [self modelIsEqual:object]; }
|
||||
@end
|
||||
|
||||
*/
|
||||
@interface NSObject (YYModel)
|
||||
|
||||
/**
|
||||
Creates and returns a new instance of the receiver from a json.
|
||||
This method is thread-safe.
|
||||
|
||||
@param json A json object in `NSDictionary`, `NSString` or `NSData`.
|
||||
|
||||
@return A new instance created from the json, or nil if an error occurs.
|
||||
*/
|
||||
+ (nullable instancetype)modelWithJSON:(id)json;
|
||||
|
||||
/**
|
||||
Creates and returns a new instance of the receiver from a key-value dictionary.
|
||||
This method is thread-safe.
|
||||
|
||||
@param dictionary A key-value dictionary mapped to the instance's properties.
|
||||
Any invalid key-value pair in dictionary will be ignored.
|
||||
|
||||
@return A new instance created from the dictionary, or nil if an error occurs.
|
||||
|
||||
@discussion The key in `dictionary` will mapped to the reciever's property name,
|
||||
and the value will set to the property. If the value's type does not match the
|
||||
property, this method will try to convert the value based on these rules:
|
||||
|
||||
`NSString` or `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger...
|
||||
`NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd".
|
||||
`NSString` -> NSURL.
|
||||
`NSValue` -> struct or union, such as CGRect, CGSize, ...
|
||||
`NSString` -> SEL, Class.
|
||||
*/
|
||||
+ (nullable instancetype)modelWithDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
/**
|
||||
Set the receiver's properties with a json object.
|
||||
|
||||
@discussion Any invalid data in json will be ignored.
|
||||
|
||||
@param json A json object of `NSDictionary`, `NSString` or `NSData`, mapped to the
|
||||
receiver's properties.
|
||||
|
||||
@return Whether succeed.
|
||||
*/
|
||||
- (BOOL)modelSetWithJSON:(id)json;
|
||||
|
||||
/**
|
||||
Set the receiver's properties with a key-value dictionary.
|
||||
|
||||
@param dic A key-value dictionary mapped to the receiver's properties.
|
||||
Any invalid key-value pair in dictionary will be ignored.
|
||||
|
||||
@discussion The key in `dictionary` will mapped to the reciever's property name,
|
||||
and the value will set to the property. If the value's type doesn't match the
|
||||
property, this method will try to convert the value based on these rules:
|
||||
|
||||
`NSString`, `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger...
|
||||
`NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd".
|
||||
`NSString` -> NSURL.
|
||||
`NSValue` -> struct or union, such as CGRect, CGSize, ...
|
||||
`NSString` -> SEL, Class.
|
||||
|
||||
@return Whether succeed.
|
||||
*/
|
||||
- (BOOL)modelSetWithDictionary:(NSDictionary *)dic;
|
||||
|
||||
/**
|
||||
Generate a json object from the receiver's properties.
|
||||
|
||||
@return A json object in `NSDictionary` or `NSArray`, or nil if an error occurs.
|
||||
See [NSJSONSerialization isValidJSONObject] for more information.
|
||||
|
||||
@discussion Any of the invalid property is ignored.
|
||||
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it just convert
|
||||
the inner object to json object.
|
||||
*/
|
||||
- (nullable id)modelToJSONObject;
|
||||
|
||||
/**
|
||||
Generate a json string's data from the receiver's properties.
|
||||
|
||||
@return A json string's data, or nil if an error occurs.
|
||||
|
||||
@discussion Any of the invalid property is ignored.
|
||||
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the
|
||||
inner object to json string.
|
||||
*/
|
||||
- (nullable NSData *)modelToJSONData;
|
||||
|
||||
/**
|
||||
Generate a json string from the receiver's properties.
|
||||
|
||||
@return A json string, or nil if an error occurs.
|
||||
|
||||
@discussion Any of the invalid property is ignored.
|
||||
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the
|
||||
inner object to json string.
|
||||
*/
|
||||
- (nullable NSString *)modelToJSONString;
|
||||
|
||||
/**
|
||||
Copy a instance with the receiver's properties.
|
||||
|
||||
@return A copied instance, or nil if an error occurs.
|
||||
*/
|
||||
- (nullable id)modelCopy;
|
||||
|
||||
/**
|
||||
Encode the receiver's properties to a coder.
|
||||
|
||||
@param aCoder An archiver object.
|
||||
*/
|
||||
- (void)modelEncodeWithCoder:(NSCoder *)aCoder;
|
||||
|
||||
/**
|
||||
Decode the receiver's properties from a decoder.
|
||||
|
||||
@param aDecoder An archiver object.
|
||||
|
||||
@return self
|
||||
*/
|
||||
- (id)modelInitWithCoder:(NSCoder *)aDecoder;
|
||||
|
||||
/**
|
||||
Get a hash code with the receiver's properties.
|
||||
|
||||
@return Hash code.
|
||||
*/
|
||||
- (NSUInteger)modelHash;
|
||||
|
||||
/**
|
||||
Compares the receiver with another object for equality, based on properties.
|
||||
|
||||
@param model Another object.
|
||||
|
||||
@return `YES` if the reciever is equal to the object, otherwise `NO`.
|
||||
*/
|
||||
- (BOOL)modelIsEqual:(id)model;
|
||||
|
||||
/**
|
||||
Description method for debugging purposes based on properties.
|
||||
|
||||
@return A string that describes the contents of the receiver.
|
||||
*/
|
||||
- (NSString *)modelDescription;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Provide some data-model method for NSArray.
|
||||
*/
|
||||
@interface NSArray (YYModel)
|
||||
|
||||
/**
|
||||
Creates and returns an array from a json-array.
|
||||
This method is thread-safe.
|
||||
|
||||
@param cls The instance's class in array.
|
||||
@param json A json array of `NSArray`, `NSString` or `NSData`.
|
||||
Example: [{"name","Mary"},{name:"Joe"}]
|
||||
|
||||
@return A array, or nil if an error occurs.
|
||||
*/
|
||||
+ (nullable NSArray *)modelArrayWithClass:(Class)cls json:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Provide some data-model method for NSDictionary.
|
||||
*/
|
||||
@interface NSDictionary (YYModel)
|
||||
|
||||
/**
|
||||
Creates and returns a dictionary from a json.
|
||||
This method is thread-safe.
|
||||
|
||||
@param cls The value instance's class in dictionary.
|
||||
@param json A json dictionary of `NSDictionary`, `NSString` or `NSData`.
|
||||
Example: {"user1":{"name","Mary"}, "user2": {name:"Joe"}}
|
||||
|
||||
@return A dictionary, or nil if an error occurs.
|
||||
*/
|
||||
+ (nullable NSDictionary *)modelDictionaryWithClass:(Class)cls json:(id)json;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
If the default model transform does not fit to your model class, implement one or
|
||||
more method in this protocol to change the default key-value transform process.
|
||||
There's no need to add '<YYModel>' to your class header.
|
||||
*/
|
||||
@protocol YYModel <NSObject>
|
||||
@optional
|
||||
|
||||
/**
|
||||
Custom property mapper.
|
||||
|
||||
@discussion If the key in JSON/Dictionary does not match to the model's property name,
|
||||
implements this method and returns the additional mapper.
|
||||
|
||||
Example:
|
||||
|
||||
json:
|
||||
{
|
||||
"n":"Harry Pottery",
|
||||
"p": 256,
|
||||
"ext" : {
|
||||
"desc" : "A book written by J.K.Rowling."
|
||||
},
|
||||
"ID" : 100010
|
||||
}
|
||||
|
||||
model:
|
||||
@interface YYBook : NSObject
|
||||
@property NSString *name;
|
||||
@property NSInteger page;
|
||||
@property NSString *desc;
|
||||
@property NSString *bookID;
|
||||
@end
|
||||
|
||||
@implementation YYBook
|
||||
+ (NSDictionary *)modelCustomPropertyMapper {
|
||||
return @{@"name" : @"n",
|
||||
@"page" : @"p",
|
||||
@"desc" : @"ext.desc",
|
||||
@"bookID": @[@"id", @"ID", @"book_id"]};
|
||||
}
|
||||
@end
|
||||
|
||||
@return A custom mapper for properties.
|
||||
*/
|
||||
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
|
||||
|
||||
/**
|
||||
The generic class mapper for container properties.
|
||||
|
||||
@discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
|
||||
implements this method and returns a property->class mapper, tells which kind of
|
||||
object will be add to the array/set/dictionary.
|
||||
|
||||
Example:
|
||||
@class YYShadow, YYBorder, YYAttachment;
|
||||
|
||||
@interface YYAttributes
|
||||
@property NSString *name;
|
||||
@property NSArray *shadows;
|
||||
@property NSSet *borders;
|
||||
@property NSDictionary *attachments;
|
||||
@end
|
||||
|
||||
@implementation YYAttributes
|
||||
+ (NSDictionary *)modelContainerPropertyGenericClass {
|
||||
return @{@"shadows" : [YYShadow class],
|
||||
@"borders" : YYBorder.class,
|
||||
@"attachments" : @"YYAttachment" };
|
||||
}
|
||||
@end
|
||||
|
||||
@return A class mapper.
|
||||
*/
|
||||
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
|
||||
|
||||
/**
|
||||
If you need to create instances of different classes during json->object transform,
|
||||
use the method to choose custom class based on dictionary data.
|
||||
|
||||
@discussion If the model implements this method, it will be called to determine resulting class
|
||||
during `+modelWithJSON:`, `+modelWithDictionary:`, conveting object of properties of parent objects
|
||||
(both singular and containers via `+modelContainerPropertyGenericClass`).
|
||||
|
||||
Example:
|
||||
@class YYCircle, YYRectangle, YYLine;
|
||||
|
||||
@implementation YYShape
|
||||
|
||||
+ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
|
||||
if (dictionary[@"radius"] != nil) {
|
||||
return [YYCircle class];
|
||||
} else if (dictionary[@"width"] != nil) {
|
||||
return [YYRectangle class];
|
||||
} else if (dictionary[@"y2"] != nil) {
|
||||
return [YYLine class];
|
||||
} else {
|
||||
return [self class];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@param dictionary The json/kv dictionary.
|
||||
|
||||
@return Class to create from this dictionary, `nil` to use current class.
|
||||
|
||||
*/
|
||||
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
/**
|
||||
All the properties in blacklist will be ignored in model transform process.
|
||||
Returns nil to ignore this feature.
|
||||
|
||||
@return An array of property's name.
|
||||
*/
|
||||
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
|
||||
|
||||
/**
|
||||
If a property is not in the whitelist, it will be ignored in model transform process.
|
||||
Returns nil to ignore this feature.
|
||||
|
||||
@return An array of property's name.
|
||||
*/
|
||||
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
|
||||
|
||||
/**
|
||||
This method's behavior is similar to `- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;`,
|
||||
but be called before the model transform.
|
||||
|
||||
@discussion If the model implements this method, it will be called before
|
||||
`+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`.
|
||||
If this method returns nil, the transform process will ignore this model.
|
||||
|
||||
@param dic The json/kv dictionary.
|
||||
|
||||
@return Returns the modified dictionary, or nil to ignore this model.
|
||||
*/
|
||||
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
|
||||
|
||||
/**
|
||||
If the default json-to-model transform does not fit to your model object, implement
|
||||
this method to do additional process. You can also use this method to validate the
|
||||
model's properties.
|
||||
|
||||
@discussion If the model implements this method, it will be called at the end of
|
||||
`+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`.
|
||||
If this method returns NO, the transform process will ignore this model.
|
||||
|
||||
@param dic The json/kv dictionary.
|
||||
|
||||
@return Returns YES if the model is valid, or NO to ignore this model.
|
||||
*/
|
||||
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
|
||||
|
||||
/**
|
||||
If the default model-to-json transform does not fit to your model class, implement
|
||||
this method to do additional process. You can also use this method to validate the
|
||||
json dictionary.
|
||||
|
||||
@discussion If the model implements this method, it will be called at the end of
|
||||
`-modelToJSONObject` and `-modelToJSONString`.
|
||||
If this method returns NO, the transform process will ignore this json dictionary.
|
||||
|
||||
@param dic The json dictionary.
|
||||
|
||||
@return Returns YES if the model is valid, or NO to ignore this model.
|
||||
*/
|
||||
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
1838
ShenQi/YYModel/NSObject+YYModel.m
Normal file
1838
ShenQi/YYModel/NSObject+YYModel.m
Normal file
@@ -0,0 +1,1838 @@
|
||||
//
|
||||
// NSObject+YYModel.m
|
||||
// YYKit <https://github.com/ibireme/YYKit>
|
||||
//
|
||||
// Created by ibireme on 15/5/10.
|
||||
// Copyright (c) 2015 ibireme.
|
||||
//
|
||||
// This source code is licensed under the MIT-style license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
//
|
||||
|
||||
#import "NSObject+YYModel.h"
|
||||
#import "YYClassInfo.h"
|
||||
#import <objc/message.h>
|
||||
|
||||
#define force_inline __inline__ __attribute__((always_inline))
|
||||
|
||||
/// Foundation Class Type
|
||||
typedef NS_ENUM (NSUInteger, YYEncodingNSType) {
|
||||
YYEncodingTypeNSUnknown = 0,
|
||||
YYEncodingTypeNSString,
|
||||
YYEncodingTypeNSMutableString,
|
||||
YYEncodingTypeNSValue,
|
||||
YYEncodingTypeNSNumber,
|
||||
YYEncodingTypeNSDecimalNumber,
|
||||
YYEncodingTypeNSData,
|
||||
YYEncodingTypeNSMutableData,
|
||||
YYEncodingTypeNSDate,
|
||||
YYEncodingTypeNSURL,
|
||||
YYEncodingTypeNSArray,
|
||||
YYEncodingTypeNSMutableArray,
|
||||
YYEncodingTypeNSDictionary,
|
||||
YYEncodingTypeNSMutableDictionary,
|
||||
YYEncodingTypeNSSet,
|
||||
YYEncodingTypeNSMutableSet,
|
||||
};
|
||||
|
||||
/// Get the Foundation class type from property info.
|
||||
static force_inline YYEncodingNSType YYClassGetNSType(Class cls) {
|
||||
if (!cls) return YYEncodingTypeNSUnknown;
|
||||
if ([cls isSubclassOfClass:[NSMutableString class]]) return YYEncodingTypeNSMutableString;
|
||||
if ([cls isSubclassOfClass:[NSString class]]) return YYEncodingTypeNSString;
|
||||
if ([cls isSubclassOfClass:[NSDecimalNumber class]]) return YYEncodingTypeNSDecimalNumber;
|
||||
if ([cls isSubclassOfClass:[NSNumber class]]) return YYEncodingTypeNSNumber;
|
||||
if ([cls isSubclassOfClass:[NSValue class]]) return YYEncodingTypeNSValue;
|
||||
if ([cls isSubclassOfClass:[NSMutableData class]]) return YYEncodingTypeNSMutableData;
|
||||
if ([cls isSubclassOfClass:[NSData class]]) return YYEncodingTypeNSData;
|
||||
if ([cls isSubclassOfClass:[NSDate class]]) return YYEncodingTypeNSDate;
|
||||
if ([cls isSubclassOfClass:[NSURL class]]) return YYEncodingTypeNSURL;
|
||||
if ([cls isSubclassOfClass:[NSMutableArray class]]) return YYEncodingTypeNSMutableArray;
|
||||
if ([cls isSubclassOfClass:[NSArray class]]) return YYEncodingTypeNSArray;
|
||||
if ([cls isSubclassOfClass:[NSMutableDictionary class]]) return YYEncodingTypeNSMutableDictionary;
|
||||
if ([cls isSubclassOfClass:[NSDictionary class]]) return YYEncodingTypeNSDictionary;
|
||||
if ([cls isSubclassOfClass:[NSMutableSet class]]) return YYEncodingTypeNSMutableSet;
|
||||
if ([cls isSubclassOfClass:[NSSet class]]) return YYEncodingTypeNSSet;
|
||||
return YYEncodingTypeNSUnknown;
|
||||
}
|
||||
|
||||
/// Whether the type is c number.
|
||||
static force_inline BOOL YYEncodingTypeIsCNumber(YYEncodingType type) {
|
||||
switch (type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeBool:
|
||||
case YYEncodingTypeInt8:
|
||||
case YYEncodingTypeUInt8:
|
||||
case YYEncodingTypeInt16:
|
||||
case YYEncodingTypeUInt16:
|
||||
case YYEncodingTypeInt32:
|
||||
case YYEncodingTypeUInt32:
|
||||
case YYEncodingTypeInt64:
|
||||
case YYEncodingTypeUInt64:
|
||||
case YYEncodingTypeFloat:
|
||||
case YYEncodingTypeDouble:
|
||||
case YYEncodingTypeLongDouble: return YES;
|
||||
default: return NO;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a number value from 'id'.
|
||||
static force_inline NSNumber *YYNSNumberCreateFromID(__unsafe_unretained id value) {
|
||||
static NSCharacterSet *dot;
|
||||
static NSDictionary *dic;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
|
||||
dic = @{@"TRUE" : @(YES),
|
||||
@"True" : @(YES),
|
||||
@"true" : @(YES),
|
||||
@"FALSE" : @(NO),
|
||||
@"False" : @(NO),
|
||||
@"false" : @(NO),
|
||||
@"YES" : @(YES),
|
||||
@"Yes" : @(YES),
|
||||
@"yes" : @(YES),
|
||||
@"NO" : @(NO),
|
||||
@"No" : @(NO),
|
||||
@"no" : @(NO),
|
||||
@"NIL" : (id)kCFNull,
|
||||
@"Nil" : (id)kCFNull,
|
||||
@"nil" : (id)kCFNull,
|
||||
@"NULL" : (id)kCFNull,
|
||||
@"Null" : (id)kCFNull,
|
||||
@"null" : (id)kCFNull,
|
||||
@"(NULL)" : (id)kCFNull,
|
||||
@"(Null)" : (id)kCFNull,
|
||||
@"(null)" : (id)kCFNull,
|
||||
@"<NULL>" : (id)kCFNull,
|
||||
@"<Null>" : (id)kCFNull,
|
||||
@"<null>" : (id)kCFNull};
|
||||
});
|
||||
|
||||
if (!value || value == (id)kCFNull) return nil;
|
||||
if ([value isKindOfClass:[NSNumber class]]) return value;
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
NSNumber *num = dic[value];
|
||||
if (num) {
|
||||
if (num == (id)kCFNull) return nil;
|
||||
return num;
|
||||
}
|
||||
if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
|
||||
const char *cstring = ((NSString *)value).UTF8String;
|
||||
if (!cstring) return nil;
|
||||
double num = atof(cstring);
|
||||
if (isnan(num) || isinf(num)) return nil;
|
||||
return @(num);
|
||||
} else {
|
||||
const char *cstring = ((NSString *)value).UTF8String;
|
||||
if (!cstring) return nil;
|
||||
return @(atoll(cstring));
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
/// Parse string to date.
|
||||
static force_inline NSDate *YYNSDateFromString(__unsafe_unretained NSString *string) {
|
||||
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
|
||||
#define kParserNum 34
|
||||
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
{
|
||||
/*
|
||||
2014-01-20 // Google
|
||||
*/
|
||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
||||
formatter.dateFormat = @"yyyy-MM-dd";
|
||||
blocks[10] = ^(NSString *string) { return [formatter dateFromString:string]; };
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
2014-01-20 12:24:48
|
||||
2014-01-20T12:24:48 // Google
|
||||
2014-01-20 12:24:48.000
|
||||
2014-01-20T12:24:48.000
|
||||
*/
|
||||
NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
|
||||
formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
||||
formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
|
||||
|
||||
NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
|
||||
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
||||
formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
|
||||
formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
||||
formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
|
||||
|
||||
NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
|
||||
formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
||||
formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
|
||||
|
||||
blocks[19] = ^(NSString *string) {
|
||||
if ([string characterAtIndex:10] == 'T') {
|
||||
return [formatter1 dateFromString:string];
|
||||
} else {
|
||||
return [formatter2 dateFromString:string];
|
||||
}
|
||||
};
|
||||
|
||||
blocks[23] = ^(NSString *string) {
|
||||
if ([string characterAtIndex:10] == 'T') {
|
||||
return [formatter3 dateFromString:string];
|
||||
} else {
|
||||
return [formatter4 dateFromString:string];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
2014-01-20T12:24:48Z // Github, Apple
|
||||
2014-01-20T12:24:48+0800 // Facebook
|
||||
2014-01-20T12:24:48+12:00 // Google
|
||||
2014-01-20T12:24:48.000Z
|
||||
2014-01-20T12:24:48.000+0800
|
||||
2014-01-20T12:24:48.000+12:00
|
||||
*/
|
||||
NSDateFormatter *formatter = [NSDateFormatter new];
|
||||
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
|
||||
NSDateFormatter *formatter2 = [NSDateFormatter new];
|
||||
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
|
||||
blocks[20] = ^(NSString *string) { return [formatter dateFromString:string]; };
|
||||
blocks[24] = ^(NSString *string) { return [formatter dateFromString:string]?: [formatter2 dateFromString:string]; };
|
||||
blocks[25] = ^(NSString *string) { return [formatter dateFromString:string]; };
|
||||
blocks[28] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
|
||||
blocks[29] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
|
||||
}
|
||||
|
||||
{
|
||||
/*
|
||||
Fri Sep 04 00:12:21 +0800 2015 // Weibo, Twitter
|
||||
Fri Sep 04 00:12:21.000 +0800 2015
|
||||
*/
|
||||
NSDateFormatter *formatter = [NSDateFormatter new];
|
||||
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
|
||||
|
||||
NSDateFormatter *formatter2 = [NSDateFormatter new];
|
||||
formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
|
||||
|
||||
blocks[30] = ^(NSString *string) { return [formatter dateFromString:string]; };
|
||||
blocks[34] = ^(NSString *string) { return [formatter2 dateFromString:string]; };
|
||||
}
|
||||
});
|
||||
if (!string) return nil;
|
||||
if (string.length > kParserNum) return nil;
|
||||
YYNSDateParseBlock parser = blocks[string.length];
|
||||
if (!parser) return nil;
|
||||
return parser(string);
|
||||
#undef kParserNum
|
||||
}
|
||||
|
||||
|
||||
/// Get the 'NSBlock' class.
|
||||
static force_inline Class YYNSBlockClass() {
|
||||
static Class cls;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
void (^block)(void) = ^{};
|
||||
cls = ((NSObject *)block).class;
|
||||
while (class_getSuperclass(cls) != [NSObject class]) {
|
||||
cls = class_getSuperclass(cls);
|
||||
}
|
||||
});
|
||||
return cls; // current is "NSBlock"
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Get the ISO date formatter.
|
||||
|
||||
ISO8601 format example:
|
||||
2010-07-09T16:13:30+12:00
|
||||
2011-01-11T11:11:11+0000
|
||||
2011-01-26T19:06:43Z
|
||||
|
||||
length: 20/24/25
|
||||
*/
|
||||
static force_inline NSDateFormatter *YYISODateFormatter() {
|
||||
static NSDateFormatter *formatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
formatter = [[NSDateFormatter alloc] init];
|
||||
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
});
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/// Get the value with key paths from dictionary
|
||||
/// The dic should be NSDictionary, and the keyPath should not be nil.
|
||||
static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
|
||||
id value = nil;
|
||||
for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
|
||||
value = dic[keyPaths[i]];
|
||||
if (i + 1 < max) {
|
||||
if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
dic = value;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Get the value with multi key (or key path) from dictionary
|
||||
/// The dic should be NSDictionary
|
||||
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
|
||||
id value = nil;
|
||||
for (NSString *key in multiKeys) {
|
||||
if ([key isKindOfClass:[NSString class]]) {
|
||||
value = dic[key];
|
||||
if (value) break;
|
||||
} else {
|
||||
value = YYValueForKeyPath(dic, (NSArray *)key);
|
||||
if (value) break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// A property info in object model.
|
||||
@interface _YYModelPropertyMeta : NSObject {
|
||||
@package
|
||||
NSString *_name; ///< property's name
|
||||
YYEncodingType _type; ///< property's type
|
||||
YYEncodingNSType _nsType; ///< property's Foundation type
|
||||
BOOL _isCNumber; ///< is c number type
|
||||
Class _cls; ///< property's class, or nil
|
||||
Class _genericCls; ///< container's generic class, or nil if threr's no generic class
|
||||
SEL _getter; ///< getter, or nil if the instances cannot respond
|
||||
SEL _setter; ///< setter, or nil if the instances cannot respond
|
||||
BOOL _isKVCCompatible; ///< YES if it can access with key-value coding
|
||||
BOOL _isStructAvailableForKeyedArchiver; ///< YES if the struct can encoded with keyed archiver/unarchiver
|
||||
BOOL _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
|
||||
|
||||
/*
|
||||
property->key: _mappedToKey:key _mappedToKeyPath:nil _mappedToKeyArray:nil
|
||||
property->keyPath: _mappedToKey:keyPath _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
|
||||
property->keys: _mappedToKey:keys[0] _mappedToKeyPath:nil/keyPath _mappedToKeyArray:keys(array)
|
||||
*/
|
||||
NSString *_mappedToKey; ///< the key mapped to
|
||||
NSArray *_mappedToKeyPath; ///< the key path mapped to (nil if the name is not key path)
|
||||
NSArray *_mappedToKeyArray; ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
|
||||
YYClassPropertyInfo *_info; ///< property's info
|
||||
_YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation _YYModelPropertyMeta
|
||||
+ (instancetype)metaWithClassInfo:(YYClassInfo *)classInfo propertyInfo:(YYClassPropertyInfo *)propertyInfo generic:(Class)generic {
|
||||
|
||||
// support pseudo generic class with protocol name
|
||||
if (!generic && propertyInfo.protocols) {
|
||||
for (NSString *protocol in propertyInfo.protocols) {
|
||||
Class cls = objc_getClass(protocol.UTF8String);
|
||||
if (cls) {
|
||||
generic = cls;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_YYModelPropertyMeta *meta = [self new];
|
||||
meta->_name = propertyInfo.name;
|
||||
meta->_type = propertyInfo.type;
|
||||
meta->_info = propertyInfo;
|
||||
meta->_genericCls = generic;
|
||||
|
||||
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeObject) {
|
||||
meta->_nsType = YYClassGetNSType(propertyInfo.cls);
|
||||
} else {
|
||||
meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
|
||||
}
|
||||
if ((meta->_type & YYEncodingTypeMask) == YYEncodingTypeStruct) {
|
||||
/*
|
||||
It seems that NSKeyedUnarchiver cannot decode NSValue except these structs:
|
||||
*/
|
||||
static NSSet *types = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableSet *set = [NSMutableSet new];
|
||||
// 32 bit
|
||||
[set addObject:@"{CGSize=ff}"];
|
||||
[set addObject:@"{CGPoint=ff}"];
|
||||
[set addObject:@"{CGRect={CGPoint=ff}{CGSize=ff}}"];
|
||||
[set addObject:@"{CGAffineTransform=ffffff}"];
|
||||
[set addObject:@"{UIEdgeInsets=ffff}"];
|
||||
[set addObject:@"{UIOffset=ff}"];
|
||||
// 64 bit
|
||||
[set addObject:@"{CGSize=dd}"];
|
||||
[set addObject:@"{CGPoint=dd}"];
|
||||
[set addObject:@"{CGRect={CGPoint=dd}{CGSize=dd}}"];
|
||||
[set addObject:@"{CGAffineTransform=dddddd}"];
|
||||
[set addObject:@"{UIEdgeInsets=dddd}"];
|
||||
[set addObject:@"{UIOffset=dd}"];
|
||||
types = set;
|
||||
});
|
||||
if ([types containsObject:propertyInfo.typeEncoding]) {
|
||||
meta->_isStructAvailableForKeyedArchiver = YES;
|
||||
}
|
||||
}
|
||||
meta->_cls = propertyInfo.cls;
|
||||
|
||||
if (generic) {
|
||||
meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
|
||||
} else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
|
||||
meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
|
||||
}
|
||||
|
||||
if (propertyInfo.getter) {
|
||||
if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
|
||||
meta->_getter = propertyInfo.getter;
|
||||
}
|
||||
}
|
||||
if (propertyInfo.setter) {
|
||||
if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
|
||||
meta->_setter = propertyInfo.setter;
|
||||
}
|
||||
}
|
||||
|
||||
if (meta->_getter && meta->_setter) {
|
||||
/*
|
||||
KVC invalid type:
|
||||
long double
|
||||
pointer (such as SEL/CoreFoundation object)
|
||||
*/
|
||||
switch (meta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeBool:
|
||||
case YYEncodingTypeInt8:
|
||||
case YYEncodingTypeUInt8:
|
||||
case YYEncodingTypeInt16:
|
||||
case YYEncodingTypeUInt16:
|
||||
case YYEncodingTypeInt32:
|
||||
case YYEncodingTypeUInt32:
|
||||
case YYEncodingTypeInt64:
|
||||
case YYEncodingTypeUInt64:
|
||||
case YYEncodingTypeFloat:
|
||||
case YYEncodingTypeDouble:
|
||||
case YYEncodingTypeObject:
|
||||
case YYEncodingTypeClass:
|
||||
case YYEncodingTypeBlock:
|
||||
case YYEncodingTypeStruct:
|
||||
case YYEncodingTypeUnion: {
|
||||
meta->_isKVCCompatible = YES;
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
/// A class info in object model.
|
||||
@interface _YYModelMeta : NSObject {
|
||||
@package
|
||||
YYClassInfo *_classInfo;
|
||||
/// Key:mapped key and key path, Value:_YYModelPropertyMeta.
|
||||
NSDictionary *_mapper;
|
||||
/// Array<_YYModelPropertyMeta>, all property meta of this model.
|
||||
NSArray *_allPropertyMetas;
|
||||
/// Array<_YYModelPropertyMeta>, property meta which is mapped to a key path.
|
||||
NSArray *_keyPathPropertyMetas;
|
||||
/// Array<_YYModelPropertyMeta>, property meta which is mapped to multi keys.
|
||||
NSArray *_multiKeysPropertyMetas;
|
||||
/// The number of mapped key (and key path), same to _mapper.count.
|
||||
NSUInteger _keyMappedCount;
|
||||
/// Model class type.
|
||||
YYEncodingNSType _nsType;
|
||||
|
||||
BOOL _hasCustomWillTransformFromDictionary;
|
||||
BOOL _hasCustomTransformFromDictionary;
|
||||
BOOL _hasCustomTransformToDictionary;
|
||||
BOOL _hasCustomClassFromDictionary;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation _YYModelMeta
|
||||
- (instancetype)initWithClass:(Class)cls {
|
||||
YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:cls];
|
||||
if (!classInfo) return nil;
|
||||
self = [super init];
|
||||
|
||||
// Get black list
|
||||
NSSet *blacklist = nil;
|
||||
if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
|
||||
NSArray *properties = [(id<YYModel>)cls modelPropertyBlacklist];
|
||||
if (properties) {
|
||||
blacklist = [NSSet setWithArray:properties];
|
||||
}
|
||||
}
|
||||
|
||||
// Get white list
|
||||
NSSet *whitelist = nil;
|
||||
if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
|
||||
NSArray *properties = [(id<YYModel>)cls modelPropertyWhitelist];
|
||||
if (properties) {
|
||||
whitelist = [NSSet setWithArray:properties];
|
||||
}
|
||||
}
|
||||
|
||||
// Get container property's generic class
|
||||
NSDictionary *genericMapper = nil;
|
||||
if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
|
||||
genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
|
||||
if (genericMapper) {
|
||||
NSMutableDictionary *tmp = [NSMutableDictionary new];
|
||||
[genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
if (![key isKindOfClass:[NSString class]]) return;
|
||||
Class meta = object_getClass(obj);
|
||||
if (!meta) return;
|
||||
if (class_isMetaClass(meta)) {
|
||||
tmp[key] = obj;
|
||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||
Class cls = NSClassFromString(obj);
|
||||
if (cls) {
|
||||
tmp[key] = cls;
|
||||
}
|
||||
}
|
||||
}];
|
||||
genericMapper = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Create all property metas.
|
||||
NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
|
||||
YYClassInfo *curClassInfo = classInfo;
|
||||
while (curClassInfo && curClassInfo.superCls != nil) { // recursive parse super class, but ignore root class (NSObject/NSProxy)
|
||||
for (YYClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
|
||||
if (!propertyInfo.name) continue;
|
||||
if (blacklist && [blacklist containsObject:propertyInfo.name]) continue;
|
||||
if (whitelist && ![whitelist containsObject:propertyInfo.name]) continue;
|
||||
_YYModelPropertyMeta *meta = [_YYModelPropertyMeta metaWithClassInfo:classInfo
|
||||
propertyInfo:propertyInfo
|
||||
generic:genericMapper[propertyInfo.name]];
|
||||
if (!meta || !meta->_name) continue;
|
||||
if (!meta->_getter || !meta->_setter) continue;
|
||||
if (allPropertyMetas[meta->_name]) continue;
|
||||
allPropertyMetas[meta->_name] = meta;
|
||||
}
|
||||
curClassInfo = curClassInfo.superClassInfo;
|
||||
}
|
||||
if (allPropertyMetas.count) _allPropertyMetas = allPropertyMetas.allValues.copy;
|
||||
|
||||
// create mapper
|
||||
NSMutableDictionary *mapper = [NSMutableDictionary new];
|
||||
NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
|
||||
NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
|
||||
|
||||
if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
|
||||
NSDictionary *customMapper = [(id <YYModel>)cls modelCustomPropertyMapper];
|
||||
[customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL *stop) {
|
||||
_YYModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
|
||||
if (!propertyMeta) return;
|
||||
[allPropertyMetas removeObjectForKey:propertyName];
|
||||
|
||||
if ([mappedToKey isKindOfClass:[NSString class]]) {
|
||||
if (mappedToKey.length == 0) return;
|
||||
|
||||
propertyMeta->_mappedToKey = mappedToKey;
|
||||
NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
|
||||
for (NSString *onePath in keyPath) {
|
||||
if (onePath.length == 0) {
|
||||
NSMutableArray *tmp = keyPath.mutableCopy;
|
||||
[tmp removeObject:@""];
|
||||
keyPath = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keyPath.count > 1) {
|
||||
propertyMeta->_mappedToKeyPath = keyPath;
|
||||
[keyPathPropertyMetas addObject:propertyMeta];
|
||||
}
|
||||
propertyMeta->_next = mapper[mappedToKey] ?: nil;
|
||||
mapper[mappedToKey] = propertyMeta;
|
||||
|
||||
} else if ([mappedToKey isKindOfClass:[NSArray class]]) {
|
||||
|
||||
NSMutableArray *mappedToKeyArray = [NSMutableArray new];
|
||||
for (NSString *oneKey in ((NSArray *)mappedToKey)) {
|
||||
if (![oneKey isKindOfClass:[NSString class]]) continue;
|
||||
if (oneKey.length == 0) continue;
|
||||
|
||||
NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
|
||||
if (keyPath.count > 1) {
|
||||
[mappedToKeyArray addObject:keyPath];
|
||||
} else {
|
||||
[mappedToKeyArray addObject:oneKey];
|
||||
}
|
||||
|
||||
if (!propertyMeta->_mappedToKey) {
|
||||
propertyMeta->_mappedToKey = oneKey;
|
||||
propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
|
||||
}
|
||||
}
|
||||
if (!propertyMeta->_mappedToKey) return;
|
||||
|
||||
propertyMeta->_mappedToKeyArray = mappedToKeyArray;
|
||||
[multiKeysPropertyMetas addObject:propertyMeta];
|
||||
|
||||
propertyMeta->_next = mapper[mappedToKey] ?: nil;
|
||||
mapper[mappedToKey] = propertyMeta;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
[allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
|
||||
propertyMeta->_mappedToKey = name;
|
||||
propertyMeta->_next = mapper[name] ?: nil;
|
||||
mapper[name] = propertyMeta;
|
||||
}];
|
||||
|
||||
if (mapper.count) _mapper = mapper;
|
||||
if (keyPathPropertyMetas) _keyPathPropertyMetas = keyPathPropertyMetas;
|
||||
if (multiKeysPropertyMetas) _multiKeysPropertyMetas = multiKeysPropertyMetas;
|
||||
|
||||
_classInfo = classInfo;
|
||||
_keyMappedCount = _allPropertyMetas.count;
|
||||
_nsType = YYClassGetNSType(cls);
|
||||
_hasCustomWillTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)]);
|
||||
_hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
|
||||
_hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
|
||||
_hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Returns the cached model class meta
|
||||
+ (instancetype)metaWithClass:(Class)cls {
|
||||
if (!cls) return nil;
|
||||
static CFMutableDictionaryRef cache;
|
||||
static dispatch_once_t onceToken;
|
||||
static dispatch_semaphore_t lock;
|
||||
dispatch_once(&onceToken, ^{
|
||||
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
lock = dispatch_semaphore_create(1);
|
||||
});
|
||||
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
|
||||
dispatch_semaphore_signal(lock);
|
||||
if (!meta || meta->_classInfo.needUpdate) {
|
||||
meta = [[_YYModelMeta alloc] initWithClass:cls];
|
||||
if (meta) {
|
||||
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
|
||||
dispatch_semaphore_signal(lock);
|
||||
}
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Get number from property.
|
||||
@discussion Caller should hold strong reference to the parameters before this function returns.
|
||||
@param model Should not be nil.
|
||||
@param meta Should not be nil, meta.isCNumber should be YES, meta.getter should not be nil.
|
||||
@return A number object, or nil if failed.
|
||||
*/
|
||||
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model,
|
||||
__unsafe_unretained _YYModelPropertyMeta *meta) {
|
||||
switch (meta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeBool: {
|
||||
return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeInt8: {
|
||||
return @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeUInt8: {
|
||||
return @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeInt16: {
|
||||
return @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeUInt16: {
|
||||
return @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeInt32: {
|
||||
return @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeUInt32: {
|
||||
return @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeInt64: {
|
||||
return @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeUInt64: {
|
||||
return @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
|
||||
}
|
||||
case YYEncodingTypeFloat: {
|
||||
float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
|
||||
if (isnan(num) || isinf(num)) return nil;
|
||||
return @(num);
|
||||
}
|
||||
case YYEncodingTypeDouble: {
|
||||
double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
|
||||
if (isnan(num) || isinf(num)) return nil;
|
||||
return @(num);
|
||||
}
|
||||
case YYEncodingTypeLongDouble: {
|
||||
double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
|
||||
if (isnan(num) || isinf(num)) return nil;
|
||||
return @(num);
|
||||
}
|
||||
default: return nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Set number to property.
|
||||
@discussion Caller should hold strong reference to the parameters before this function returns.
|
||||
@param model Should not be nil.
|
||||
@param num Can be nil.
|
||||
@param meta Should not be nil, meta.isCNumber should be YES, meta.setter should not be nil.
|
||||
*/
|
||||
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
|
||||
__unsafe_unretained NSNumber *num,
|
||||
__unsafe_unretained _YYModelPropertyMeta *meta) {
|
||||
switch (meta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeBool: {
|
||||
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
|
||||
} break;
|
||||
case YYEncodingTypeInt8: {
|
||||
((void (*)(id, SEL, int8_t))(void *) objc_msgSend)((id)model, meta->_setter, (int8_t)num.charValue);
|
||||
} break;
|
||||
case YYEncodingTypeUInt8: {
|
||||
((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint8_t)num.unsignedCharValue);
|
||||
} break;
|
||||
case YYEncodingTypeInt16: {
|
||||
((void (*)(id, SEL, int16_t))(void *) objc_msgSend)((id)model, meta->_setter, (int16_t)num.shortValue);
|
||||
} break;
|
||||
case YYEncodingTypeUInt16: {
|
||||
((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint16_t)num.unsignedShortValue);
|
||||
} break;
|
||||
case YYEncodingTypeInt32: {
|
||||
((void (*)(id, SEL, int32_t))(void *) objc_msgSend)((id)model, meta->_setter, (int32_t)num.intValue);
|
||||
}
|
||||
case YYEncodingTypeUInt32: {
|
||||
((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint32_t)num.unsignedIntValue);
|
||||
} break;
|
||||
case YYEncodingTypeInt64: {
|
||||
if ([num isKindOfClass:[NSDecimalNumber class]]) {
|
||||
((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue);
|
||||
} else {
|
||||
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.longLongValue);
|
||||
}
|
||||
} break;
|
||||
case YYEncodingTypeUInt64: {
|
||||
if ([num isKindOfClass:[NSDecimalNumber class]]) {
|
||||
((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.stringValue.longLongValue);
|
||||
} else {
|
||||
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue);
|
||||
}
|
||||
} break;
|
||||
case YYEncodingTypeFloat: {
|
||||
float f = num.floatValue;
|
||||
if (isnan(f) || isinf(f)) f = 0;
|
||||
((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f);
|
||||
} break;
|
||||
case YYEncodingTypeDouble: {
|
||||
double d = num.doubleValue;
|
||||
if (isnan(d) || isinf(d)) d = 0;
|
||||
((void (*)(id, SEL, double))(void *) objc_msgSend)((id)model, meta->_setter, d);
|
||||
} break;
|
||||
case YYEncodingTypeLongDouble: {
|
||||
long double d = num.doubleValue;
|
||||
if (isnan(d) || isinf(d)) d = 0;
|
||||
((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d);
|
||||
} // break; commented for code coverage in next line
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Set value to model with a property meta.
|
||||
|
||||
@discussion Caller should hold strong reference to the parameters before this function returns.
|
||||
|
||||
@param model Should not be nil.
|
||||
@param value Should not be nil, but can be NSNull.
|
||||
@param meta Should not be nil, and meta->_setter should not be nil.
|
||||
*/
|
||||
static void ModelSetValueForProperty(__unsafe_unretained id model,
|
||||
__unsafe_unretained id value,
|
||||
__unsafe_unretained _YYModelPropertyMeta *meta) {
|
||||
if (meta->_isCNumber) {
|
||||
NSNumber *num = YYNSNumberCreateFromID(value);
|
||||
ModelSetNumberToProperty(model, num, meta);
|
||||
if (num) [num class]; // hold the number
|
||||
} else if (meta->_nsType) {
|
||||
if (value == (id)kCFNull) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
|
||||
} else {
|
||||
switch (meta->_nsType) {
|
||||
case YYEncodingTypeNSString:
|
||||
case YYEncodingTypeNSMutableString: {
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
if (meta->_nsType == YYEncodingTypeNSString) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSString *)value).mutableCopy);
|
||||
}
|
||||
} else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
(meta->_nsType == YYEncodingTypeNSString) ?
|
||||
((NSNumber *)value).stringValue :
|
||||
((NSNumber *)value).stringValue.mutableCopy);
|
||||
} else if ([value isKindOfClass:[NSData class]]) {
|
||||
NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
|
||||
} else if ([value isKindOfClass:[NSURL class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
(meta->_nsType == YYEncodingTypeNSString) ?
|
||||
((NSURL *)value).absoluteString :
|
||||
((NSURL *)value).absoluteString.mutableCopy);
|
||||
} else if ([value isKindOfClass:[NSAttributedString class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
(meta->_nsType == YYEncodingTypeNSString) ?
|
||||
((NSAttributedString *)value).string :
|
||||
((NSAttributedString *)value).string.mutableCopy);
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSValue:
|
||||
case YYEncodingTypeNSNumber:
|
||||
case YYEncodingTypeNSDecimalNumber: {
|
||||
if (meta->_nsType == YYEncodingTypeNSNumber) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSNumberCreateFromID(value));
|
||||
} else if (meta->_nsType == YYEncodingTypeNSDecimalNumber) {
|
||||
if ([value isKindOfClass:[NSDecimalNumber class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
|
||||
NSDecimal dec = decNum.decimalValue;
|
||||
if (dec._length == 0 && dec._isNegative) {
|
||||
decNum = nil; // NaN
|
||||
}
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
|
||||
}
|
||||
} else { // YYEncodingTypeNSValue
|
||||
if ([value isKindOfClass:[NSValue class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSData:
|
||||
case YYEncodingTypeNSMutableData: {
|
||||
if ([value isKindOfClass:[NSData class]]) {
|
||||
if (meta->_nsType == YYEncodingTypeNSData) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else {
|
||||
NSMutableData *data = ((NSData *)value).mutableCopy;
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
|
||||
}
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (meta->_nsType == YYEncodingTypeNSMutableData) {
|
||||
data = ((NSData *)data).mutableCopy;
|
||||
}
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSDate: {
|
||||
if ([value isKindOfClass:[NSDate class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, YYNSDateFromString(value));
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSURL: {
|
||||
if ([value isKindOfClass:[NSURL class]]) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
|
||||
NSString *str = [value stringByTrimmingCharactersInSet:set];
|
||||
if (str.length == 0) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSArray:
|
||||
case YYEncodingTypeNSMutableArray: {
|
||||
if (meta->_genericCls) {
|
||||
NSArray *valueArr = nil;
|
||||
if ([value isKindOfClass:[NSArray class]]) valueArr = value;
|
||||
else if ([value isKindOfClass:[NSSet class]]) valueArr = ((NSSet *)value).allObjects;
|
||||
if (valueArr) {
|
||||
NSMutableArray *objectArr = [NSMutableArray new];
|
||||
for (id one in valueArr) {
|
||||
if ([one isKindOfClass:meta->_genericCls]) {
|
||||
[objectArr addObject:one];
|
||||
} else if ([one isKindOfClass:[NSDictionary class]]) {
|
||||
Class cls = meta->_genericCls;
|
||||
if (meta->_hasCustomClassFromDictionary) {
|
||||
cls = [cls modelCustomClassForDictionary:one];
|
||||
if (!cls) cls = meta->_genericCls; // for xcode code coverage
|
||||
}
|
||||
NSObject *newOne = [cls new];
|
||||
[newOne modelSetWithDictionary:one];
|
||||
if (newOne) [objectArr addObject:newOne];
|
||||
}
|
||||
}
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
|
||||
}
|
||||
} else {
|
||||
if ([value isKindOfClass:[NSArray class]]) {
|
||||
if (meta->_nsType == YYEncodingTypeNSArray) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
((NSArray *)value).mutableCopy);
|
||||
}
|
||||
} else if ([value isKindOfClass:[NSSet class]]) {
|
||||
if (meta->_nsType == YYEncodingTypeNSArray) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
((NSSet *)value).allObjects.mutableCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSDictionary:
|
||||
case YYEncodingTypeNSMutableDictionary: {
|
||||
if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
if (meta->_genericCls) {
|
||||
NSMutableDictionary *dic = [NSMutableDictionary new];
|
||||
[((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) {
|
||||
if ([oneValue isKindOfClass:[NSDictionary class]]) {
|
||||
Class cls = meta->_genericCls;
|
||||
if (meta->_hasCustomClassFromDictionary) {
|
||||
cls = [cls modelCustomClassForDictionary:oneValue];
|
||||
if (!cls) cls = meta->_genericCls; // for xcode code coverage
|
||||
}
|
||||
NSObject *newOne = [cls new];
|
||||
[newOne modelSetWithDictionary:(id)oneValue];
|
||||
if (newOne) dic[oneKey] = newOne;
|
||||
}
|
||||
}];
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
|
||||
} else {
|
||||
if (meta->_nsType == YYEncodingTypeNSDictionary) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
((NSDictionary *)value).mutableCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeNSSet:
|
||||
case YYEncodingTypeNSMutableSet: {
|
||||
NSSet *valueSet = nil;
|
||||
if ([value isKindOfClass:[NSArray class]]) valueSet = [NSMutableSet setWithArray:value];
|
||||
else if ([value isKindOfClass:[NSSet class]]) valueSet = ((NSSet *)value);
|
||||
|
||||
if (meta->_genericCls) {
|
||||
NSMutableSet *set = [NSMutableSet new];
|
||||
for (id one in valueSet) {
|
||||
if ([one isKindOfClass:meta->_genericCls]) {
|
||||
[set addObject:one];
|
||||
} else if ([one isKindOfClass:[NSDictionary class]]) {
|
||||
Class cls = meta->_genericCls;
|
||||
if (meta->_hasCustomClassFromDictionary) {
|
||||
cls = [cls modelCustomClassForDictionary:one];
|
||||
if (!cls) cls = meta->_genericCls; // for xcode code coverage
|
||||
}
|
||||
NSObject *newOne = [cls new];
|
||||
[newOne modelSetWithDictionary:one];
|
||||
if (newOne) [set addObject:newOne];
|
||||
}
|
||||
}
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
|
||||
} else {
|
||||
if (meta->_nsType == YYEncodingTypeNSSet) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
|
||||
} else {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
|
||||
meta->_setter,
|
||||
((NSSet *)valueSet).mutableCopy);
|
||||
}
|
||||
}
|
||||
} // break; commented for code coverage in next line
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BOOL isNull = (value == (id)kCFNull);
|
||||
switch (meta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeObject: {
|
||||
if (isNull) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
|
||||
} else if ([value isKindOfClass:meta->_cls] || !meta->_cls) {
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
|
||||
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
NSObject *one = nil;
|
||||
if (meta->_getter) {
|
||||
one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
|
||||
}
|
||||
if (one) {
|
||||
[one modelSetWithDictionary:value];
|
||||
} else {
|
||||
Class cls = meta->_cls;
|
||||
if (meta->_hasCustomClassFromDictionary) {
|
||||
cls = [cls modelCustomClassForDictionary:value];
|
||||
if (!cls) cls = meta->_genericCls; // for xcode code coverage
|
||||
}
|
||||
one = [cls new];
|
||||
[one modelSetWithDictionary:value];
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeClass: {
|
||||
if (isNull) {
|
||||
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
|
||||
} else {
|
||||
Class cls = nil;
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
cls = NSClassFromString(value);
|
||||
if (cls) {
|
||||
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls);
|
||||
}
|
||||
} else {
|
||||
cls = object_getClass(value);
|
||||
if (cls) {
|
||||
if (class_isMetaClass(cls)) {
|
||||
((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeSEL: {
|
||||
if (isNull) {
|
||||
((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL);
|
||||
} else if ([value isKindOfClass:[NSString class]]) {
|
||||
SEL sel = NSSelectorFromString(value);
|
||||
if (sel) ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel);
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeBlock: {
|
||||
if (isNull) {
|
||||
((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())NULL);
|
||||
} else if ([value isKindOfClass:YYNSBlockClass()]) {
|
||||
((void (*)(id, SEL, void (^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void (^)())value);
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypeStruct:
|
||||
case YYEncodingTypeUnion:
|
||||
case YYEncodingTypeCArray: {
|
||||
if ([value isKindOfClass:[NSValue class]]) {
|
||||
const char *valueType = ((NSValue *)value).objCType;
|
||||
const char *metaType = meta->_info.typeEncoding.UTF8String;
|
||||
if (valueType && metaType && strcmp(valueType, metaType) == 0) {
|
||||
[model setValue:value forKey:meta->_name];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case YYEncodingTypePointer:
|
||||
case YYEncodingTypeCString: {
|
||||
if (isNull) {
|
||||
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
|
||||
} else if ([value isKindOfClass:[NSValue class]]) {
|
||||
NSValue *nsValue = value;
|
||||
if (nsValue.objCType && strcmp(nsValue.objCType, "^v") == 0) {
|
||||
((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
|
||||
}
|
||||
}
|
||||
} // break; commented for code coverage in next line
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *modelMeta; ///< _YYModelMeta
|
||||
void *model; ///< id (self)
|
||||
void *dictionary; ///< NSDictionary (json)
|
||||
} ModelSetContext;
|
||||
|
||||
/**
|
||||
Apply function for dictionary, to set the key-value pair to model.
|
||||
|
||||
@param _key should not be nil, NSString.
|
||||
@param _value should not be nil.
|
||||
@param _context _context.modelMeta and _context.model should not be nil.
|
||||
*/
|
||||
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
|
||||
ModelSetContext *context = _context;
|
||||
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
|
||||
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
|
||||
__unsafe_unretained id model = (__bridge id)(context->model);
|
||||
while (propertyMeta) {
|
||||
if (propertyMeta->_setter) {
|
||||
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
|
||||
}
|
||||
propertyMeta = propertyMeta->_next;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Apply function for model property meta, to set dictionary to model.
|
||||
|
||||
@param _propertyMeta should not be nil, _YYModelPropertyMeta.
|
||||
@param _context _context.model and _context.dictionary should not be nil.
|
||||
*/
|
||||
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
|
||||
ModelSetContext *context = _context;
|
||||
__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
|
||||
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
|
||||
if (!propertyMeta->_setter) return;
|
||||
id value = nil;
|
||||
|
||||
if (propertyMeta->_mappedToKeyArray) {
|
||||
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
|
||||
} else if (propertyMeta->_mappedToKeyPath) {
|
||||
value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
|
||||
} else {
|
||||
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
|
||||
}
|
||||
|
||||
if (value) {
|
||||
__unsafe_unretained id model = (__bridge id)(context->model);
|
||||
ModelSetValueForProperty(model, value, propertyMeta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull),
|
||||
or nil if an error occurs.
|
||||
|
||||
@param model Model, can be nil.
|
||||
@return JSON object, nil if an error occurs.
|
||||
*/
|
||||
static id ModelToJSONObjectRecursive(NSObject *model) {
|
||||
if (!model || model == (id)kCFNull) return model;
|
||||
if ([model isKindOfClass:[NSString class]]) return model;
|
||||
if ([model isKindOfClass:[NSNumber class]]) return model;
|
||||
if ([model isKindOfClass:[NSDictionary class]]) {
|
||||
if ([NSJSONSerialization isValidJSONObject:model]) return model;
|
||||
NSMutableDictionary *newDic = [NSMutableDictionary new];
|
||||
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
||||
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
|
||||
if (!stringKey) return;
|
||||
id jsonObj = ModelToJSONObjectRecursive(obj);
|
||||
if (!jsonObj) jsonObj = (id)kCFNull;
|
||||
newDic[stringKey] = jsonObj;
|
||||
}];
|
||||
return newDic;
|
||||
}
|
||||
if ([model isKindOfClass:[NSSet class]]) {
|
||||
NSArray *array = ((NSSet *)model).allObjects;
|
||||
if ([NSJSONSerialization isValidJSONObject:array]) return array;
|
||||
NSMutableArray *newArray = [NSMutableArray new];
|
||||
for (id obj in array) {
|
||||
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
|
||||
[newArray addObject:obj];
|
||||
} else {
|
||||
id jsonObj = ModelToJSONObjectRecursive(obj);
|
||||
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
|
||||
}
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
if ([model isKindOfClass:[NSArray class]]) {
|
||||
if ([NSJSONSerialization isValidJSONObject:model]) return model;
|
||||
NSMutableArray *newArray = [NSMutableArray new];
|
||||
for (id obj in (NSArray *)model) {
|
||||
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
|
||||
[newArray addObject:obj];
|
||||
} else {
|
||||
id jsonObj = ModelToJSONObjectRecursive(obj);
|
||||
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
|
||||
}
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString;
|
||||
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string;
|
||||
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model];
|
||||
if ([model isKindOfClass:[NSData class]]) return nil;
|
||||
|
||||
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
|
||||
if (!modelMeta || modelMeta->_keyMappedCount == 0) return nil;
|
||||
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
|
||||
__unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
|
||||
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
|
||||
if (!propertyMeta->_getter) return;
|
||||
|
||||
id value = nil;
|
||||
if (propertyMeta->_isCNumber) {
|
||||
value = ModelCreateNumberFromProperty(model, propertyMeta);
|
||||
} else if (propertyMeta->_nsType) {
|
||||
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
|
||||
value = ModelToJSONObjectRecursive(v);
|
||||
} else {
|
||||
switch (propertyMeta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeObject: {
|
||||
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
|
||||
value = ModelToJSONObjectRecursive(v);
|
||||
if (value == (id)kCFNull) value = nil;
|
||||
} break;
|
||||
case YYEncodingTypeClass: {
|
||||
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
|
||||
value = v ? NSStringFromClass(v) : nil;
|
||||
} break;
|
||||
case YYEncodingTypeSEL: {
|
||||
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
|
||||
value = v ? NSStringFromSelector(v) : nil;
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (!value) return;
|
||||
|
||||
if (propertyMeta->_mappedToKeyPath) {
|
||||
NSMutableDictionary *superDic = dic;
|
||||
NSMutableDictionary *subDic = nil;
|
||||
for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
|
||||
NSString *key = propertyMeta->_mappedToKeyPath[i];
|
||||
if (i + 1 == max) { // end
|
||||
if (!superDic[key]) superDic[key] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
subDic = superDic[key];
|
||||
if (subDic) {
|
||||
if ([subDic isKindOfClass:[NSDictionary class]]) {
|
||||
subDic = subDic.mutableCopy;
|
||||
superDic[key] = subDic;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
subDic = [NSMutableDictionary new];
|
||||
superDic[key] = subDic;
|
||||
}
|
||||
superDic = subDic;
|
||||
subDic = nil;
|
||||
}
|
||||
} else {
|
||||
if (!dic[propertyMeta->_mappedToKey]) {
|
||||
dic[propertyMeta->_mappedToKey] = value;
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (modelMeta->_hasCustomTransformToDictionary) {
|
||||
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
|
||||
if (!suc) return nil;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Add indent to string (exclude first line)
|
||||
static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) {
|
||||
for (NSUInteger i = 0, max = desc.length; i < max; i++) {
|
||||
unichar c = [desc characterAtIndex:i];
|
||||
if (c == '\n') {
|
||||
for (NSUInteger j = 0; j < indent; j++) {
|
||||
[desc insertString:@" " atIndex:i + 1];
|
||||
}
|
||||
i += indent * 4;
|
||||
max += indent * 4;
|
||||
}
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
/// Generaate a description string
|
||||
static NSString *ModelDescription(NSObject *model) {
|
||||
static const int kDescMaxLength = 100;
|
||||
if (!model) return @"<nil>";
|
||||
if (model == (id)kCFNull) return @"<null>";
|
||||
if (![model isKindOfClass:[NSObject class]]) return [NSString stringWithFormat:@"%@",model];
|
||||
|
||||
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:model.class];
|
||||
switch (modelMeta->_nsType) {
|
||||
case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: {
|
||||
return [NSString stringWithFormat:@"\"%@\"",model];
|
||||
}
|
||||
|
||||
case YYEncodingTypeNSValue:
|
||||
case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: {
|
||||
NSString *tmp = model.description;
|
||||
if (tmp.length > kDescMaxLength) {
|
||||
tmp = [tmp substringToIndex:kDescMaxLength];
|
||||
tmp = [tmp stringByAppendingString:@"..."];
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
case YYEncodingTypeNSNumber:
|
||||
case YYEncodingTypeNSDecimalNumber:
|
||||
case YYEncodingTypeNSDate:
|
||||
case YYEncodingTypeNSURL: {
|
||||
return [NSString stringWithFormat:@"%@",model];
|
||||
}
|
||||
|
||||
case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: {
|
||||
model = ((NSSet *)model).allObjects;
|
||||
} // no break
|
||||
|
||||
case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: {
|
||||
NSArray *array = (id)model;
|
||||
NSMutableString *desc = [NSMutableString new];
|
||||
if (array.count == 0) {
|
||||
return [desc stringByAppendingString:@"[]"];
|
||||
} else {
|
||||
[desc appendFormat:@"[\n"];
|
||||
for (NSUInteger i = 0, max = array.count; i < max; i++) {
|
||||
NSObject *obj = array[i];
|
||||
[desc appendString:@" "];
|
||||
[desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)];
|
||||
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
|
||||
}
|
||||
[desc appendString:@"]"];
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: {
|
||||
NSDictionary *dic = (id)model;
|
||||
NSMutableString *desc = [NSMutableString new];
|
||||
if (dic.count == 0) {
|
||||
return [desc stringByAppendingString:@"{}"];
|
||||
} else {
|
||||
NSArray *keys = dic.allKeys;
|
||||
|
||||
[desc appendFormat:@"{\n"];
|
||||
for (NSUInteger i = 0, max = keys.count; i < max; i++) {
|
||||
NSString *key = keys[i];
|
||||
NSObject *value = dic[key];
|
||||
[desc appendString:@" "];
|
||||
[desc appendFormat:@"%@ = %@",key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, 1)];
|
||||
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
|
||||
}
|
||||
[desc appendString:@"}"];
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
default: {
|
||||
NSMutableString *desc = [NSMutableString new];
|
||||
[desc appendFormat:@"<%@: %p>", model.class, model];
|
||||
if (modelMeta->_allPropertyMetas.count == 0) return desc;
|
||||
|
||||
// sort property names
|
||||
NSArray *properties = [modelMeta->_allPropertyMetas
|
||||
sortedArrayUsingComparator:^NSComparisonResult(_YYModelPropertyMeta *p1, _YYModelPropertyMeta *p2) {
|
||||
return [p1->_name compare:p2->_name];
|
||||
}];
|
||||
|
||||
[desc appendFormat:@" {\n"];
|
||||
for (NSUInteger i = 0, max = properties.count; i < max; i++) {
|
||||
_YYModelPropertyMeta *property = properties[i];
|
||||
NSString *propertyDesc;
|
||||
if (property->_isCNumber) {
|
||||
NSNumber *num = ModelCreateNumberFromProperty(model, property);
|
||||
propertyDesc = num.stringValue;
|
||||
} else {
|
||||
switch (property->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeObject: {
|
||||
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
|
||||
propertyDesc = ModelDescription(v);
|
||||
if (!propertyDesc) propertyDesc = @"<nil>";
|
||||
} break;
|
||||
case YYEncodingTypeClass: {
|
||||
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
|
||||
propertyDesc = ((NSObject *)v).description;
|
||||
if (!propertyDesc) propertyDesc = @"<nil>";
|
||||
} break;
|
||||
case YYEncodingTypeSEL: {
|
||||
SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
|
||||
if (sel) propertyDesc = NSStringFromSelector(sel);
|
||||
else propertyDesc = @"<NULL>";
|
||||
} break;
|
||||
case YYEncodingTypeBlock: {
|
||||
id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
|
||||
propertyDesc = block ? ((NSObject *)block).description : @"<nil>";
|
||||
} break;
|
||||
case YYEncodingTypeCArray: case YYEncodingTypeCString: case YYEncodingTypePointer: {
|
||||
void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
|
||||
propertyDesc = [NSString stringWithFormat:@"%p",pointer];
|
||||
} break;
|
||||
case YYEncodingTypeStruct: case YYEncodingTypeUnion: {
|
||||
NSValue *value = [model valueForKey:property->_name];
|
||||
propertyDesc = value ? value.description : @"{unknown}";
|
||||
} break;
|
||||
default: propertyDesc = @"<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, 1);
|
||||
[desc appendFormat:@" %@ = %@",property->_name, propertyDesc];
|
||||
[desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
|
||||
}
|
||||
[desc appendFormat:@"}"];
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@implementation NSObject (YYModel)
|
||||
|
||||
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
|
||||
if (!json || json == (id)kCFNull) return nil;
|
||||
NSDictionary *dic = nil;
|
||||
NSData *jsonData = nil;
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
dic = json;
|
||||
} else if ([json isKindOfClass:[NSString class]]) {
|
||||
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
|
||||
} else if ([json isKindOfClass:[NSData class]]) {
|
||||
jsonData = json;
|
||||
}
|
||||
if (jsonData) {
|
||||
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
|
||||
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
|
||||
}
|
||||
return dic;
|
||||
}
|
||||
|
||||
+ (instancetype)modelWithJSON:(id)json {
|
||||
NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
|
||||
return [self modelWithDictionary:dic];
|
||||
}
|
||||
|
||||
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary {
|
||||
if (!dictionary || dictionary == (id)kCFNull) return nil;
|
||||
if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
Class cls = [self class];
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:cls];
|
||||
if (modelMeta->_hasCustomClassFromDictionary) {
|
||||
cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
|
||||
}
|
||||
|
||||
NSObject *one = [cls new];
|
||||
if ([one modelSetWithDictionary:dictionary]) return one;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)modelSetWithJSON:(id)json {
|
||||
NSDictionary *dic = [NSObject _yy_dictionaryWithJSON:json];
|
||||
return [self modelSetWithDictionary:dic];
|
||||
}
|
||||
|
||||
- (BOOL)modelSetWithDictionary:(NSDictionary *)dic {
|
||||
if (!dic || dic == (id)kCFNull) return NO;
|
||||
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
|
||||
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
|
||||
if (modelMeta->_keyMappedCount == 0) return NO;
|
||||
|
||||
if (modelMeta->_hasCustomWillTransformFromDictionary) {
|
||||
dic = [((id<YYModel>)self) modelCustomWillTransformFromDictionary:dic];
|
||||
if (![dic isKindOfClass:[NSDictionary class]]) return NO;
|
||||
}
|
||||
|
||||
ModelSetContext context = {0};
|
||||
context.modelMeta = (__bridge void *)(modelMeta);
|
||||
context.model = (__bridge void *)(self);
|
||||
context.dictionary = (__bridge void *)(dic);
|
||||
|
||||
if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
|
||||
CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
|
||||
if (modelMeta->_keyPathPropertyMetas) {
|
||||
CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
|
||||
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
|
||||
ModelSetWithPropertyMetaArrayFunction,
|
||||
&context);
|
||||
}
|
||||
if (modelMeta->_multiKeysPropertyMetas) {
|
||||
CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
|
||||
CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
|
||||
ModelSetWithPropertyMetaArrayFunction,
|
||||
&context);
|
||||
}
|
||||
} else {
|
||||
CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
|
||||
CFRangeMake(0, modelMeta->_keyMappedCount),
|
||||
ModelSetWithPropertyMetaArrayFunction,
|
||||
&context);
|
||||
}
|
||||
|
||||
if (modelMeta->_hasCustomTransformFromDictionary) {
|
||||
return [((id<YYModel>)self) modelCustomTransformFromDictionary:dic];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)modelToJSONObject {
|
||||
/*
|
||||
Apple said:
|
||||
The top level object is an NSArray or NSDictionary.
|
||||
All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
|
||||
All dictionary keys are instances of NSString.
|
||||
Numbers are not NaN or infinity.
|
||||
*/
|
||||
id jsonObject = ModelToJSONObjectRecursive(self);
|
||||
if ([jsonObject isKindOfClass:[NSArray class]]) return jsonObject;
|
||||
if ([jsonObject isKindOfClass:[NSDictionary class]]) return jsonObject;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSData *)modelToJSONData {
|
||||
id jsonObject = [self modelToJSONObject];
|
||||
if (!jsonObject) return nil;
|
||||
return [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:NULL];
|
||||
}
|
||||
|
||||
- (NSString *)modelToJSONString {
|
||||
NSData *jsonData = [self modelToJSONData];
|
||||
if (jsonData.length == 0) return nil;
|
||||
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (id)modelCopy{
|
||||
if (self == (id)kCFNull) return self;
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
|
||||
if (modelMeta->_nsType) return [self copy];
|
||||
|
||||
NSObject *one = [self.class new];
|
||||
for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
|
||||
if (!propertyMeta->_getter || !propertyMeta->_setter) continue;
|
||||
|
||||
if (propertyMeta->_isCNumber) {
|
||||
switch (propertyMeta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeBool: {
|
||||
bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeInt8:
|
||||
case YYEncodingTypeUInt8: {
|
||||
uint8_t num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeInt16:
|
||||
case YYEncodingTypeUInt16: {
|
||||
uint16_t num = ((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeInt32:
|
||||
case YYEncodingTypeUInt32: {
|
||||
uint32_t num = ((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeInt64:
|
||||
case YYEncodingTypeUInt64: {
|
||||
uint64_t num = ((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeFloat: {
|
||||
float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, float))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeDouble: {
|
||||
double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} break;
|
||||
case YYEncodingTypeLongDouble: {
|
||||
long double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
|
||||
} // break; commented for code coverage in next line
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
switch (propertyMeta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeObject:
|
||||
case YYEncodingTypeClass:
|
||||
case YYEncodingTypeBlock: {
|
||||
id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
|
||||
} break;
|
||||
case YYEncodingTypeSEL:
|
||||
case YYEncodingTypePointer:
|
||||
case YYEncodingTypeCString: {
|
||||
size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
|
||||
} break;
|
||||
case YYEncodingTypeStruct:
|
||||
case YYEncodingTypeUnion: {
|
||||
@try {
|
||||
NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
|
||||
if (value) {
|
||||
[one setValue:value forKey:propertyMeta->_name];
|
||||
}
|
||||
} @catch (NSException *exception) {}
|
||||
} // break; commented for code coverage in next line
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)modelEncodeWithCoder:(NSCoder *)aCoder {
|
||||
if (!aCoder) return;
|
||||
if (self == (id)kCFNull) {
|
||||
[((id<NSCoding>)self)encodeWithCoder:aCoder];
|
||||
return;
|
||||
}
|
||||
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
|
||||
if (modelMeta->_nsType) {
|
||||
[((id<NSCoding>)self)encodeWithCoder:aCoder];
|
||||
return;
|
||||
}
|
||||
|
||||
for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
|
||||
if (!propertyMeta->_getter) return;
|
||||
|
||||
if (propertyMeta->_isCNumber) {
|
||||
NSNumber *value = ModelCreateNumberFromProperty(self, propertyMeta);
|
||||
if (value) [aCoder encodeObject:value forKey:propertyMeta->_name];
|
||||
} else {
|
||||
switch (propertyMeta->_type & YYEncodingTypeMask) {
|
||||
case YYEncodingTypeObject: {
|
||||
id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) {
|
||||
if ([value isKindOfClass:[NSValue class]]) {
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
[aCoder encodeObject:value forKey:propertyMeta->_name];
|
||||
}
|
||||
} else {
|
||||
[aCoder encodeObject:value forKey:propertyMeta->_name];
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case YYEncodingTypeSEL: {
|
||||
SEL value = ((SEL (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter);
|
||||
if (value) {
|
||||
NSString *str = NSStringFromSelector(value);
|
||||
[aCoder encodeObject:str forKey:propertyMeta->_name];
|
||||
}
|
||||
} break;
|
||||
case YYEncodingTypeStruct:
|
||||
case YYEncodingTypeUnion: {
|
||||
if (propertyMeta->_isKVCCompatible && propertyMeta->_isStructAvailableForKeyedArchiver) {
|
||||
@try {
|
||||
NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
|
||||
[aCoder encodeObject:value forKey:propertyMeta->_name];
|
||||
} @catch (NSException *exception) {}
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (id)modelInitWithCoder:(NSCoder *)aDecoder {
|
||||
if (!aDecoder) return self;
|
||||
if (self == (id)kCFNull) return self;
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
|
||||
if (modelMeta->_nsType) return self;
|
||||
|
||||
for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
|
||||
if (!propertyMeta->_setter) continue;
|
||||
|
||||
if (propertyMeta->_isCNumber) {
|
||||
NSNumber *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
|
||||
if ([value isKindOfClass:[NSNumber class]]) {
|
||||
ModelSetNumberToProperty(self, value, propertyMeta);
|
||||
[value class];
|
||||
}
|
||||
} else {
|
||||
YYEncodingType type = propertyMeta->_type & YYEncodingTypeMask;
|
||||
switch (type) {
|
||||
case YYEncodingTypeObject: {
|
||||
id value = [aDecoder decodeObjectForKey:propertyMeta->_name];
|
||||
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)self, propertyMeta->_setter, value);
|
||||
} break;
|
||||
case YYEncodingTypeSEL: {
|
||||
NSString *str = [aDecoder decodeObjectForKey:propertyMeta->_name];
|
||||
if ([str isKindOfClass:[NSString class]]) {
|
||||
SEL sel = NSSelectorFromString(str);
|
||||
((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_setter, sel);
|
||||
}
|
||||
} break;
|
||||
case YYEncodingTypeStruct:
|
||||
case YYEncodingTypeUnion: {
|
||||
if (propertyMeta->_isKVCCompatible) {
|
||||
@try {
|
||||
NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
|
||||
if (value) [self setValue:value forKey:propertyMeta->_name];
|
||||
} @catch (NSException *exception) {}
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)modelHash {
|
||||
if (self == (id)kCFNull) return [self hash];
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
|
||||
if (modelMeta->_nsType) return [self hash];
|
||||
|
||||
NSUInteger value = 0;
|
||||
NSUInteger count = 0;
|
||||
for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
|
||||
if (!propertyMeta->_isKVCCompatible) continue;
|
||||
value ^= [[self valueForKey:NSStringFromSelector(propertyMeta->_getter)] hash];
|
||||
count++;
|
||||
}
|
||||
if (count == 0) value = (long)((__bridge void *)self);
|
||||
return value;
|
||||
}
|
||||
|
||||
- (BOOL)modelIsEqual:(id)model {
|
||||
if (self == model) return YES;
|
||||
if (![model isMemberOfClass:self.class]) return NO;
|
||||
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
|
||||
if (modelMeta->_nsType) return [self isEqual:model];
|
||||
if ([self hash] != [model hash]) return NO;
|
||||
|
||||
for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
|
||||
if (!propertyMeta->_isKVCCompatible) continue;
|
||||
id this = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
|
||||
id that = [model valueForKey:NSStringFromSelector(propertyMeta->_getter)];
|
||||
if (this == that) continue;
|
||||
if (this == nil || that == nil) return NO;
|
||||
if (![this isEqual:that]) return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)modelDescription {
|
||||
return ModelDescription(self);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation NSArray (YYModel)
|
||||
|
||||
+ (NSArray *)modelArrayWithClass:(Class)cls json:(id)json {
|
||||
if (!json) return nil;
|
||||
NSArray *arr = nil;
|
||||
NSData *jsonData = nil;
|
||||
if ([json isKindOfClass:[NSArray class]]) {
|
||||
arr = json;
|
||||
} else if ([json isKindOfClass:[NSString class]]) {
|
||||
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
|
||||
} else if ([json isKindOfClass:[NSData class]]) {
|
||||
jsonData = json;
|
||||
}
|
||||
if (jsonData) {
|
||||
arr = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
|
||||
if (![arr isKindOfClass:[NSArray class]]) arr = nil;
|
||||
}
|
||||
return [self modelArrayWithClass:cls array:arr];
|
||||
}
|
||||
|
||||
+ (NSArray *)modelArrayWithClass:(Class)cls array:(NSArray *)arr {
|
||||
if (!cls || !arr) return nil;
|
||||
NSMutableArray *result = [NSMutableArray new];
|
||||
for (NSDictionary *dic in arr) {
|
||||
if (![dic isKindOfClass:[NSDictionary class]]) continue;
|
||||
NSObject *obj = [cls modelWithDictionary:dic];
|
||||
if (obj) [result addObject:obj];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSDictionary (YYModel)
|
||||
|
||||
+ (NSDictionary *)modelDictionaryWithClass:(Class)cls json:(id)json {
|
||||
if (!json) return nil;
|
||||
NSDictionary *dic = nil;
|
||||
NSData *jsonData = nil;
|
||||
if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
dic = json;
|
||||
} else if ([json isKindOfClass:[NSString class]]) {
|
||||
jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
|
||||
} else if ([json isKindOfClass:[NSData class]]) {
|
||||
jsonData = json;
|
||||
}
|
||||
if (jsonData) {
|
||||
dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
|
||||
if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
|
||||
}
|
||||
return [self modelDictionaryWithClass:cls dictionary:dic];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic {
|
||||
if (!cls || !dic) return nil;
|
||||
NSMutableDictionary *result = [NSMutableDictionary new];
|
||||
for (NSString *key in dic.allKeys) {
|
||||
if (![key isKindOfClass:[NSString class]]) continue;
|
||||
NSObject *obj = [cls modelWithDictionary:dic[key]];
|
||||
if (obj) result[key] = obj;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
200
ShenQi/YYModel/YYClassInfo.h
Normal file
200
ShenQi/YYModel/YYClassInfo.h
Normal file
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// YYClassInfo.h
|
||||
// YYKit <https://github.com/ibireme/YYKit>
|
||||
//
|
||||
// Created by ibireme on 15/5/9.
|
||||
// Copyright (c) 2015 ibireme.
|
||||
//
|
||||
// This source code is licensed under the MIT-style license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Type encoding's type.
|
||||
*/
|
||||
typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
|
||||
YYEncodingTypeMask = 0xFF, ///< mask of type value
|
||||
YYEncodingTypeUnknown = 0, ///< unknown
|
||||
YYEncodingTypeVoid = 1, ///< void
|
||||
YYEncodingTypeBool = 2, ///< bool
|
||||
YYEncodingTypeInt8 = 3, ///< char / BOOL
|
||||
YYEncodingTypeUInt8 = 4, ///< unsigned char
|
||||
YYEncodingTypeInt16 = 5, ///< short
|
||||
YYEncodingTypeUInt16 = 6, ///< unsigned short
|
||||
YYEncodingTypeInt32 = 7, ///< int
|
||||
YYEncodingTypeUInt32 = 8, ///< unsigned int
|
||||
YYEncodingTypeInt64 = 9, ///< long long
|
||||
YYEncodingTypeUInt64 = 10, ///< unsigned long long
|
||||
YYEncodingTypeFloat = 11, ///< float
|
||||
YYEncodingTypeDouble = 12, ///< double
|
||||
YYEncodingTypeLongDouble = 13, ///< long double
|
||||
YYEncodingTypeObject = 14, ///< id
|
||||
YYEncodingTypeClass = 15, ///< Class
|
||||
YYEncodingTypeSEL = 16, ///< SEL
|
||||
YYEncodingTypeBlock = 17, ///< block
|
||||
YYEncodingTypePointer = 18, ///< void*
|
||||
YYEncodingTypeStruct = 19, ///< struct
|
||||
YYEncodingTypeUnion = 20, ///< union
|
||||
YYEncodingTypeCString = 21, ///< char*
|
||||
YYEncodingTypeCArray = 22, ///< char[10] (for example)
|
||||
|
||||
YYEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier
|
||||
YYEncodingTypeQualifierConst = 1 << 8, ///< const
|
||||
YYEncodingTypeQualifierIn = 1 << 9, ///< in
|
||||
YYEncodingTypeQualifierInout = 1 << 10, ///< inout
|
||||
YYEncodingTypeQualifierOut = 1 << 11, ///< out
|
||||
YYEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy
|
||||
YYEncodingTypeQualifierByref = 1 << 13, ///< byref
|
||||
YYEncodingTypeQualifierOneway = 1 << 14, ///< oneway
|
||||
|
||||
YYEncodingTypePropertyMask = 0xFF0000, ///< mask of property
|
||||
YYEncodingTypePropertyReadonly = 1 << 16, ///< readonly
|
||||
YYEncodingTypePropertyCopy = 1 << 17, ///< copy
|
||||
YYEncodingTypePropertyRetain = 1 << 18, ///< retain
|
||||
YYEncodingTypePropertyNonatomic = 1 << 19, ///< nonatomic
|
||||
YYEncodingTypePropertyWeak = 1 << 20, ///< weak
|
||||
YYEncodingTypePropertyCustomGetter = 1 << 21, ///< getter=
|
||||
YYEncodingTypePropertyCustomSetter = 1 << 22, ///< setter=
|
||||
YYEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic
|
||||
};
|
||||
|
||||
/**
|
||||
Get the type from a Type-Encoding string.
|
||||
|
||||
@discussion See also:
|
||||
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
|
||||
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
|
||||
|
||||
@param typeEncoding A Type-Encoding string.
|
||||
@return The encoding type.
|
||||
*/
|
||||
YYEncodingType YYEncodingGetType(const char *typeEncoding);
|
||||
|
||||
|
||||
/**
|
||||
Instance variable information.
|
||||
*/
|
||||
@interface YYClassIvarInfo : NSObject
|
||||
@property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct
|
||||
@property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name
|
||||
@property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset
|
||||
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding
|
||||
@property (nonatomic, assign, readonly) YYEncodingType type; ///< Ivar's type
|
||||
|
||||
/**
|
||||
Creates and returns an ivar info object.
|
||||
|
||||
@param ivar ivar opaque struct
|
||||
@return A new object, or nil if an error occurs.
|
||||
*/
|
||||
- (instancetype)initWithIvar:(Ivar)ivar;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Method information.
|
||||
*/
|
||||
@interface YYClassMethodInfo : NSObject
|
||||
@property (nonatomic, assign, readonly) Method method; ///< method opaque struct
|
||||
@property (nonatomic, strong, readonly) NSString *name; ///< method name
|
||||
@property (nonatomic, assign, readonly) SEL sel; ///< method's selector
|
||||
@property (nonatomic, assign, readonly) IMP imp; ///< method's implementation
|
||||
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types
|
||||
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type
|
||||
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type
|
||||
|
||||
/**
|
||||
Creates and returns a method info object.
|
||||
|
||||
@param method method opaque struct
|
||||
@return A new object, or nil if an error occurs.
|
||||
*/
|
||||
- (instancetype)initWithMethod:(Method)method;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Property information.
|
||||
*/
|
||||
@interface YYClassPropertyInfo : NSObject
|
||||
@property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct
|
||||
@property (nonatomic, strong, readonly) NSString *name; ///< property's name
|
||||
@property (nonatomic, assign, readonly) YYEncodingType type; ///< property's type
|
||||
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value
|
||||
@property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name
|
||||
@property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil
|
||||
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil
|
||||
@property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull)
|
||||
@property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull)
|
||||
|
||||
/**
|
||||
Creates and returns a property info object.
|
||||
|
||||
@param property property opaque struct
|
||||
@return A new object, or nil if an error occurs.
|
||||
*/
|
||||
- (instancetype)initWithProperty:(objc_property_t)property;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
Class information for a class.
|
||||
*/
|
||||
@interface YYClassInfo : NSObject
|
||||
@property (nonatomic, assign, readonly) Class cls; ///< class object
|
||||
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
|
||||
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object
|
||||
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
|
||||
@property (nonatomic, strong, readonly) NSString *name; ///< class name
|
||||
@property (nullable, nonatomic, strong, readonly) YYClassInfo *superClassInfo; ///< super class's class info
|
||||
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassIvarInfo *> *ivarInfos; ///< ivars
|
||||
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassMethodInfo *> *methodInfos; ///< methods
|
||||
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos; ///< properties
|
||||
|
||||
/**
|
||||
If the class is changed (for example: you add a method to this class with
|
||||
'class_addMethod()'), you should call this method to refresh the class info cache.
|
||||
|
||||
After called this method, `needUpdate` will returns `YES`, and you should call
|
||||
'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info.
|
||||
*/
|
||||
- (void)setNeedUpdate;
|
||||
|
||||
/**
|
||||
If this method returns `YES`, you should stop using this instance and call
|
||||
`classInfoWithClass` or `classInfoWithClassName` to get the updated class info.
|
||||
|
||||
@return Whether this class info need update.
|
||||
*/
|
||||
- (BOOL)needUpdate;
|
||||
|
||||
/**
|
||||
Get the class info of a specified Class.
|
||||
|
||||
@discussion This method will cache the class info and super-class info
|
||||
at the first access to the Class. This method is thread-safe.
|
||||
|
||||
@param cls A class.
|
||||
@return A class info, or nil if an error occurs.
|
||||
*/
|
||||
+ (nullable instancetype)classInfoWithClass:(Class)cls;
|
||||
|
||||
/**
|
||||
Get the class info of a specified Class.
|
||||
|
||||
@discussion This method will cache the class info and super-class info
|
||||
at the first access to the Class. This method is thread-safe.
|
||||
|
||||
@param className A class name.
|
||||
@return A class info, or nil if an error occurs.
|
||||
*/
|
||||
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
362
ShenQi/YYModel/YYClassInfo.m
Normal file
362
ShenQi/YYModel/YYClassInfo.m
Normal file
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// YYClassInfo.m
|
||||
// YYKit <https://github.com/ibireme/YYKit>
|
||||
//
|
||||
// Created by ibireme on 15/5/9.
|
||||
// Copyright (c) 2015 ibireme.
|
||||
//
|
||||
// This source code is licensed under the MIT-style license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
//
|
||||
|
||||
#import "YYClassInfo.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
YYEncodingType YYEncodingGetType(const char *typeEncoding) {
|
||||
char *type = (char *)typeEncoding;
|
||||
if (!type) return YYEncodingTypeUnknown;
|
||||
size_t len = strlen(type);
|
||||
if (len == 0) return YYEncodingTypeUnknown;
|
||||
|
||||
YYEncodingType qualifier = 0;
|
||||
bool prefix = true;
|
||||
while (prefix) {
|
||||
switch (*type) {
|
||||
case 'r': {
|
||||
qualifier |= YYEncodingTypeQualifierConst;
|
||||
type++;
|
||||
} break;
|
||||
case 'n': {
|
||||
qualifier |= YYEncodingTypeQualifierIn;
|
||||
type++;
|
||||
} break;
|
||||
case 'N': {
|
||||
qualifier |= YYEncodingTypeQualifierInout;
|
||||
type++;
|
||||
} break;
|
||||
case 'o': {
|
||||
qualifier |= YYEncodingTypeQualifierOut;
|
||||
type++;
|
||||
} break;
|
||||
case 'O': {
|
||||
qualifier |= YYEncodingTypeQualifierBycopy;
|
||||
type++;
|
||||
} break;
|
||||
case 'R': {
|
||||
qualifier |= YYEncodingTypeQualifierByref;
|
||||
type++;
|
||||
} break;
|
||||
case 'V': {
|
||||
qualifier |= YYEncodingTypeQualifierOneway;
|
||||
type++;
|
||||
} break;
|
||||
default: { prefix = false; } break;
|
||||
}
|
||||
}
|
||||
|
||||
len = strlen(type);
|
||||
if (len == 0) return YYEncodingTypeUnknown | qualifier;
|
||||
|
||||
switch (*type) {
|
||||
case 'v': return YYEncodingTypeVoid | qualifier;
|
||||
case 'B': return YYEncodingTypeBool | qualifier;
|
||||
case 'c': return YYEncodingTypeInt8 | qualifier;
|
||||
case 'C': return YYEncodingTypeUInt8 | qualifier;
|
||||
case 's': return YYEncodingTypeInt16 | qualifier;
|
||||
case 'S': return YYEncodingTypeUInt16 | qualifier;
|
||||
case 'i': return YYEncodingTypeInt32 | qualifier;
|
||||
case 'I': return YYEncodingTypeUInt32 | qualifier;
|
||||
case 'l': return YYEncodingTypeInt32 | qualifier;
|
||||
case 'L': return YYEncodingTypeUInt32 | qualifier;
|
||||
case 'q': return YYEncodingTypeInt64 | qualifier;
|
||||
case 'Q': return YYEncodingTypeUInt64 | qualifier;
|
||||
case 'f': return YYEncodingTypeFloat | qualifier;
|
||||
case 'd': return YYEncodingTypeDouble | qualifier;
|
||||
case 'D': return YYEncodingTypeLongDouble | qualifier;
|
||||
case '#': return YYEncodingTypeClass | qualifier;
|
||||
case ':': return YYEncodingTypeSEL | qualifier;
|
||||
case '*': return YYEncodingTypeCString | qualifier;
|
||||
case '^': return YYEncodingTypePointer | qualifier;
|
||||
case '[': return YYEncodingTypeCArray | qualifier;
|
||||
case '(': return YYEncodingTypeUnion | qualifier;
|
||||
case '{': return YYEncodingTypeStruct | qualifier;
|
||||
case '@': {
|
||||
if (len == 2 && *(type + 1) == '?')
|
||||
return YYEncodingTypeBlock | qualifier;
|
||||
else
|
||||
return YYEncodingTypeObject | qualifier;
|
||||
}
|
||||
default: return YYEncodingTypeUnknown | qualifier;
|
||||
}
|
||||
}
|
||||
|
||||
@implementation YYClassIvarInfo
|
||||
|
||||
- (instancetype)initWithIvar:(Ivar)ivar {
|
||||
if (!ivar) return nil;
|
||||
self = [super init];
|
||||
_ivar = ivar;
|
||||
const char *name = ivar_getName(ivar);
|
||||
if (name) {
|
||||
_name = [NSString stringWithUTF8String:name];
|
||||
}
|
||||
_offset = ivar_getOffset(ivar);
|
||||
const char *typeEncoding = ivar_getTypeEncoding(ivar);
|
||||
if (typeEncoding) {
|
||||
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
|
||||
_type = YYEncodingGetType(typeEncoding);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation YYClassMethodInfo
|
||||
|
||||
- (instancetype)initWithMethod:(Method)method {
|
||||
if (!method) return nil;
|
||||
self = [super init];
|
||||
_method = method;
|
||||
_sel = method_getName(method);
|
||||
_imp = method_getImplementation(method);
|
||||
const char *name = sel_getName(_sel);
|
||||
if (name) {
|
||||
_name = [NSString stringWithUTF8String:name];
|
||||
}
|
||||
const char *typeEncoding = method_getTypeEncoding(method);
|
||||
if (typeEncoding) {
|
||||
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
|
||||
}
|
||||
char *returnType = method_copyReturnType(method);
|
||||
if (returnType) {
|
||||
_returnTypeEncoding = [NSString stringWithUTF8String:returnType];
|
||||
free(returnType);
|
||||
}
|
||||
unsigned int argumentCount = method_getNumberOfArguments(method);
|
||||
if (argumentCount > 0) {
|
||||
NSMutableArray *argumentTypes = [NSMutableArray new];
|
||||
for (unsigned int i = 0; i < argumentCount; i++) {
|
||||
char *argumentType = method_copyArgumentType(method, i);
|
||||
NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
|
||||
[argumentTypes addObject:type ? type : @""];
|
||||
if (argumentType) free(argumentType);
|
||||
}
|
||||
_argumentTypeEncodings = argumentTypes;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation YYClassPropertyInfo
|
||||
|
||||
- (instancetype)initWithProperty:(objc_property_t)property {
|
||||
if (!property) return nil;
|
||||
self = [super init];
|
||||
_property = property;
|
||||
const char *name = property_getName(property);
|
||||
if (name) {
|
||||
_name = [NSString stringWithUTF8String:name];
|
||||
}
|
||||
|
||||
YYEncodingType type = 0;
|
||||
unsigned int attrCount;
|
||||
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
|
||||
for (unsigned int i = 0; i < attrCount; i++) {
|
||||
switch (attrs[i].name[0]) {
|
||||
case 'T': { // Type encoding
|
||||
if (attrs[i].value) {
|
||||
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
|
||||
type = YYEncodingGetType(attrs[i].value);
|
||||
|
||||
if ((type & YYEncodingTypeMask) == YYEncodingTypeObject && _typeEncoding.length) {
|
||||
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
|
||||
if (![scanner scanString:@"@\"" intoString:NULL]) continue;
|
||||
|
||||
NSString *clsName = nil;
|
||||
if ([scanner scanUpToCharactersFromSet: [NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
|
||||
if (clsName.length) _cls = objc_getClass(clsName.UTF8String);
|
||||
}
|
||||
|
||||
NSMutableArray *protocols = nil;
|
||||
while ([scanner scanString:@"<" intoString:NULL]) {
|
||||
NSString* protocol = nil;
|
||||
if ([scanner scanUpToString:@">" intoString: &protocol]) {
|
||||
if (protocol.length) {
|
||||
if (!protocols) protocols = [NSMutableArray new];
|
||||
[protocols addObject:protocol];
|
||||
}
|
||||
}
|
||||
[scanner scanString:@">" intoString:NULL];
|
||||
}
|
||||
_protocols = protocols;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 'V': { // Instance variable
|
||||
if (attrs[i].value) {
|
||||
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
|
||||
}
|
||||
} break;
|
||||
case 'R': {
|
||||
type |= YYEncodingTypePropertyReadonly;
|
||||
} break;
|
||||
case 'C': {
|
||||
type |= YYEncodingTypePropertyCopy;
|
||||
} break;
|
||||
case '&': {
|
||||
type |= YYEncodingTypePropertyRetain;
|
||||
} break;
|
||||
case 'N': {
|
||||
type |= YYEncodingTypePropertyNonatomic;
|
||||
} break;
|
||||
case 'D': {
|
||||
type |= YYEncodingTypePropertyDynamic;
|
||||
} break;
|
||||
case 'W': {
|
||||
type |= YYEncodingTypePropertyWeak;
|
||||
} break;
|
||||
case 'G': {
|
||||
type |= YYEncodingTypePropertyCustomGetter;
|
||||
if (attrs[i].value) {
|
||||
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
|
||||
}
|
||||
} break;
|
||||
case 'S': {
|
||||
type |= YYEncodingTypePropertyCustomSetter;
|
||||
if (attrs[i].value) {
|
||||
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
|
||||
}
|
||||
} // break; commented for code coverage in next line
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (attrs) {
|
||||
free(attrs);
|
||||
attrs = NULL;
|
||||
}
|
||||
|
||||
_type = type;
|
||||
if (_name.length) {
|
||||
if (!_getter) {
|
||||
_getter = NSSelectorFromString(_name);
|
||||
}
|
||||
if (!_setter) {
|
||||
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation YYClassInfo {
|
||||
BOOL _needUpdate;
|
||||
}
|
||||
|
||||
- (instancetype)initWithClass:(Class)cls {
|
||||
if (!cls) return nil;
|
||||
self = [super init];
|
||||
_cls = cls;
|
||||
_superCls = class_getSuperclass(cls);
|
||||
_isMeta = class_isMetaClass(cls);
|
||||
if (!_isMeta) {
|
||||
_metaCls = objc_getMetaClass(class_getName(cls));
|
||||
}
|
||||
_name = NSStringFromClass(cls);
|
||||
[self _update];
|
||||
|
||||
_superClassInfo = [self.class classInfoWithClass:_superCls];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_update {
|
||||
_ivarInfos = nil;
|
||||
_methodInfos = nil;
|
||||
_propertyInfos = nil;
|
||||
|
||||
Class cls = self.cls;
|
||||
unsigned int methodCount = 0;
|
||||
Method *methods = class_copyMethodList(cls, &methodCount);
|
||||
if (methods) {
|
||||
NSMutableDictionary *methodInfos = [NSMutableDictionary new];
|
||||
_methodInfos = methodInfos;
|
||||
for (unsigned int i = 0; i < methodCount; i++) {
|
||||
YYClassMethodInfo *info = [[YYClassMethodInfo alloc] initWithMethod:methods[i]];
|
||||
if (info.name) methodInfos[info.name] = info;
|
||||
}
|
||||
free(methods);
|
||||
}
|
||||
unsigned int propertyCount = 0;
|
||||
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
|
||||
if (properties) {
|
||||
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
|
||||
_propertyInfos = propertyInfos;
|
||||
for (unsigned int i = 0; i < propertyCount; i++) {
|
||||
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
|
||||
if (info.name) propertyInfos[info.name] = info;
|
||||
}
|
||||
free(properties);
|
||||
}
|
||||
|
||||
unsigned int ivarCount = 0;
|
||||
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
|
||||
if (ivars) {
|
||||
NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
|
||||
_ivarInfos = ivarInfos;
|
||||
for (unsigned int i = 0; i < ivarCount; i++) {
|
||||
YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
|
||||
if (info.name) ivarInfos[info.name] = info;
|
||||
}
|
||||
free(ivars);
|
||||
}
|
||||
|
||||
if (!_ivarInfos) _ivarInfos = @{};
|
||||
if (!_methodInfos) _methodInfos = @{};
|
||||
if (!_propertyInfos) _propertyInfos = @{};
|
||||
|
||||
_needUpdate = NO;
|
||||
}
|
||||
|
||||
- (void)setNeedUpdate {
|
||||
_needUpdate = YES;
|
||||
}
|
||||
|
||||
- (BOOL)needUpdate {
|
||||
return _needUpdate;
|
||||
}
|
||||
|
||||
+ (instancetype)classInfoWithClass:(Class)cls {
|
||||
if (!cls) return nil;
|
||||
static CFMutableDictionaryRef classCache;
|
||||
static CFMutableDictionaryRef metaCache;
|
||||
static dispatch_once_t onceToken;
|
||||
static dispatch_semaphore_t lock;
|
||||
dispatch_once(&onceToken, ^{
|
||||
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
lock = dispatch_semaphore_create(1);
|
||||
});
|
||||
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
YYClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
|
||||
if (info && info->_needUpdate) {
|
||||
[info _update];
|
||||
}
|
||||
dispatch_semaphore_signal(lock);
|
||||
if (!info) {
|
||||
info = [[YYClassInfo alloc] initWithClass:cls];
|
||||
if (info) {
|
||||
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
|
||||
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
|
||||
dispatch_semaphore_signal(lock);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
+ (instancetype)classInfoWithClassName:(NSString *)className {
|
||||
Class cls = NSClassFromString(className);
|
||||
return [self classInfoWithClass:cls];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user