Browse Source

Merge pull request #5 from digal/custom-model-class

Ability to choose custom model (sub-)class based on dictionary Data
Yaoyuan 9 năm trước cách đây
mục cha
commit
6cd58464ae
2 tập tin đã thay đổi với 75 bổ sung6 xóa
  1. 34 0
      YYModel/NSObject+YYModel.h
  2. 41 6
      YYModel/NSObject+YYModel.m

+ 34 - 0
YYModel/NSObject+YYModel.h

@@ -319,6 +319,40 @@
  */
 + (NSDictionary *)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.
+
+ */
++ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary;
+
 /**
  All the properties in blacklist will be ignored in model transform process.
  Returns nil to ignore this feature.

+ 41 - 6
YYModel/NSObject+YYModel.m

@@ -292,7 +292,7 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
     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 _hasCustomClassFromDictionary; ///< class/generic class implements +modelCustomClassForDictionary:
     NSString *_mappedToKey;      ///< the key mapped to
     NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
     _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
@@ -311,6 +311,14 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
         meta->_isCNumber = YYEncodingTypeIsCNumber(meta->_type);
     }
     meta->_cls = propertyInfo.cls;
+
+    if (generic) {
+        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
+    } else if (meta->_cls && meta->_type == YYEncodingTypeNSUnknown) {
+        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
+    }
+
+    
     if (propertyInfo.getter) {
         SEL sel = NSSelectorFromString(propertyInfo.getter);
         if ([classInfo.cls instancesRespondToSelector:sel]) {
@@ -323,6 +331,7 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
             meta->_setter = sel;
         }
     }
+    
     return meta;
 }
 @end
@@ -344,6 +353,7 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
     
     BOOL _hasCustomTransformFromDictionary;
     BOOL _hasCustomTransformToDictionary;
+    BOOL _hasCustomClassFromDictionary;
 }
 @end
 
@@ -452,6 +462,7 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
     _nsType = YYClassGetNSType(cls);
     _hasCustomTransformFromDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)]);
     _hasCustomTransformToDictionary = ([cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)]);
+    _hasCustomClassFromDictionary = ([cls respondsToSelector:@selector(modelCustomClassForDictionary:)]);
     
     return self;
 }
@@ -739,7 +750,12 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                                 if ([one isKindOfClass:meta->_genericCls]) {
                                     [objectArr addObject:one];
                                 } else if ([one isKindOfClass:[NSDictionary class]]) {
-                                    NSObject *newOne = [meta->_genericCls new];
+                                    Class clazz = meta->_genericCls;
+                                    if (meta->_hasCustomClassFromDictionary) {
+                                        clazz = [clazz modelCustomClassForDictionary:one] ?: clazz;
+                                    }
+                                    
+                                    NSObject *newOne = [clazz new];
                                     [newOne yy_modelSetWithDictionary:one];
                                     if (newOne) [objectArr addObject:newOne];
                                 }
@@ -778,7 +794,12 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             NSMutableDictionary *dic = [NSMutableDictionary new];
                             [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, NSObject *oneValue, BOOL *stop) {
                                 if ([oneValue isKindOfClass:[NSDictionary class]]) {
-                                    NSObject *o = [meta->_genericCls new];
+                                    Class clazz = meta->_genericCls;
+                                    if (meta->_hasCustomClassFromDictionary) {
+                                        clazz = [clazz modelCustomClassForDictionary:oneValue] ?: clazz;
+                                    }
+
+                                    NSObject *o = [clazz new];
                                     [o yy_modelSetWithDictionary:(id)oneValue];
                                     if (o) dic[oneKey] = o;
                                 }
@@ -810,7 +831,11 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             if ([one isKindOfClass:meta->_genericCls]) {
                                 [set addObject:one];
                             } else if ([one isKindOfClass:[NSDictionary class]]) {
-                                NSObject *newOne = [meta->_genericCls new];
+                                Class clazz = meta->_genericCls;
+                                if (meta->_hasCustomClassFromDictionary) {
+                                    clazz = [clazz modelCustomClassForDictionary:one] ?: clazz;
+                                }
+                                NSObject *newOne = [clazz new];
                                 [newOne yy_modelSetWithDictionary:one];
                                 if (newOne) [set addObject:newOne];
                             }
@@ -848,7 +873,11 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                     if (one) {
                         [one yy_modelSetWithDictionary:value];
                     } else {
-                        one = [meta->_cls new];
+                        Class clazz = meta->_cls;
+                        if (meta->_hasCustomClassFromDictionary) {
+                            clazz = [clazz modelCustomClassForDictionary:value] ?: clazz;
+                        }
+                        one = [clazz new];
                         [one yy_modelSetWithDictionary:value];
                         ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                     }
@@ -1100,7 +1129,13 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
 + (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
     if (!dictionary || dictionary == (id)kCFNull) return nil;
     if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
-    NSObject *one = [self new];
+    Class clazz = [self class];
+    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:clazz];
+    if (modelMeta->_hasCustomClassFromDictionary) {
+        clazz = [clazz modelCustomClassForDictionary:dictionary] ?: clazz;
+    }
+
+    NSObject *one = [clazz new];
     [one yy_modelSetWithDictionary:dictionary];
     return one;
 }