如何集成钓试?
支持CocoaPods,在 Podfile 中添加
pod 'YYModel'
絮短。支持Carthage滨巴,在 Cartfile 中添加
github "ibireme/YYModel"
择懂。在需要使用的地方只需要包含“YYModel.h”這一個頭文件就可以了霎奢。這個頭文件沒有具體內(nèi)容,只是加了一層包含關(guān)系,方便使用吉嫩。
- 一些可用的API,都在文件“NSObject+YYModel.h”中嗅定,注釋還是比較詳細(xì)的自娩。是NSObject的類別,所以對于自定義的Model可以直接使用渠退。這個和JSONModel需要一個固定基類相比在用法上要簡潔一點(diǎn)忙迁。
- 定義也在文件“NSObject+YYModel.h”中,卻是對NSArray和NSDictionary的類別碎乃。提供了方便方法姊扔,對于集合中的元素進(jìn)行JSON -》Model的轉(zhuǎn)化,將JSON對象的集合轉(zhuǎn)變?yōu)镸odel的集合梅誓。
基本用法
JSON -》Model
+ (nullable instancetype)yy_modelWithJSON:(id)json;
- 這是一個類方法恰梢,直接使用,返回一個類的實(shí)例
instancetype
梗掰,相當(dāng)于初始化函數(shù)[[xxx alloc] init];
- 輸入?yún)?shù)json的類型是id嵌言,動態(tài)類型;期望是一個JSON對象及穗,類型可以是
NSDictionary
,NSString
orNSData
. 從網(wǎng)絡(luò)上傳過來什么可以直接用摧茴,很方便。 - 不需要調(diào)用系統(tǒng)函數(shù)將JSON轉(zhuǎn)換為Dictionary埂陆,YYModel里面已經(jīng)做了這一步了苛白。
- 返回的實(shí)例
instancetype
有可能為nil
Model -》JSON
- (nullable id)yy_modelToJSONObject;
- 這是一個實(shí)例方法。實(shí)例已經(jīng)存在焚虱,將實(shí)例轉(zhuǎn)換為JSON對象丸氛,然后網(wǎng)絡(luò)傳輸,符合使用場景著摔。
- 返回值是id缓窜,動態(tài)類型;結(jié)果是一個JSON對象谍咆,類型可以是
NSDictionary
orNSArray
禾锤,符合JSON的定義(字典或者數(shù)組)。JSON 數(shù)據(jù)格式 - 返回的JSON對象有可能為
nil
實(shí)際的例子
JSON對象
// JSON:
{
"uid":123456,
"name":"Harry",
"created":"1965-07-31T00:00:00+0000"
}
Model定義
// Model:
@interface User : NSObject
@property UInt64 uid;
@property NSString *name;
@property NSDate *created;
@end
@implementation User
@end
JSON -》Model
// 將 JSON (NSData,NSString,NSDictionary) 轉(zhuǎn)換為 Model:
User *user = [User yy_modelWithJSON:json];
Model -》JSON
// 將 Model 轉(zhuǎn)換為 JSON 對象:
NSDictionary *json = [user yy_modelToJSONObject];
NSArray JSON -》Model
對于JSON對象數(shù)組的情況摹察,[{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}]
恩掷,對數(shù)組中的成員進(jìn)行轉(zhuǎn)換,得到一個Model的數(shù)組[user1, user2]
/**
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 *)yy_modelArrayWithClass:(Class)cls json:(id)json;
@end
- 這個方法對于返回JSON對象數(shù)組的網(wǎng)絡(luò)請求比較方便供嚎,這是數(shù)組的類別黄娘,可以用數(shù)組直接調(diào)用峭状,返回值就是一個Model的數(shù)組,省去了數(shù)組遍歷的步驟
- 數(shù)組的Model-》JSON的過程沒有提供方便方法
NSDictionary JSON -》Model
對于JSON對象字典的情況逼争,{"user1":{"id":12345, "name":"Mary", "created":"1965-07-31T00:00:00+0000"}, "user2": {"id":12346, "name":"Joe", "created":"1970-07-31T00:08:15+0000"}}
, 對字典中的值進(jìn)行轉(zhuǎn)換优床,key保持不變,得到一個值為Model的新字典{"user1" : user1, "user2" : user2}
/**
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 *)yy_modelDictionaryWithClass:(Class)cls json:(id)json;
@end
- 對于字典誓焦,一般會定義一個與之對應(yīng)的Model進(jìn)行互相轉(zhuǎn)換胆敞。這種將Model定義與字典的值進(jìn)行對應(yīng)的使用場景有點(diǎn)特殊。這種場景容易跟普通使用方法混淆杂伟,不推薦用移层。
- 字典的Model-》JSON的過程沒有提供方便方法
特殊情況處理
通過一個協(xié)議,方便讓使用者來指定一些特殊情況的處理方法赫粥。這些方法都是可選的观话。
/**
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
@end
不需要加
<YYModel>
是因為實(shí)現(xiàn)者(代理)是Model自己。這是特殊的用法越平。
1. Model 屬性名和 JSON 中的 Key 不相同
協(xié)議方法:
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
JSON對象:
// JSON:
{
"n":"Harry Pottery",
"p": 256,
"ext" : {
"desc" : "A book written by J.K.Rowing."
},
"ID" : 100010
}
Model定義:
// Model:
@interface Book : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation Book
//返回一個 Dict匪燕,將 Model 屬性名對映射到 JSON 的 Key。
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID" : @[@"id",@"ID",@"book_id"]};
}
@end
2. 容器類屬性(NSArray/NSSet/NSDictionary)
協(xié)議方法:
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
Model定義:
@class Shadow, Border, Attachment;
@interface Attributes
@property NSString *name;
@property NSArray *shadows; //Array<Shadow>
@property NSSet *borders; //Set<Border>
@property NSMutableDictionary *attachments; //Dict<NSString,Attachment>
@end
@implementation Attributes
// 返回容器類中的所需要存放的數(shù)據(jù)類型 (以 Class 或 Class Name 的形式)喧笔。
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"shadows" : [Shadow class],
@"borders" : Border.class,
@"attachments" : @"Attachment" };
}
@end
其他的特性比如黑白名單帽驯,轉(zhuǎn)換后的校驗,自定義字典key對應(yīng)的類等功能不是很常用书闸。
Model中包含Model的方式是支持的尼变,不需要特殊指定。對頂級類NSObeject加類別浆劲,優(yōu)勢就體現(xiàn)出來了嫌术。
對比測試
在下載的包中有一個對比測試程序,
ModelBenchmark.xcodeproj
牌借,重點(diǎn)聚焦在“性能”和“容錯”兩個方面度气。通過數(shù)據(jù)對比,還做了excel的圖表膨报,一目了然磷籍,視覺震撼比較大。同時還寫了測評博客现柠,更方便傳播院领。相對于其他方案,“性能”提升非常明顯够吩,在博客里也解釋了這點(diǎn)比然,讓人比較信服。iOS JSON 模型轉(zhuǎn)換庫評測
GitHub user的例子是簡單使用周循。weibo Model是比較復(fù)雜的例子强法,涉及key map万俗,容器類處理等核心內(nèi)容。
序列化饮怯,要實(shí)現(xiàn)NSCoding的協(xié)議函數(shù)闰歪。還有深拷貝,判斷是否相等硕淑,hash值等都是在Model定義時有可能涉及的內(nèi)容课竣。簡單但繁瑣嘉赎,通過例子也給出了簡潔的使用方法置媳。
#define YYModelSynthCoderAndHash \
- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; } \
- (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; } \
- (id)copyWithZone:(NSZone *)zone { return [self yy_modelCopy]; } \
- (NSUInteger)hash { return [self yy_modelHash]; } \
- (BOOL)isEqual:(id)object { return [self yy_modelIsEqual:object]; }
在實(shí)際使用中,用上面這個宏公条,不鼓勵也不反對拇囊。
根據(jù)實(shí)際情況,實(shí)現(xiàn)上面的函數(shù)靶橱。使用YYModel后寥袭,只要一句話調(diào)用就好了,帶來了比較大的便利关霸。
在測試“容錯”那部分传黄,logError是一個變量,是一個block队寇,相當(dāng)于一個匿名函數(shù)膘掰。在這個場景中,比有名函數(shù)調(diào)用要靈活很多佳遣。在log中识埋,通過?等字符很形象啊。
對于崩潰的情況零渐,使用了
try catch
結(jié)構(gòu)窒舟。通過對比,可以看出FastEasyMapping
和JSONModel
“容錯”性能比較差诵盼,如果后臺數(shù)據(jù)返回錯誤惠豺,很容易崩潰。YYModel
的“容錯”性能是最好的风宁,并且“性能”有5~10倍的提升耕腾,這兩個庫是可以考慮替換掉。在“性能”測試中杀糯,用了
CACurrentMediaTime()
這個函數(shù)來統(tǒng)計時間扫俺,將for
循環(huán)放在一個@autoreleasepool
中。相當(dāng)于[[NSDate data] timeIntervalSinceReferenceDate];``, 在
QuartzCore`框架中固翰。
NSDate 狼纬、CFAbsoluteTimeGetCurrent羹呵、CACurrentMediaTime 的區(qū)別
begin = CACurrentMediaTime();
@autoreleasepool {
for (int i = 0; i < count; i++) {
NSDictionary *json = [user yy_modelToJSONObject];
[holder addObject:json];
}
}
end = CACurrentMediaTime();
- 在一個大函數(shù)中,通過
{}
進(jìn)行分塊疗琉。有時候switch
語句編譯不過冈欢,將case
下面的內(nèi)容包起來就好了∮颍基本上用到的場合不多凑耻。
數(shù)據(jù)結(jié)構(gòu)
iOS開發(fā)-Runtime詳解
Objective-C Runtime 運(yùn)行時之三:方法與消息
Objective-C中的一些特殊的數(shù)據(jù)類型 id、nil柠贤、Nil香浩、SEL
id
在文件objc/objc.h
中
/// A pointer to an instance of a class.
typedef struct objc_object *id;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
Class
在文件objc/objc.h
中
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
在文件objc/runtime.h
中
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統(tǒng)運(yùn)行版本進(jìn)行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本臼勉,但我們?nèi)阅軓闹蝎@取一些有用信息邻吭。
OC之OBJC2_UNAVAILABLE
SEL
在文件objc/objc.h
中
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
- 結(jié)構(gòu)體
objc_selector
的定義找不到 - 可以將SEL理解為方法名的hash值,可以加快方法的查找速度
- C中函數(shù)名是函數(shù)實(shí)現(xiàn)的地址宴霸,而SEL只跟函數(shù)名有關(guān)囱晴,不涉及函數(shù)實(shí)現(xiàn)的地址。將函數(shù)名和函數(shù)實(shí)現(xiàn)分離瓢谢,可以實(shí)現(xiàn)函數(shù)交換等功能畸写。
- SEL只跟方法名有關(guān),跟參數(shù)無關(guān)氓扛,沒有C++中的函數(shù)重載功能
- Opaque Types
IMP
在文件objc/objc.h
中
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
Method
在文件objc/runtime.h
中
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
方法:SEL(函數(shù)名)枯芬、IMP(函數(shù)實(shí)現(xiàn))、method_types(參數(shù))的統(tǒng)一體幢尚。
Ivar
在文件objc/runtime.h
中
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
objc_property_t
在文件objc/runtime.h
中
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
-
Property
沒有找到 -
struct objc_property
定義沒有找到
Meta Class
- 實(shí)例對象(instance)破停,類(class),元類(meta class) 三者之間的關(guān)系通過isa指針聯(lián)系起來
- 類(class)存實(shí)例方法(- 開頭的方法)
-
元類(meta class)存類方法(+ 開頭的方法)
- 每個Class都有一個isa指針指向一個唯一的Meta Class
- 每一個Meta Class的isa指針都指向最上層的Meta Class
- 最上層的Meta Class的isa指針指向自己尉剩,形成一個回路
- 每一個Meta Class的super class指針指向它原本Class的 Super Class的Meta Class真慢。但是最上層的Meta Class的 Super Class指向NSObject Class本身
- 最上層的NSObject Class的super class指向 nil
深入淺出Cocoa之類與對象
[Cocoa]深入淺出Cocoa 之動態(tài)創(chuàng)建類
刨根問底Objective-C Runtime
YYClassInfo文件
根據(jù)原生的數(shù)據(jù)結(jié)構(gòu),進(jìn)行的類抽象理茎。
YYEncodingType
- 自定義的類型黑界,是一種
NS_OPTIONS
- 將類型,修飾符等信息整合在一個變量中皂林,效率較高朗鸠。總共用到了3個字節(jié)础倍。由不同的mask來整合烛占。
-
YYEncodingType YYEncodingGetType(const char *typeEncoding);
這個全局函數(shù)用來將字符轉(zhuǎn)化為自定義的類型 - Type Encoding
- Declared Properties
YYClassIvarInfo
- 對應(yīng)
Ivar
數(shù)據(jù)結(jié)構(gòu) - 構(gòu)建方法也是從
Ivar
作為輸入?yún)?shù)
- (instancetype)initWithIvar:(Ivar)ivar;
- 用到的系統(tǒng)API
const char *ivar_getName(Ivar v);
const char *ivar_getTypeEncoding(Ivar v);
ptrdiff_t ivar_getOffset(Ivar v);
ptrdiff_t是C/C++標(biāo)準(zhǔn)庫中定義的一個與機(jī)器相關(guān)的數(shù)據(jù)類型。ptrdiff_t類型變量通常用來保存兩個指針減法操作的結(jié)果。ptrdiff_t定義在stddef.h(cstddef)這個文件內(nèi)忆家。ptrdiff_t通常被定義為long int類型犹菇。
ptrdiff_t定義在C99標(biāo)準(zhǔn)中。
YYClassMethodInfo
- 對應(yīng)
Method
數(shù)據(jù)結(jié)構(gòu) -
- (instancetype)initWithMethod:(Method)method;
通過Method
創(chuàng)建 - 用到的系統(tǒng)API
SEL method_getName(Method m);
IMP method_getImplementation(Method m);
const char *method_getTypeEncoding(Method m);
char *method_copyReturnType(Method m);
unsigned int method_getNumberOfArguments(Method m);
char *method_copyArgumentType(Method m, unsigned int index);
YYClassPropertyInfo
- 對應(yīng)
struct objc_property_t
數(shù)據(jù)結(jié)構(gòu) - (instancetype)initWithProperty:(objc_property_t)property;
- 用到的系統(tǒng)API
const char *property_getName(objc_property_t property) ;
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount);
SEL NSSelectorFromString(NSString *aSelectorName);
- 如果是類芽卿,通過函數(shù)
Class objc_getClass(const char *name);
獲取屬性的類型信息 - getter和setter函數(shù)揭芍,如果沒有,會生成默認(rèn)的
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter) {
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
YYClassInfo
- 對應(yīng)
Class
數(shù)據(jù)結(jié)構(gòu) + (instancetype)classInfoWithClass:(Class)cls;
- 這里用了兩個靜態(tài)的字典來存儲類信息卸例。key是
Class
称杨,value是YYClassInfo
】曜考慮到類型嵌套姑原,會有一大堆的類型信息需要保存。這里用了Core Fountdation的字典旦装。這兩個字典是單例页衙。
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
- 這里也用到了線程保護(hù)摊滔,使用的GCD
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);
}
- 用到的系統(tǒng)API
Class class_getSuperclass(Class cls) ;
BOOL class_isMetaClass(Class cls);
Class objc_getMetaClass(const char *name);
const char *class_getName(Class cls);
NSString *NSStringFromClass(Class aClass);
Method *class_copyMethodList(Class cls, unsigned int *outCount);
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount);
Ivar *class_copyIvarList(Class cls, unsigned int *outCount);
NSObject+YYModel文件
YYEncodingNSType
- 這是NS_ENUM阴绢,簡單枚舉,沒有位操作
- 對應(yīng)屬性中的Foundation類型艰躺,比如NSString呻袭,NSNumber等
- YYEncodingType是包括基本類型,比如int腺兴,double等
- YYEncodingType中的YYEncodingTypeObject進(jìn)一步細(xì)分左电,可以得到相應(yīng)的YYEncodingNSType
- 相關(guān)的靜態(tài)全局函數(shù)是
static force_inline YYEncodingNSType YYClassGetNSType(Class cls);
YYNSDateFromString
- 這是一個靜態(tài)全局函數(shù)
- 功能是將NSString轉(zhuǎn)化為對應(yīng)的NSDate
- 由于日期format的種類很多,這里引入了一個block的數(shù)組
- 將NSString的字符個數(shù)作為數(shù)據(jù)的標(biāo)號页响,這種思維比較奇特
typedef NSDate* (^YYNSDateParseBlock)(NSString *string);
#define kParserNum 34
static YYNSDateParseBlock blocks[kParserNum + 1] = {0};
static dispatch_once_t onceToken;
....
YYNSDateParseBlock parser = blocks[string.length];
if (!parser) return nil;
return parser(string);
#undef kParserNum
_YYModelPropertyMeta
- 對
YYClassPropertyInfo
進(jìn)一步信息整合 - 這里對屬性的類型進(jìn)行判斷:
是一個Foundation類型篓足,還是一個C數(shù)值類型,或者是一個容器類型等
_YYModelMeta
- 對
YYClassInfo
進(jìn)一步信息整合` - 遍歷類所有的屬性闰蚕,直到根類
// 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;
- 處理代理函數(shù)
modelPropertyBlacklist
---黑名單 - 處理代理函數(shù)
modelPropertyWhitelist
---白名單 - 處理代理函數(shù)
modelContainerPropertyGenericClass
---容器類型指定栈拖,支持class類型和字符串的類名 - 將類所有的屬性轉(zhuǎn)換為_YYModelPropertyMeta數(shù)組
- 處理代理函數(shù)
modelCustomPropertyMapper
---屬性和JSON鍵名稱的對應(yīng)關(guān)系。這種對應(yīng)關(guān)系支持.格式的鏈?zhǔn)疥P(guān)系和一對多的數(shù)組没陡。保存在相應(yīng)的_YYModelPropertyMeta
成員中涩哟。 - 做標(biāo)記,判斷用戶是否自定義了以下協(xié)議函數(shù):
modelCustomWillTransformFromDictionary
modelCustomTransformFromDictionary
modelCustomTransformToDictionary
modelCustomClassForDictionary
ModelSetContext
typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;
- 這是一個結(jié)構(gòu)體
- 將類信息(_YYModelMeta)盼玄,類(model)贴彼,JSON(NSDictionary)等放在一起。
- model --- modelMeta --- dictionary埃儿;相互轉(zhuǎn)化的兩種結(jié)構(gòu)通過一個中間過渡數(shù)據(jù)結(jié)構(gòu)器仗,整合在一起
- 類型都是void *,是C的指針
函數(shù)調(diào)用流程
JSON -》Model
- 起點(diǎn):
+ (nullable instancetype)yy_modelWithJSON:(id)json;
- 將JSON對象轉(zhuǎn)換為字典:
+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json
這里用到了系統(tǒng)的JSON轉(zhuǎn)字典API:
+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
- 字典轉(zhuǎn)模型:
+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
- 設(shè)置模型屬性:
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic;
- 利用函數(shù)
CFDictionaryApplyFunction
調(diào)用static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context);
或者童番,利用函數(shù)CFArrayApplyFunction
調(diào)用static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context);
- 上面兩個函數(shù)精钮,都調(diào)用全局函數(shù):
static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _YYModelPropertyMeta *meta);
所有的實(shí)際工作都在這里
Model -》JSON
- API函數(shù):
- (nullable id)yy_modelToJSONObject;
id的類型是
NSDictionary
orNSArray
暴心,根據(jù)實(shí)際情況指定
- 如果需要
NSDictionary
orNSArray
的結(jié)果,那么調(diào)用另外兩個API:
- (nullable NSData *)yy_modelToJSONData;
- (nullable NSString *)yy_modelToJSONString;
都是基于
- (nullable id)yy_modelToJSONObject;
的結(jié)果做格式轉(zhuǎn)換杂拨,這里用到了系統(tǒng)API:+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
- 實(shí)際的工作在全局函數(shù)
static id ModelToJSONObjectRecursive(NSObject *model);
中完成
幾個知識點(diǎn)
objc_msgSend
- Object-C中的消息轉(zhuǎn)發(fā)专普,最終都要轉(zhuǎn)換為對函數(shù)
objc_msgSend
的調(diào)用 - 調(diào)用
objc_msgSend
時,要強(qiáng)制轉(zhuǎn)換為具體的函數(shù)指針 - 高效編寫代碼的方法(九):了解objc_msgSend
- 調(diào)用objc_msgSend警告處理
- Dispatch Objective-C Messages Using the Method Function’s Prototype
//objc_msgSend(self,selector,@"test");
((void(*)(id, SEL, id))objc_msgSend)(self, selector, @"test");```
- (int) doSomething:(int) x { ... }
- (void) doSomethingElse {
int (action)(id, SEL, int) = (int ()(id, SEL, int)) objc_msgSend;
action(self, @selector(doSomething:), 0);
}```
- 這里調(diào)用的時候多了一個
(void *)
弹沽,多了一步強(qiáng)制轉(zhuǎn)換檀夹,本質(zhì)上都是為了解決64位硬件上崩潰的的問題
((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);```
double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);```
__bridge
- 在ARC下,將Object-C指針轉(zhuǎn)換為C指針
- Object-C 指針 和 C 指針的相互轉(zhuǎn)換 與ARC 并驗證__bridge關(guān)鍵字的作用
ModelSetContext context = {0};
context.modelMeta = (__bridge void *)(modelMeta);
context.model = (__bridge void *)(self);
context.dictionary = (__bridge void *)(dic);
ModelSetContext是一個C的struct
ModelSetValueForProperty
作用:將JSON(根式是Dictionary)的value(類型是id)賦值給Model的屬性
方式:通過setter函數(shù)來實(shí)現(xiàn)策橘,objc_msgSend
的SEL
參數(shù)基本上都是meta->_setter
case1:數(shù)字類型
- 實(shí)現(xiàn)過程在全局函數(shù)中
ModelSetNumberToProperty
- 細(xì)分為布爾型炸渡,8位,16位丽已,32位蚌堵,64位整數(shù),浮點(diǎn)數(shù)等等各種具體類型
case2:Foundation類型
Model屬性類型是
NSString
或者NSMutableString
沛婴;對value(id)
的具體類型做了容錯處理:
** NSString:直接設(shè)置
** NSMutableString:轉(zhuǎn)化為NSString
之后吼畏,調(diào)用mutableCopy
NSNumber:取屬性stringValue
** NSData:**轉(zhuǎn)化為NSString
NSURL:取屬性absoluteString
NSAttributedString:取屬性string
Model屬性類型是
NSDecimalNumber
;對value(id)
的類型是NSDecimalNumber
嘁灯、NSNumber
泻蚊、NSString
的情況做了容錯處理Model屬性類型是
NSData
;對value(id)
的類型是NSString
的情況做了容錯處理Model屬性類型是
NSDate
丑婿;對value(id)
的類型是NSString
的情況做了容錯處理Model屬性類型是
NSURL
性雄;對value(id)
的類型是NSString
的情況做了容錯處理Model屬性類型是
NSDictionary
、NSSet
羹奉、NSArray
等容器類型時秒旋,對容器中每個成員調(diào)用函數(shù)yy_modelSetWithDictionary
,一層層深入下去诀拭。
case3:其他類型
都對value為nil的情況做了處理
Model屬性類型是
id
對象類型時迁筛,調(diào)用函數(shù)yy_modelSetWithDictionary
,一層層深入下去炫加。Model屬性類型是
Class
類型時瑰煎,如果value是NSString
,則調(diào)用函數(shù)NSClassFromString
進(jìn)行轉(zhuǎn)化俗孝,然后設(shè)置酒甸。如果是其他類型,則判斷其“元類”class_isMetaClass
是否存在赋铝。存在插勤,則直接設(shè)置Model屬性類型是
SEL
類型時,如果value是NSString
,則調(diào)用函數(shù)NSSelectorFromString
進(jìn)行轉(zhuǎn)化农尖,然后設(shè)置析恋。Model屬性類型是
Block
類型時,將value強(qiáng)制轉(zhuǎn)換為void (^)()
進(jìn)行設(shè)置盛卡。Model屬性類型是
struct
助隧、union
、char[10]
等類型時滑沧,如果value是NSValue
并村,則調(diào)用函數(shù)- (void)setValue:(nullable id)value forKey:(NSString *)key;
進(jìn)行設(shè)置。Model屬性類型是
void*
滓技、char*
等類型時哩牍,如果value是NSValue
,則將value強(qiáng)制轉(zhuǎn)換為NSValue
令漂,取屬性pointerValue
進(jìn)行設(shè)置膝昆。進(jìn)行設(shè)置。
個人意見
- 看上去只有兩個文件叠必,但是類有很多荚孵,用了很多的內(nèi)部類。這種方式不是很認(rèn)同挠唆。還是推薦一個類一個源文件的方式处窥。當(dāng)然嘱吗,這里的場景是高內(nèi)聚的一個整體玄组,本來也是把一個類的各子成員(都是
struct
),還是比較合適的谒麦。 - 將所有的頭文件都?xì)w總為一個
YYMode.h
俄讹,這種方式是非常好的,推薦使用绕德。 - 對于NSArray這種集合提供方便方法患膛;對于JSON對象,采用id類型耻蛇,支持NSDictionary, NSString, NSData三種類型踪蹬;在處理的時候,統(tǒng)一為NSDictionary臣咖。這種方式跃捣,統(tǒng)籌考慮了實(shí)現(xiàn)和使用的方便性,只是增加了幾層函數(shù)調(diào)用夺蛇,值得推薦疚漆。
- 協(xié)議的定義、使用者、實(shí)現(xiàn)者都是同一個(self娶聘,Model自己)闻镶,這里是特殊的使用場景。
一般情況下應(yīng)該分3個文件(協(xié)議定義丸升,使用者铆农,實(shí)現(xiàn)者)或者2個文件(協(xié)議的定義放在使用者的文件中)。 - 采用協(xié)議的設(shè)計方式狡耻,讓使用者對特殊使用場景做自定義顿涣,值得推薦
- 將_YYModelMeta(runtime中各種struct對應(yīng)的類)作為轉(zhuǎn)換的中間載體,思維很巧妙
-
if
后面只有一個語句酝豪,省略了{}
涛碑,這種習(xí)慣不是很好。
if (num) [num class]; // hold the number
參考文章
YYModel
iOS JSON 模型轉(zhuǎn)換庫評測
JSON 數(shù)據(jù)格式
Objective-C Runtime Programming Guide