Browse Source

unit test with almost 100% code coverage

ibireme 9 years ago
parent
commit
13cc597951

+ 2 - 1
.travis.yml

@@ -4,4 +4,5 @@ xcode_project: Framework/YYModel.xcodeproj
 xcode_scheme: YYModel
 
 script:
-- xctool -project Framework/YYModel.xcodeproj -scheme YYModel build test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
+- xctool -project Framework/YYModel.xcodeproj -scheme YYModel build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
+- xctool -project Framework/YYModel.xcodeproj -scheme YYModel test -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO

+ 157 - 3
Framework/YYModel.xcodeproj/project.pbxproj

@@ -7,14 +7,50 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		AB1DAC8F1C0AF02B00442613 /* YYTestModelToJSON.m in Sources */ = {isa = PBXBuildFile; fileRef = AB1DAC8E1C0AF02B00442613 /* YYTestModelToJSON.m */; };
+		ABA06CAC1C08514D00AD2108 /* YYModel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9D41A071BD0FABE00CD8EBF /* YYModel.framework */; };
+		ABA06CB21C08566100AD2108 /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A171BD0FB3300CD8EBF /* NSObject+YYModel.m */; };
+		ABA06CB31C08566100AD2108 /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A191BD0FB3300CD8EBF /* YYClassInfo.m */; };
+		ABA06CBA1C08595900AD2108 /* YYTestModelMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = ABA06CB91C08595900AD2108 /* YYTestModelMapper.m */; };
+		ABA06CBF1C085A6D00AD2108 /* YYTestClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = ABA06CBE1C085A6D00AD2108 /* YYTestClassInfo.m */; };
+		ABFEC7191C0BE7A900B3D8C5 /* YYTestCustomTransform.m in Sources */ = {isa = PBXBuildFile; fileRef = ABFEC7181C0BE7A900B3D8C5 /* YYTestCustomTransform.m */; };
+		ABFEC71B1C0BF23200B3D8C5 /* YYTestCustomClass.m in Sources */ = {isa = PBXBuildFile; fileRef = ABFEC71A1C0BF23200B3D8C5 /* YYTestCustomClass.m */; };
+		ABFEC71D1C0C284B00B3D8C5 /* YYTestNestModel.m in Sources */ = {isa = PBXBuildFile; fileRef = ABFEC71C1C0C284B00B3D8C5 /* YYTestNestModel.m */; };
+		D95943EE1C0B46B6002D88BD /* YYTestCopyingAndCoding.m in Sources */ = {isa = PBXBuildFile; fileRef = D95943ED1C0B46B6002D88BD /* YYTestCopyingAndCoding.m */; };
+		D95943F01C0B6467002D88BD /* YYTestBlacklistWhitelist.m in Sources */ = {isa = PBXBuildFile; fileRef = D95943EF1C0B6467002D88BD /* YYTestBlacklistWhitelist.m */; };
+		D9B26FF61C08D13F004880F0 /* YYTestHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D9B26FF51C08D13F004880F0 /* YYTestHelper.m */; };
+		D9B270271C09FDF8004880F0 /* YYTestAutoTypeConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = D9B270261C09FDF8004880F0 /* YYTestAutoTypeConvert.m */; };
 		D9D41A1B1BD0FB3300CD8EBF /* NSObject+YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = D9D41A161BD0FB3300CD8EBF /* NSObject+YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		D9D41A1C1BD0FB3300CD8EBF /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A171BD0FB3300CD8EBF /* NSObject+YYModel.m */; settings = {ASSET_TAGS = (); }; };
+		D9D41A1C1BD0FB3300CD8EBF /* NSObject+YYModel.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A171BD0FB3300CD8EBF /* NSObject+YYModel.m */; };
 		D9D41A1D1BD0FB3300CD8EBF /* YYClassInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = D9D41A181BD0FB3300CD8EBF /* YYClassInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		D9D41A1E1BD0FB3300CD8EBF /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A191BD0FB3300CD8EBF /* YYClassInfo.m */; settings = {ASSET_TAGS = (); }; };
+		D9D41A1E1BD0FB3300CD8EBF /* YYClassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = D9D41A191BD0FB3300CD8EBF /* YYClassInfo.m */; };
 		D9D41A1F1BD0FB3300CD8EBF /* YYModel.h in Headers */ = {isa = PBXBuildFile; fileRef = D9D41A1A1BD0FB3300CD8EBF /* YYModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		ABA06CAD1C08514D00AD2108 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = D9D419FE1BD0FABE00CD8EBF /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = D9D41A061BD0FABE00CD8EBF;
+			remoteInfo = YYModel;
+		};
+/* End PBXContainerItemProxy section */
+
 /* Begin PBXFileReference section */
+		AB1DAC8E1C0AF02B00442613 /* YYTestModelToJSON.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestModelToJSON.m; sourceTree = "<group>"; };
+		ABA06CA71C08514D00AD2108 /* YYModelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YYModelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		ABA06CB51C08589300AD2108 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		ABA06CB91C08595900AD2108 /* YYTestModelMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestModelMapper.m; sourceTree = "<group>"; };
+		ABA06CBE1C085A6D00AD2108 /* YYTestClassInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestClassInfo.m; sourceTree = "<group>"; };
+		ABFEC7181C0BE7A900B3D8C5 /* YYTestCustomTransform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestCustomTransform.m; sourceTree = "<group>"; };
+		ABFEC71A1C0BF23200B3D8C5 /* YYTestCustomClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestCustomClass.m; sourceTree = "<group>"; };
+		ABFEC71C1C0C284B00B3D8C5 /* YYTestNestModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestNestModel.m; sourceTree = "<group>"; };
+		D95943ED1C0B46B6002D88BD /* YYTestCopyingAndCoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestCopyingAndCoding.m; sourceTree = "<group>"; };
+		D95943EF1C0B6467002D88BD /* YYTestBlacklistWhitelist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestBlacklistWhitelist.m; sourceTree = "<group>"; };
+		D9B26FF41C08D13F004880F0 /* YYTestHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YYTestHelper.h; sourceTree = "<group>"; };
+		D9B26FF51C08D13F004880F0 /* YYTestHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestHelper.m; sourceTree = "<group>"; };
+		D9B270261C09FDF8004880F0 /* YYTestAutoTypeConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YYTestAutoTypeConvert.m; sourceTree = "<group>"; };
 		D9D41A071BD0FABE00CD8EBF /* YYModel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YYModel.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		D9D41A121BD0FADC00CD8EBF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		D9D41A161BD0FB3300CD8EBF /* NSObject+YYModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+YYModel.h"; sourceTree = "<group>"; };
@@ -25,6 +61,14 @@
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
+		ABA06CA41C08514D00AD2108 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				ABA06CAC1C08514D00AD2108 /* YYModel.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		D9D41A031BD0FABE00CD8EBF /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -35,10 +79,31 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		ABA06CB41C08589300AD2108 /* YYModelTests */ = {
+			isa = PBXGroup;
+			children = (
+				D9B26FF41C08D13F004880F0 /* YYTestHelper.h */,
+				D9B26FF51C08D13F004880F0 /* YYTestHelper.m */,
+				ABA06CBE1C085A6D00AD2108 /* YYTestClassInfo.m */,
+				ABA06CB91C08595900AD2108 /* YYTestModelMapper.m */,
+				ABFEC71C1C0C284B00B3D8C5 /* YYTestNestModel.m */,
+				D9B270261C09FDF8004880F0 /* YYTestAutoTypeConvert.m */,
+				AB1DAC8E1C0AF02B00442613 /* YYTestModelToJSON.m */,
+				D95943ED1C0B46B6002D88BD /* YYTestCopyingAndCoding.m */,
+				D95943EF1C0B6467002D88BD /* YYTestBlacklistWhitelist.m */,
+				ABFEC7181C0BE7A900B3D8C5 /* YYTestCustomTransform.m */,
+				ABFEC71A1C0BF23200B3D8C5 /* YYTestCustomClass.m */,
+				ABA06CB51C08589300AD2108 /* Info.plist */,
+			);
+			name = YYModelTests;
+			path = ../YYModelTests;
+			sourceTree = "<group>";
+		};
 		D9D419FD1BD0FABE00CD8EBF = {
 			isa = PBXGroup;
 			children = (
 				D9D41A151BD0FB3300CD8EBF /* YYModel */,
+				ABA06CB41C08589300AD2108 /* YYModelTests */,
 				D9D41A141BD0FB1900CD8EBF /* Supporting Files */,
 				D9D41A081BD0FABE00CD8EBF /* Products */,
 			);
@@ -48,6 +113,7 @@
 			isa = PBXGroup;
 			children = (
 				D9D41A071BD0FABE00CD8EBF /* YYModel.framework */,
+				ABA06CA71C08514D00AD2108 /* YYModelTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -89,6 +155,24 @@
 /* End PBXHeadersBuildPhase section */
 
 /* Begin PBXNativeTarget section */
+		ABA06CA61C08514D00AD2108 /* YYModelTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = ABA06CB11C08514D00AD2108 /* Build configuration list for PBXNativeTarget "YYModelTests" */;
+			buildPhases = (
+				ABA06CA31C08514D00AD2108 /* Sources */,
+				ABA06CA41C08514D00AD2108 /* Frameworks */,
+				ABA06CA51C08514D00AD2108 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				ABA06CAE1C08514D00AD2108 /* PBXTargetDependency */,
+			);
+			name = YYModelTests;
+			productName = YYModelTests;
+			productReference = ABA06CA71C08514D00AD2108 /* YYModelTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 		D9D41A061BD0FABE00CD8EBF /* YYModel */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = D9D41A0F1BD0FABE00CD8EBF /* Build configuration list for PBXNativeTarget "YYModel" */;
@@ -113,9 +197,12 @@
 		D9D419FE1BD0FABE00CD8EBF /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 0700;
+				LastUpgradeCheck = 0710;
 				ORGANIZATIONNAME = ibireme;
 				TargetAttributes = {
+					ABA06CA61C08514D00AD2108 = {
+						CreatedOnToolsVersion = 7.1;
+					};
 					D9D41A061BD0FABE00CD8EBF = {
 						CreatedOnToolsVersion = 7.0;
 					};
@@ -134,11 +221,19 @@
 			projectRoot = "";
 			targets = (
 				D9D41A061BD0FABE00CD8EBF /* YYModel */,
+				ABA06CA61C08514D00AD2108 /* YYModelTests */,
 			);
 		};
 /* End PBXProject section */
 
 /* Begin PBXResourcesBuildPhase section */
+		ABA06CA51C08514D00AD2108 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		D9D41A051BD0FABE00CD8EBF /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -149,6 +244,25 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
+		ABA06CA31C08514D00AD2108 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				ABA06CBF1C085A6D00AD2108 /* YYTestClassInfo.m in Sources */,
+				ABFEC7191C0BE7A900B3D8C5 /* YYTestCustomTransform.m in Sources */,
+				D95943F01C0B6467002D88BD /* YYTestBlacklistWhitelist.m in Sources */,
+				ABA06CB21C08566100AD2108 /* NSObject+YYModel.m in Sources */,
+				ABA06CBA1C08595900AD2108 /* YYTestModelMapper.m in Sources */,
+				D9B26FF61C08D13F004880F0 /* YYTestHelper.m in Sources */,
+				ABFEC71D1C0C284B00B3D8C5 /* YYTestNestModel.m in Sources */,
+				D9B270271C09FDF8004880F0 /* YYTestAutoTypeConvert.m in Sources */,
+				ABA06CB31C08566100AD2108 /* YYClassInfo.m in Sources */,
+				ABFEC71B1C0BF23200B3D8C5 /* YYTestCustomClass.m in Sources */,
+				D95943EE1C0B46B6002D88BD /* YYTestCopyingAndCoding.m in Sources */,
+				AB1DAC8F1C0AF02B00442613 /* YYTestModelToJSON.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		D9D41A021BD0FABE00CD8EBF /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -160,7 +274,38 @@
 		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		ABA06CAE1C08514D00AD2108 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = D9D41A061BD0FABE00CD8EBF /* YYModel */;
+			targetProxy = ABA06CAD1C08514D00AD2108 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin XCBuildConfiguration section */
+		ABA06CAF1C08514D00AD2108 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				INFOPLIST_FILE = ../YYModelTests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				ONLY_ACTIVE_ARCH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = com.ibireme.YYModelTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		ABA06CB01C08514D00AD2108 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				INFOPLIST_FILE = ../YYModelTests/Info.plist;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				PRODUCT_BUNDLE_IDENTIFIER = com.ibireme.YYModelTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
 		D9D41A0D1BD0FABE00CD8EBF /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
@@ -286,6 +431,15 @@
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
+		ABA06CB11C08514D00AD2108 /* Build configuration list for PBXNativeTarget "YYModelTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				ABA06CAF1C08514D00AD2108 /* Debug */,
+				ABA06CB01C08514D00AD2108 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		D9D41A011BD0FABE00CD8EBF /* Build configuration list for PBXProject "YYModel" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 22 - 2
Framework/YYModel.xcodeproj/xcshareddata/xcschemes/YYModel.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0700"
+   LastUpgradeVersion = "0710"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -26,9 +26,29 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES">
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
       <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ABA06CA61C08514D00AD2108"
+               BuildableName = "YYModelTests.xctest"
+               BlueprintName = "YYModelTests"
+               ReferencedContainer = "container:YYModel.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
       </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "D9D41A061BD0FABE00CD8EBF"
+            BuildableName = "YYModel.framework"
+            BlueprintName = "YYModel"
+            ReferencedContainer = "container:YYModel.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
       <AdditionalOptions>
       </AdditionalOptions>
    </TestAction>

+ 2 - 2
README.md

@@ -251,7 +251,7 @@ EEE MMM dd HH:mm:ss Z yyyy
 
 	@implementation YYShadow
 	- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
-	- (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; }
+	- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; 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]; }
@@ -552,7 +552,7 @@ EEE MMM dd HH:mm:ss Z yyyy
 	@implementation YYShadow
 	// 直接添加以下代码即可自动完成
 	- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
-	- (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; }
+	- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; 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]; }

+ 1 - 1
YYModel/NSObject+YYModel.h

@@ -56,7 +56,7 @@
  
      @implementation YYShadow
      - (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
-     - (id)initWithCoder:(NSCoder *)aDecoder { return [self yy_modelInitWithCoder:aDecoder]; }
+     - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; 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]; }

+ 198 - 102
YYModel/NSObject+YYModel.m

@@ -292,9 +292,12 @@ 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 _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:
     NSString *_mappedToKey;      ///< the key mapped to
     NSArray *_mappedToKeyPath;   ///< the key path mapped to (nil if the name is not key path)
+    YYClassPropertyInfo *_info;  ///< property's info
     _YYModelPropertyMeta *_next; ///< next meta if there are multiple properties mapped to the same key.
 }
 @end
@@ -304,20 +307,49 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
     _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 NSKeyedArchiver 
+         */
+        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->_type == YYEncodingTypeNSUnknown) {
+    } else if (meta->_cls && meta->_nsType == YYEncodingTypeNSUnknown) {
         meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
     }
-
     
     if (propertyInfo.getter) {
         SEL sel = NSSelectorFromString(propertyInfo.getter);
@@ -332,6 +364,35 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
         }
     }
     
+    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
@@ -386,7 +447,7 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
     if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
         genericMapper = [(id<YYModel>)cls modelContainerPropertyGenericClass];
         if (genericMapper) {
-            NSMutableDictionary *tmp = genericMapper.mutableCopy;
+            NSMutableDictionary *tmp = [NSMutableDictionary new];
             [genericMapper enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
                 if (![key isKindOfClass:[NSString class]]) return;
                 Class meta = object_getClass(obj);
@@ -440,20 +501,18 @@ static force_inline id YYValueForKeyPath(__unsafe_unretained NSDictionary *dic,
                 }
                 [allPropertyMetas removeObjectForKey:propertyName];
                 if (mapper[mappedToKey]) {
-                    ((_YYModelPropertyMeta *)mapper[mappedToKey])->_next = propertyMeta;
-                } else {
-                    mapper[mappedToKey] = propertyMeta;
+                    propertyMeta->_next = mapper[mappedToKey];
                 }
+                mapper[mappedToKey] = propertyMeta;
             }
         }];
     }
     [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _YYModelPropertyMeta *propertyMeta, BOOL *stop) {
         propertyMeta->_mappedToKey = name;
         if (mapper[name]) {
-            ((_YYModelPropertyMeta *)mapper[name])->_next = propertyMeta;
-        } else {
-            mapper[name] = propertyMeta;
+            propertyMeta->_next = mapper[name];
         }
+        mapper[name] = propertyMeta;
     }];
     
     if (mapper.count) _mapper = mapper;
@@ -506,46 +565,46 @@ static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained
     switch (meta->_type & YYEncodingTypeMask) {
         case YYEncodingTypeBool: {
             return @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeInt8: {
             return @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeUInt8: {
             return @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeInt16: {
             return @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeUInt16: {
             return @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeInt32: {
             return @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeUInt32: {
             return @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeInt64: {
             return @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeUInt64: {
             return @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
-        } break;
+        }
         case YYEncodingTypeFloat: {
             float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
             if (isnan(num) || isinf(num)) return nil;
             return @(num);
-        } break;
+        }
         case YYEncodingTypeDouble: {
             double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
             if (isnan(num) || isinf(num)) return nil;
             return @(num);
-        } break;
+        }
         case YYEncodingTypeLongDouble: {
             double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
             if (isnan(num) || isinf(num)) return nil;
             return @(num);
-        } break;
+        }
         default: return nil;
     }
 }
@@ -602,12 +661,12 @@ static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model,
             ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f);
         } break;
         case YYEncodingTypeDouble: {
-            double d = num.floatValue;
+            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: {
-            double d = num.floatValue;
+            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;
@@ -642,11 +701,7 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                         if (meta->_nsType == YYEncodingTypeNSString) {
                             ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                         } else {
-                            if ([value isKindOfClass:[NSMutableString class]]) {
-                                ((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);
-                            }
+                            ((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,
@@ -704,7 +759,7 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                         if (meta->_nsType == YYEncodingTypeNSData) {
                             ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                         } else {
-                            NSMutableData *data = [value isKindOfClass:[NSMutableData class]] ? value : ((NSData *)value).mutableCopy;
+                            NSMutableData *data = ((NSData *)value).mutableCopy;
                             ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                         }
                     } else if ([value isKindOfClass:[NSString class]]) {
@@ -750,12 +805,12 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                                 if ([one isKindOfClass:meta->_genericCls]) {
                                     [objectArr addObject:one];
                                 } else if ([one isKindOfClass:[NSDictionary class]]) {
-                                    Class clazz = meta->_genericCls;
+                                    Class cls = meta->_genericCls;
                                     if (meta->_hasCustomClassFromDictionary) {
-                                        clazz = [clazz modelCustomClassForDictionary:one] ?: clazz;
+                                        cls = [cls modelCustomClassForDictionary:one];
+                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                     }
-                                    
-                                    NSObject *newOne = [clazz new];
+                                    NSObject *newOne = [cls new];
                                     [newOne yy_modelSetWithDictionary:one];
                                     if (newOne) [objectArr addObject:newOne];
                                 }
@@ -769,8 +824,6 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             } else {
                                 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                                meta->_setter,
-                                                                               [value isKindOfClass:[NSMutableArray class]] ?
-                                                                               value :
                                                                                ((NSArray *)value).mutableCopy);
                             }
                         } else if ([value isKindOfClass:[NSSet class]]) {
@@ -779,8 +832,6 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             } else {
                                 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                                meta->_setter,
-                                                                               [value isKindOfClass:[NSMutableArray class]] ?
-                                                                               ((NSSet *)value).allObjects :
                                                                                ((NSSet *)value).allObjects.mutableCopy);
                             }
                         }
@@ -792,16 +843,16 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                     if ([value isKindOfClass:[NSDictionary class]]) {
                         if (meta->_genericCls) {
                             NSMutableDictionary *dic = [NSMutableDictionary new];
-                            [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, NSObject *oneValue, BOOL *stop) {
+                            [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(NSString *oneKey, id oneValue, BOOL *stop) {
                                 if ([oneValue isKindOfClass:[NSDictionary class]]) {
-                                    Class clazz = meta->_genericCls;
+                                    Class cls = meta->_genericCls;
                                     if (meta->_hasCustomClassFromDictionary) {
-                                        clazz = [clazz modelCustomClassForDictionary:(NSDictionary *)oneValue] ?: clazz;
+                                        cls = [cls modelCustomClassForDictionary:oneValue];
+                                        if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                     }
-
-                                    NSObject *o = [clazz new];
-                                    [o yy_modelSetWithDictionary:(id)oneValue];
-                                    if (o) dic[oneKey] = o;
+                                    NSObject *newOne = [cls new];
+                                    [newOne yy_modelSetWithDictionary:(id)oneValue];
+                                    if (newOne) dic[oneKey] = newOne;
                                 }
                             }];
                             ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
@@ -811,8 +862,6 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             } else {
                                 ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                                meta->_setter,
-                                                                               [value isKindOfClass:[NSMutableDictionary class]] ?
-                                                                               value :
                                                                                ((NSDictionary *)value).mutableCopy);
                             }
                         }
@@ -831,11 +880,12 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                             if ([one isKindOfClass:meta->_genericCls]) {
                                 [set addObject:one];
                             } else if ([one isKindOfClass:[NSDictionary class]]) {
-                                Class clazz = meta->_genericCls;
+                                Class cls = meta->_genericCls;
                                 if (meta->_hasCustomClassFromDictionary) {
-                                    clazz = [clazz modelCustomClassForDictionary:one] ?: clazz;
+                                    cls = [cls modelCustomClassForDictionary:one];
+                                    if (!cls) cls = meta->_genericCls; // for xcode code coverage
                                 }
-                                NSObject *newOne = [clazz new];
+                                NSObject *newOne = [cls new];
                                 [newOne yy_modelSetWithDictionary:one];
                                 if (newOne) [set addObject:newOne];
                             }
@@ -847,8 +897,6 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                         } else {
                             ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model,
                                                                            meta->_setter,
-                                                                           [valueSet isKindOfClass:[NSMutableSet class]] ?
-                                                                           valueSet :
                                                                            ((NSSet *)valueSet).mutableCopy);
                         }
                     }
@@ -873,11 +921,12 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                     if (one) {
                         [one yy_modelSetWithDictionary:value];
                     } else {
-                        Class clazz = meta->_cls;
+                        Class cls = meta->_cls;
                         if (meta->_hasCustomClassFromDictionary) {
-                            clazz = [clazz modelCustomClassForDictionary:value] ?: clazz;
+                            cls = [cls modelCustomClassForDictionary:value];
+                            if (!cls) cls = meta->_genericCls; // for xcode code coverage
                         }
-                        one = [clazz new];
+                        one = [cls new];
                         [one yy_modelSetWithDictionary:value];
                         ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)one);
                     }
@@ -888,9 +937,21 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                 if (isNull) {
                     ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
                 } else {
-                    Class cls = ((NSObject *)value).class;
-                    if (cls && class_isMetaClass(cls)) {
-                        ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)value);
+                    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);
+                            } else {
+                                ((void (*)(id, SEL, Class))(void *) objc_msgSend)((id)model, meta->_setter, (Class)cls);
+                            }
+                        }
                     }
                 }
             } break;
@@ -916,7 +977,11 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
             case YYEncodingTypeUnion:
             case YYEncodingTypeCArray: {
                 if ([value isKindOfClass:[NSValue class]]) {
-                    [model setValue:value forKey:meta->_name];
+                    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;
                 
@@ -925,8 +990,10 @@ static void ModelSetValueForProperty(__unsafe_unretained id model,
                 if (isNull) {
                     ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
                 } else if ([value isKindOfClass:[NSValue class]]) {
-                    void *pointer = ((NSValue *)value).pointerValue;
-                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)pointer);
+                    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;
                 
@@ -1119,29 +1186,8 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
 
 @implementation NSObject (YYModel)
 
-+ (instancetype)yy_modelWithJSON:(id)json {
++ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
     if (!json || json == (id)kCFNull) return nil;
-    NSObject *one = [self new];
-    [one yy_modelSetWithJSON:json];
-    return one;
-}
-
-+ (instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary {
-    if (!dictionary || dictionary == (id)kCFNull) return nil;
-    if (![dictionary isKindOfClass:[NSDictionary class]]) return nil;
-    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;
-}
-
-- (BOOL)yy_modelSetWithJSON:(id)json {
-    if (!json || json == (id)kCFNull) return NO;
     NSDictionary *dic = nil;
     NSData *jsonData = nil;
     if ([json isKindOfClass:[NSDictionary class]]) {
@@ -1155,6 +1201,31 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
         dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
         if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
     }
+    return dic;
+}
+
++ (instancetype)yy_modelWithJSON:(id)json {
+    NSDictionary *dic = [self _yy_dictionaryWithJSON:json];
+    return [self yy_modelWithDictionary:dic];
+}
+
++ (instancetype)yy_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 yy_modelSetWithDictionary:dictionary]) return one;
+    return nil;
+}
+
+- (BOOL)yy_modelSetWithJSON:(id)json {
+    NSDictionary *dic = [NSObject _yy_dictionaryWithJSON:json];
     return [self yy_modelSetWithDictionary:dic];
 }
 
@@ -1280,11 +1351,15 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
                     ((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
                 } break;
                 case YYEncodingTypeStruct:
-                case YYEncodingTypeUnion:
-                case YYEncodingTypeCArray: {
-                    NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
-                    if (value) {
-                        [self setValue:value forKey:propertyMeta->_name];
+                case YYEncodingTypeUnion: {
+                    @try {
+                        NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
+                        if (value) {
+                            [one setValue:value forKey:propertyMeta->_name];
+                        }
+                    }
+                    @catch (NSException *exception) {
+                        // do nothing...
                     }
                 } break;
                 default: break;
@@ -1318,7 +1393,13 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
                 case YYEncodingTypeObject: {
                     id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter);
                     if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) {
-                        [aCoder encodeObject:value forKey:propertyMeta->_name];
+                        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: {
@@ -1329,10 +1410,16 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
                     }
                 } break;
                 case YYEncodingTypeStruct:
-                case YYEncodingTypeUnion:
-                case YYEncodingTypeCArray: {
-                    NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
-                    [aCoder encodeObject:value forKey:propertyMeta->_name];
+                case YYEncodingTypeUnion: {
+                    if (propertyMeta->_isKVCCompatible && propertyMeta->_isStructAvailableForKeyedArchiver) {
+                        @try {
+                            NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
+                            [aCoder encodeObject:value forKey:propertyMeta->_name];
+                        }
+                        @catch (NSException *exception) {
+                            // do nothing...
+                        }
+                    }
                 } break;
                     
                 default:
@@ -1344,7 +1431,7 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
 
 - (id)yy_modelInitWithCoder:(NSCoder *)aDecoder {
     if (!aDecoder) return self;
-    if (self == (id)kCFNull) return self;
+    if (self == (id)kCFNull) return self;    
     _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:self.class];
     if (modelMeta->_nsType) return self;
     
@@ -1352,8 +1439,11 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
         if (!propertyMeta->_setter) continue;
         
         if (propertyMeta->_isCNumber) {
-            NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
-            if (value) [self setValue:value forKey:propertyMeta->_name];
+            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) {
@@ -1369,10 +1459,16 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
                     }
                 } break;
                 case YYEncodingTypeStruct:
-                case YYEncodingTypeUnion:
-                case YYEncodingTypeCArray: {
-                    NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
-                    if (value) [self setValue:value forKey:propertyMeta->_name];
+                case YYEncodingTypeUnion: {
+                    if (propertyMeta->_isKVCCompatible) {
+                        @try {
+                            NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
+                            if (value) [self setValue:value forKey:propertyMeta->_name];
+                        }
+                        @catch (NSException *exception) {
+                            // do nothing...
+                        }
+                    }
                 } break;
                     
                 default:
@@ -1391,7 +1487,7 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
     NSUInteger value = 0;
     NSUInteger count = 0;
     for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
-        if (!propertyMeta->_getter) continue;
+        if (!propertyMeta->_isKVCCompatible) continue;
         value ^= [[self valueForKey:NSStringFromSelector(propertyMeta->_getter)] hash];
         count++;
     }
@@ -1407,7 +1503,7 @@ static id ModelToJSONObjectRecursive(NSObject *model) {
     if ([self hash] != [model hash]) return NO;
     
     for (_YYModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
-        if (!propertyMeta->_getter) continue;
+        if (!propertyMeta->_isKVCCompatible) continue;
         id this = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
         id that = [model valueForKey:NSStringFromSelector(propertyMeta->_getter)];
         if (this == that) continue;

+ 18 - 19
YYModel/YYClassInfo.h

@@ -16,7 +16,7 @@
  Type encoding's type.
  */
 typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
-    YYEncodingTypeMask       = 0x1F, ///< mask of type value
+    YYEncodingTypeMask       = 0xFF, ///< mask of type value
     YYEncodingTypeUnknown    = 0, ///< unknown
     YYEncodingTypeVoid       = 1, ///< void
     YYEncodingTypeBool       = 2, ///< bool
@@ -41,25 +41,24 @@ typedef NS_OPTIONS(NSUInteger, YYEncodingType) {
     YYEncodingTypeCString    = 21, ///< char*
     YYEncodingTypeCArray     = 22, ///< char[10] (for example)
     
-    YYEncodingTypeQualifierMask   = 0xFE0,  ///< mask of qualifier
-    YYEncodingTypeQualifierConst  = 1 << 5, ///< const
-    YYEncodingTypeQualifierIn     = 1 << 6, ///< in
-    YYEncodingTypeQualifierInout  = 1 << 7, ///< inout
-    YYEncodingTypeQualifierOut    = 1 << 8, ///< out
-    YYEncodingTypeQualifierBycopy = 1 << 9, ///< bycopy
-    YYEncodingTypeQualifierByref  = 1 << 10, ///< byref
-    YYEncodingTypeQualifierOneway = 1 << 11, ///< oneway
+    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         = 0x1FF000, ///< mask of property
-    YYEncodingTypePropertyReadonly     = 1 << 12, ///< readonly
-    YYEncodingTypePropertyCopy         = 1 << 13, ///< copy
-    YYEncodingTypePropertyRetain       = 1 << 14, ///< retain
-    YYEncodingTypePropertyNonatomic    = 1 << 15, ///< nonatomic
-    YYEncodingTypePropertyWeak         = 1 << 16, ///< weak
-    YYEncodingTypePropertyCustomGetter = 1 << 17, ///< getter=
-    YYEncodingTypePropertyCustomSetter = 1 << 18, ///< setter=
-    YYEncodingTypePropertyDynamic      = 1 << 19, ///< @dynamic
-    YYEncodingTypePropertyGarbage      = 1 << 20,
+    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
 };
 
 /**

+ 6 - 19
YYModel/YYClassInfo.m

@@ -77,7 +77,7 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
         case '#': return YYEncodingTypeClass | qualifier;
         case ':': return YYEncodingTypeSEL | qualifier;
         case '*': return YYEncodingTypeCString | qualifier;
-        case '?': return YYEncodingTypePointer | qualifier;
+        case '^': return YYEncodingTypePointer | qualifier;
         case '[': return YYEncodingTypeCArray | qualifier;
         case '(': return YYEncodingTypeUnion | qualifier;
         case '{': return YYEncodingTypeStruct | qualifier;
@@ -86,7 +86,7 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
                 return YYEncodingTypeBlock | qualifier;
             else
                 return YYEncodingTypeObject | qualifier;
-        } break;
+        }
         default: return YYEncodingTypeUnknown | qualifier;
     }
 }
@@ -138,13 +138,9 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
         NSMutableArray *argumentTypes = [NSMutableArray new];
         for (unsigned int i = 0; i < argumentCount; i++) {
             char *argumentType = method_copyArgumentType(method, i);
-            if (argumentType) {
-                NSString *type = [NSString stringWithUTF8String:argumentType];
-                [argumentTypes addObject:type ? type : @""];
-                free(argumentType);
-            } else {
-                [argumentTypes addObject:@""];
-            }
+            NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
+            [argumentTypes addObject:type ? type : @""];
+            if (argumentType) free(argumentType);
         }
         _argumentTypeEncodings = argumentTypes;
     }
@@ -207,9 +203,6 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
             case 'W': {
                 type |= YYEncodingTypePropertyWeak;
             } break;
-            case 'P': {
-                type |= YYEncodingTypePropertyGarbage;
-            } break;
             case 'G': {
                 type |= YYEncodingTypePropertyCustomGetter;
                 if (attrs[i].value) {
@@ -222,8 +215,7 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
                     _setter = [NSString stringWithUTF8String:attrs[i].value];
                 }
             } break;
-            default:
-                break;
+            default: break;
         }
     }
     if (attrs) {
@@ -265,11 +257,6 @@ YYEncodingType YYEncodingGetType(const char *typeEncoding) {
     return self;
 }
 
-- (instancetype)initWithClassName:(NSString *)className {
-    Class cls = NSClassFromString(className);
-    return [self initWithClass:cls];
-}
-
 - (void)_update {
     _ivarInfos = nil;
     _methodInfos = nil;

+ 24 - 0
YYModelTests/Info.plist

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+</dict>
+</plist>

+ 411 - 0
YYModelTests/YYTestAutoTypeConvert.m

@@ -0,0 +1,411 @@
+//
+//  YYTestAutoTypeConvert.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/28.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+#import "YYTestHelper.h"
+
+@interface YYTestAutoTypeModel : NSObject
+@property bool boolValue;
+@property BOOL BOOLValue;
+@property char charValue;
+@property unsigned char unsignedCharValue;
+@property short shortValue;
+@property unsigned short unsignedShortValue;
+@property int intValue;
+@property unsigned int unsignedIntValue;
+@property long longValue;
+@property unsigned long unsignedLongValue;
+@property long long longLongValue;
+@property unsigned long long unsignedLongLongValue;
+@property float floatValue;
+@property double doubleValue;
+@property long double longDoubleValue;
+@property (strong) Class classValue;
+@property SEL selectorValue;
+@property (copy) void (^blockValue)();
+@property void *pointerValue;
+@property CGRect structValue;
+@property CGPoint pointValue;
+
+@property (nonatomic, strong) NSObject *object;
+@property (nonatomic, strong) NSNumber *number;
+@property (nonatomic, strong) NSDecimalNumber *decimal;
+@property (nonatomic, strong) NSString *string;
+@property (nonatomic, strong) NSMutableString *mString;
+@property (nonatomic, strong) NSData *data;
+@property (nonatomic, strong) NSMutableData *mData;
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, strong) NSValue *value;
+@property (nonatomic, strong) NSURL *url;
+
+@property (nonatomic, strong) NSArray *array;
+@property (nonatomic, strong) NSMutableArray *mArray;
+@property (nonatomic, strong) NSDictionary *dict;
+@property (nonatomic, strong) NSMutableDictionary *mDict;
+@property (nonatomic, strong) NSSet *set;
+@property (nonatomic, strong) NSMutableSet *mSet;
+@end
+
+@implementation YYTestAutoTypeModel
++ (NSDictionary *)modelCustomPropertyMapper {
+    return @{ @"boolValue" : @"v",
+              @"BOOLValue" : @"v",
+              @"charValue" : @"v",
+              @"unsignedCharValue" : @"v",
+              @"shortValue" : @"v",
+              @"unsignedShortValue" : @"v",
+              @"intValue" : @"v",
+              @"unsignedIntValue" : @"v",
+              @"longValue" : @"v",
+              @"unsignedLongValue" : @"v",
+              @"longLongValue" : @"v",
+              @"unsignedLongLongValue" : @"v",
+              @"floatValue" : @"v",
+              @"doubleValue" : @"v",
+              @"longDoubleValue" : @"v",
+              @"classValue" : @"v",
+              @"selectorValue" : @"v",
+              @"blockValue" : @"v",
+              @"pointerValue" : @"v",
+              @"structValue" : @"v",
+              @"pointValue" : @"v",
+              
+              @"object" : @"v",
+              @"number" : @"v",
+              @"decimal" : @"v",
+              @"string" : @"v",
+              @"mString" : @"v",
+              @"data" : @"v",
+              @"mData" : @"v",
+              @"date" : @"v",
+              @"value" : @"v",
+              @"url" : @"v",
+              
+              @"array" : @"v",
+              @"mArray" : @"v",
+              @"dict" : @"v",
+              @"mDict" : @"v",
+              @"set" : @"v",
+              @"mSet" : @"v"
+              };
+}
+@end
+
+
+
+
+
+@interface YYTestAutoTypeConvert : XCTestCase
+
+@end
+
+@implementation YYTestAutoTypeConvert
+
+- (void)testNumber {
+    NSString *json;
+    YYTestAutoTypeModel *model;
+    
+    json = @"{\"v\" : 1}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.BOOLValue);
+    XCTAssert(model.charValue == 1);
+    XCTAssert(model.unsignedCharValue == 1);
+    XCTAssert(model.shortValue == 1);
+    XCTAssert(model.unsignedShortValue == 1);
+    XCTAssert(model.intValue == 1);
+    XCTAssert(model.unsignedIntValue == 1);
+    XCTAssert(model.longValue == 1);
+    XCTAssert(model.unsignedLongValue == 1);
+    XCTAssert(model.longLongValue == 1);
+    XCTAssert(model.unsignedLongLongValue == 1);
+    XCTAssert(model.floatValue == 1);
+    XCTAssert(model.doubleValue == 1);
+    XCTAssert(model.longDoubleValue == 1);
+    XCTAssert([model.object isEqual:@(1)]);
+    XCTAssert([model.number isEqual:@(1)]);
+    XCTAssert([model.decimal isEqual:@(1)]);
+    XCTAssert([model.string isEqualToString:@"1"]);
+    XCTAssert([model.mString isEqualToString:@"1"]);
+    XCTAssert([model.mString isKindOfClass:[NSMutableString class]]);
+    
+    json = @"{\"v\" : 1.5}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.BOOLValue);
+    XCTAssert(model.charValue == 1);
+    XCTAssert(model.unsignedCharValue == 1);
+    XCTAssert(model.shortValue == 1);
+    XCTAssert(model.unsignedShortValue == 1);
+    XCTAssert(model.intValue == 1);
+    XCTAssert(model.unsignedIntValue == 1);
+    XCTAssert(model.longValue == 1);
+    XCTAssert(model.unsignedLongValue == 1);
+    XCTAssert(model.longLongValue == 1);
+    XCTAssert(model.unsignedLongLongValue == 1);
+    XCTAssert(model.floatValue == 1.5);
+    XCTAssert(model.doubleValue == 1.5);
+    XCTAssert(model.longDoubleValue == 1.5);
+    XCTAssert([model.object isEqual:@(1.5)]);
+    XCTAssert([model.number isEqual:@(1.5)]);
+    XCTAssert([model.decimal isEqual:@(1.5)]);
+    XCTAssert([model.string isEqualToString:@"1.5"]);
+    XCTAssert([model.mString isEqualToString:@"1.5"]);
+    XCTAssert([model.mString isKindOfClass:[NSMutableString class]]);
+    
+    json = @"{\"v\" : -1}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.BOOLValue);
+    XCTAssert(model.charValue == -1);
+    XCTAssert(model.unsignedCharValue == (unsigned char)-1);
+    XCTAssert(model.shortValue == -1);
+    XCTAssert(model.unsignedShortValue == (unsigned short)-1);
+    XCTAssert(model.intValue == -1);
+    XCTAssert(model.unsignedIntValue == (unsigned int)-1);
+    XCTAssert(model.longValue == -1);
+    XCTAssert(model.unsignedLongValue == (unsigned long)-1);
+    XCTAssert(model.longLongValue == -1);
+    XCTAssert(model.unsignedLongLongValue == (unsigned long long)-1);
+    XCTAssert(model.floatValue == -1);
+    XCTAssert(model.doubleValue == -1);
+    XCTAssert(model.longDoubleValue == -1);
+    XCTAssert([model.object isEqual:@(-1)]);
+    XCTAssert([model.number isEqual:@(-1)]);
+    XCTAssert([model.decimal isEqual:@(-1)]);
+    XCTAssert([model.string isEqualToString:@"-1"]);
+    XCTAssert([model.mString isEqualToString:@"-1"]);
+    XCTAssert([model.mString isKindOfClass:[NSMutableString class]]);
+    
+    json = @"{\"v\" : \"2\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.BOOLValue);
+    XCTAssert(model.charValue == 2);
+    XCTAssert(model.unsignedCharValue == 2);
+    XCTAssert(model.shortValue == 2);
+    XCTAssert(model.unsignedShortValue == 2);
+    XCTAssert(model.intValue == 2);
+    XCTAssert(model.unsignedIntValue == 2);
+    XCTAssert(model.longValue == 2);
+    XCTAssert(model.unsignedLongValue == 2);
+    XCTAssert(model.longLongValue == 2);
+    XCTAssert(model.unsignedLongLongValue == 2);
+    XCTAssert(model.floatValue == 2);
+    XCTAssert(model.doubleValue == 2);
+    XCTAssert(model.longDoubleValue == 2);
+    XCTAssert([model.object isEqual:@"2"]);
+    XCTAssert([model.number isEqual:@(2)]);
+    XCTAssert([model.decimal isEqual:@(2)]);
+    XCTAssert([model.string isEqualToString:@"2"]);
+    XCTAssert([model.mString isEqualToString:@"2"]);
+    XCTAssert([model.mString isKindOfClass:[NSMutableString class]]);
+    
+    model.intValue = 12;
+    [model yy_modelSetWithJSON:json];
+    XCTAssert(model.intValue == 2);
+    
+    json = @"{\"v\" : \"-3.2\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(fabs(model.floatValue + 3.2) < 0.0001);
+    XCTAssert(fabs(model.doubleValue + 3.2) < 0.0001);
+    XCTAssert(fabsl(model.longDoubleValue + 3.2) < 0.0001);
+    XCTAssert([model.object isEqual:@"-3.2"]);
+    XCTAssert([model.number isEqual:@(-3.2)]);
+    XCTAssert([model.decimal isEqual:@(-3.2)]);
+    XCTAssert([model.string isEqualToString:@"-3.2"]);
+    XCTAssert([model.mString isEqualToString:@"-3.2"]);
+    XCTAssert([model.mString isKindOfClass:[NSMutableString class]]);
+    
+    
+    json = @"{\"v\" : \"true\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.intValue == 1);
+    
+    json = @"{\"v\" : \"false\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue == 0);
+    XCTAssert(model.intValue == 0);
+    
+    json = @"{\"v\" : \"YES\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue);
+    XCTAssert(model.intValue == 1);
+    
+    json = @"{\"v\" : \"NO\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue == 0);
+    XCTAssert(model.intValue == 0);
+    
+    json = @"{\"v\" : \"nil\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue == 0);
+    XCTAssert(model.intValue == 0);
+    XCTAssert([model.string isEqual:@"nil"]);
+    XCTAssert(model.number == nil);
+    
+    json = @"{\"v\" : {}}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert(model.boolValue == 0);
+    XCTAssert(model.intValue == 0);
+    XCTAssert(model.string == nil);
+    XCTAssert(model.number == nil);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSDecimalNumber decimalNumberWithString:@"9876543210"]}];
+    XCTAssert(model.unsignedLongLongValue == 9876543210LLU);
+    XCTAssert(model.longLongValue == 9876543210LL);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSValue valueWithPointer:CFArrayCreate]}];
+    XCTAssert(model.pointerValue == CFArrayCreate);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSURL class]}];
+    XCTAssert(model.classValue == [NSURL class]);
+    
+    __block int  i = 0;
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : ^{i = 1;}}];
+    model.blockValue();
+    XCTAssert(i == 1);
+}
+
+
+- (void)testDate {
+    NSString *json;
+    YYTestAutoTypeModel *model;
+    
+    json = @"{\"v\" : \"2014-05-06\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"2014-05-06 07:08:09\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"2014-05-06T07:08:09\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"2014-01-20T12:24:48Z\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"2014-01-20T12:24:48+0800\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"2014-01-20T12:24:48+12:00\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    json = @"{\"v\" : \"Fri Sep 04 00:12:21 +0800 2015\"}";
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSDate new]}];
+    XCTAssert([model.date isKindOfClass:[NSDate class]]);
+}
+
+- (void)testString {
+    NSDictionary *json;
+    YYTestAutoTypeModel *model;
+    
+    json = @{@"v" : @"Apple"};
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssertTrue([model.string isEqualToString:@"Apple"]);
+    
+    json = @{@"v" : @" github.com"};
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssertTrue([model.url isEqual:[NSURL URLWithString:@"github.com"]]);
+    
+    json = @{@"v" : @"stringWithFormat:"};
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssertTrue(model.selectorValue == @selector(stringWithFormat:));
+    
+    json = @{@"v" : @"UILabel"};
+    model = [YYTestAutoTypeModel yy_modelWithJSON:json];
+    XCTAssertTrue(model.classValue == UILabel.class);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [@"haha" dataUsingEncoding:NSUTF8StringEncoding]}];
+    XCTAssert([model.string isEqualToString:@"haha"]);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSURL URLWithString:@"https://github.com"]}];
+    XCTAssert([model.string isEqualToString:@"https://github.com"]);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : @" "}];
+    XCTAssert(model.url == nil);
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [[NSAttributedString alloc] initWithString:@"test"]}];
+    XCTAssert([model.string isEqualToString:@"test"]);
+}
+
+- (void)testValue {
+    NSValue *value;
+    YYTestAutoTypeModel *model;
+    
+    value = [NSValue valueWithCGRect:CGRectMake(1, 2, 3, 4)];
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : value}];
+    XCTAssertTrue(CGRectEqualToRect(model.structValue, CGRectMake(1, 2, 3, 4)));
+    XCTAssertTrue(CGPointEqualToPoint(model.pointValue, CGPointZero));
+    
+    value = [NSValue valueWithCGPoint:CGPointMake(1, 2)];
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : value}];
+    XCTAssertTrue(CGRectEqualToRect(model.structValue, CGRectZero));
+    XCTAssertTrue(CGPointEqualToPoint(model.pointValue, CGPointMake(1, 2)));
+}
+
+- (void)testNull {
+    YYTestAutoTypeModel *model;
+    
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSNull null]}];
+    XCTAssertTrue(model.boolValue == false);
+    XCTAssertTrue(model.object == nil);
+}
+
+- (void)testArrayAndDic {
+    NSString *json;
+    
+    json = @"[{\"v\":1},{\"v\":2},{\"v\":3}]";
+    NSArray *array = [NSArray yy_modelArrayWithClass:YYTestAutoTypeModel.class json:json];
+    XCTAssertTrue(array.count == 3);
+    XCTAssertTrue([array.firstObject isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    array = [NSArray yy_modelArrayWithClass:YYTestAutoTypeModel.class json:[YYTestHelper jsonDataFromString:json]];
+    XCTAssertTrue(array.count == 3);
+    XCTAssertTrue([array.firstObject isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    array = [NSArray yy_modelArrayWithClass:YYTestAutoTypeModel.class json:[YYTestHelper jsonObjectFromString:json]];
+    XCTAssertTrue(array.count == 3);
+    XCTAssertTrue([array.firstObject isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    
+    json = @"{\"a\":{\"v\":1},\"b\":{\"v\":2},\"c\":{\"v\":3}}";
+    NSDictionary *dict = [NSDictionary yy_modelDictionaryWithClass:YYTestAutoTypeModel.class json:json];
+    XCTAssertTrue(dict.count == 3);
+    XCTAssertTrue([dict[@"a"] isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    json = @"{\"a\":{\"v\":1},\"b\":{\"v\":2},\"c\":{\"v\":3}}";
+    dict = [NSDictionary yy_modelDictionaryWithClass:YYTestAutoTypeModel.class json:[YYTestHelper jsonDataFromString:json]];
+    XCTAssertTrue(dict.count == 3);
+    XCTAssertTrue([dict[@"a"] isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    json = @"{\"a\":{\"v\":1},\"b\":{\"v\":2},\"c\":{\"v\":3}}";
+    dict = [NSDictionary yy_modelDictionaryWithClass:YYTestAutoTypeModel.class json:[YYTestHelper jsonObjectFromString:json]];
+    XCTAssertTrue(dict.count == 3);
+    XCTAssertTrue([dict[@"a"] isKindOfClass:[YYTestAutoTypeModel class]]);
+    
+    YYTestAutoTypeModel *model;
+    model = [YYTestAutoTypeModel yy_modelWithJSON:@{@"v" : [NSSet setWithArray:@[@1,@2,@3]]}];
+    XCTAssertTrue([model.array isKindOfClass:[NSArray class]]);
+    XCTAssertTrue(model.array.count == 3);
+}
+
+@end

+ 105 - 0
YYModelTests/YYTestBlacklistWhitelist.m

@@ -0,0 +1,105 @@
+//
+//  YYTestBlacklistWhitelist.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+
+@interface YYTestBlacklistModel : NSObject
+@property (nonatomic, strong) NSString *a;
+@property (nonatomic, strong) NSString *b;
+@property (nonatomic, strong) NSString *c;
+@end
+
+@implementation YYTestBlacklistModel
++ (NSArray *)modelPropertyBlacklist {
+    return @[@"a", @"d"];
+}
+@end
+
+@interface YYTestWhitelistModel : NSObject
+@property (nonatomic, strong) NSString *a;
+@property (nonatomic, strong) NSString *b;
+@property (nonatomic, strong) NSString *c;
+@end
+
+@implementation YYTestWhitelistModel
++ (NSArray *)modelPropertyWhitelist {
+    return @[@"a", @"d"];
+}
+@end
+
+
+@interface YYTestBlackWhitelistModel : NSObject
+@property (nonatomic, strong) NSString *a;
+@property (nonatomic, strong) NSString *b;
+@property (nonatomic, strong) NSString *c;
+@end
+
+@implementation YYTestBlackWhitelistModel
++ (NSArray *)modelPropertyBlacklist {
+    return @[@"a", @"d"];
+}
++ (NSArray *)modelPropertyWhitelist {
+    return @[@"a", @"b", @"d"];
+}
+@end
+
+
+
+
+@interface YYTestBlacklistWhitelist : XCTestCase
+
+@end
+
+@implementation YYTestBlacklistWhitelist
+
+- (void)testBlacklist {
+    NSString *json = @"{\"a\":\"A\", \"b\":\"B\", \"c\":\"C\", \"d\":\"D\"}";
+    YYTestBlacklistModel *model = [YYTestBlacklistModel yy_modelWithJSON:json];
+    XCTAssert(model.a == nil);
+    XCTAssert(model.b != nil);
+    XCTAssert(model.c != nil);
+    
+    NSDictionary *dic = [model yy_modelToJSONObject];
+    XCTAssert(dic[@"a"] == nil);
+    XCTAssert(dic[@"b"] != nil);
+    XCTAssert(dic[@"c"] != nil);
+}
+
+- (void)testWhitelist {
+    NSString *json = @"{\"a\":\"A\", \"b\":\"B\", \"c\":\"C\", \"d\":\"D\"}";
+    YYTestWhitelistModel *model = [YYTestWhitelistModel yy_modelWithJSON:json];
+    XCTAssert(model.a != nil);
+    XCTAssert(model.b == nil);
+    XCTAssert(model.c == nil);
+    
+    NSDictionary *dic = [model yy_modelToJSONObject];
+    XCTAssert(dic[@"a"] != nil);
+    XCTAssert(dic[@"b"] == nil);
+    XCTAssert(dic[@"c"] == nil);
+}
+
+
+- (void)testBlackWhitelist {
+    NSString *json = @"{\"a\":\"A\", \"b\":\"B\", \"c\":\"C\", \"d\":\"D\"}";
+    YYTestBlackWhitelistModel *model = [YYTestBlackWhitelistModel yy_modelWithJSON:json];
+    XCTAssert(model.a == nil);
+    XCTAssert(model.b != nil);
+    XCTAssert(model.c == nil);
+    
+    NSDictionary *dic = [model yy_modelToJSONObject];
+    XCTAssert(dic[@"a"] == nil);
+    XCTAssert(dic[@"b"] != nil);
+    XCTAssert(dic[@"c"] == nil);
+}
+
+@end

+ 191 - 0
YYModelTests/YYTestClassInfo.m

@@ -0,0 +1,191 @@
+//
+//  YYTestClassInfo.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/27.
+//  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 <XCTest/XCTest.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import "YYModel.h"
+
+typedef union yy_union{ char a; int b;} yy_union;
+
+@interface YYTestPropertyModel : NSObject
+@property bool boolValue;
+@property BOOL BOOLValue;
+@property char charValue;
+@property unsigned char unsignedCharValue;
+@property short shortValue;
+@property unsigned short unsignedShortValue;
+@property int intValue;
+@property unsigned int unsignedIntValue;
+@property long longValue;
+@property unsigned long unsignedLongValue;
+@property long long longLongValue;
+@property unsigned long long unsignedLongLongValue;
+@property float floatValue;
+@property double doubleValue;
+@property long double longDoubleValue;
+@property (strong) NSObject *objectValue;
+@property (strong) NSArray *arrayValue;
+@property (strong) Class classValue;
+@property SEL selectorValue;
+@property (copy) void (^blockValue)();
+@property void *pointerValue;
+@property CFArrayEqualCallBack functionPointerValue;
+@property CGRect structValue;
+@property yy_union unionValue;
+@property char *cStringValue;
+
+@property (nonatomic) NSObject *nonatomicValue;
+@property (copy) NSObject *aCopyValue;
+@property (assign) NSObject *assignValue;
+@property (strong) NSObject *strongValue;
+@property (retain) NSObject *retainValue;
+@property (weak) NSObject *weakValue;
+@property (readonly) NSObject *readonlyValue;
+@property (nonatomic) NSObject *dynamicValue;
+@property (unsafe_unretained) NSObject *unsafeValue;
+@property (nonatomic, getter=getValue) NSObject *getterValue;
+@property (nonatomic, setter=setValue:) NSObject *setterValue;
+@end
+
+@implementation YYTestPropertyModel {
+    const NSObject *_constValue;
+}
+
+@dynamic dynamicValue;
+
+- (NSObject *)getValue {
+    return _getterValue;
+}
+
+- (void)setValue:(NSObject *)value {
+    _setterValue = value;
+}
+
+- (void)testConst:(const NSObject *)value {}
+- (void)testIn:(in NSObject *)value {}
+- (void)testOut:(out NSObject *)value {}
+- (void)testInout:(inout NSObject *)value {}
+- (void)testBycopy:(bycopy NSObject *)value {}
+- (void)testByref:(byref NSObject *)value {}
+- (void)testOneway:(oneway NSObject *)value {}
+@end
+
+
+
+
+
+
+@interface YYTestClassInfo : XCTestCase
+@end
+
+@implementation YYTestClassInfo
+
+- (void)testClassInfoCache {
+    YYClassInfo *info1 = [YYClassInfo classInfoWithClass:[YYTestPropertyModel class]];
+    [info1 setNeedUpdate];
+    YYClassInfo *info2 = [YYClassInfo classInfoWithClassName:@"YYTestPropertyModel"];
+    XCTAssertNotNil(info1);
+    XCTAssertNotNil(info2);
+    XCTAssertEqual(info1, info2);
+}
+
+- (void)testClassMeta {
+    YYClassInfo *classInfo = [YYClassInfo classInfoWithClass:[YYTestPropertyModel class]];
+    XCTAssertNotNil(classInfo);
+    XCTAssertEqual(classInfo.cls, [YYTestPropertyModel class]);
+    XCTAssertEqual(classInfo.superCls, [NSObject class]);
+    XCTAssertEqual(classInfo.metaCls, objc_getMetaClass("YYTestPropertyModel"));
+    XCTAssertEqual(classInfo.isMeta, NO);
+    
+    Class meta = object_getClass([YYTestPropertyModel class]);
+    YYClassInfo *metaClassInfo = [YYClassInfo classInfoWithClass:meta];
+    XCTAssertNotNil(metaClassInfo);
+    XCTAssertEqual(metaClassInfo.cls, meta);
+    XCTAssertEqual(metaClassInfo.superCls, object_getClass([NSObject class]));
+    XCTAssertEqual(metaClassInfo.metaCls, nil);
+    XCTAssertEqual(metaClassInfo.isMeta, YES);
+}
+
+- (void)testClassInfo {
+    YYClassInfo *info = [YYClassInfo classInfoWithClass:[YYTestPropertyModel class]];
+    XCTAssertEqual([self getType:info name:@"boolValue"] & YYEncodingTypeMask, YYEncodingTypeBool);
+#ifdef OBJC_BOOL_IS_BOOL
+    XCTAssertEqual([self getType:info name:@"BOOLValue"] & YYEncodingTypeMask, YYEncodingTypeBool);
+#else
+    XCTAssertEqual([self getType:info name:@"BOOLValue"] & YYEncodingTypeMask, YYEncodingTypeInt8);
+#endif
+    XCTAssertEqual([self getType:info name:@"charValue"] & YYEncodingTypeMask, YYEncodingTypeInt8);
+    XCTAssertEqual([self getType:info name:@"unsignedCharValue"] & YYEncodingTypeMask, YYEncodingTypeUInt8);
+    XCTAssertEqual([self getType:info name:@"shortValue"] & YYEncodingTypeMask, YYEncodingTypeInt16);
+    XCTAssertEqual([self getType:info name:@"unsignedShortValue"] & YYEncodingTypeMask, YYEncodingTypeUInt16);
+    XCTAssertEqual([self getType:info name:@"intValue"] & YYEncodingTypeMask, YYEncodingTypeInt32);
+    XCTAssertEqual([self getType:info name:@"unsignedIntValue"] & YYEncodingTypeMask, YYEncodingTypeUInt32);
+#ifdef __LP64__
+    XCTAssertEqual([self getType:info name:@"longValue"] & YYEncodingTypeMask, YYEncodingTypeInt64);
+    XCTAssertEqual([self getType:info name:@"unsignedLongValue"] & YYEncodingTypeMask, YYEncodingTypeUInt64);
+    XCTAssertEqual(YYEncodingGetType("l") & YYEncodingTypeMask, YYEncodingTypeInt32); // long in 32 bit system
+    XCTAssertEqual(YYEncodingGetType("L") & YYEncodingTypeMask, YYEncodingTypeUInt32); // unsingle long in 32 bit system
+#else
+    XCTAssertEqual([self getType:info name:@"longValue"] & YYEncodingTypeMask, YYEncodingTypeInt32);
+    XCTAssertEqual([self getType:info name:@"unsignedLongValue"] & YYEncodingTypeMask, YYEncodingTypeUInt32);
+#endif
+    XCTAssertEqual([self getType:info name:@"longLongValue"] & YYEncodingTypeMask, YYEncodingTypeInt64);
+    XCTAssertEqual([self getType:info name:@"unsignedLongLongValue"] & YYEncodingTypeMask, YYEncodingTypeUInt64);
+    XCTAssertEqual([self getType:info name:@"floatValue"] & YYEncodingTypeMask, YYEncodingTypeFloat);
+    XCTAssertEqual([self getType:info name:@"doubleValue"] & YYEncodingTypeMask, YYEncodingTypeDouble);
+    XCTAssertEqual([self getType:info name:@"longDoubleValue"] & YYEncodingTypeMask, YYEncodingTypeLongDouble);
+    
+    XCTAssertEqual([self getType:info name:@"objectValue"] & YYEncodingTypeMask, YYEncodingTypeObject);
+    XCTAssertEqual([self getType:info name:@"arrayValue"] & YYEncodingTypeMask, YYEncodingTypeObject);
+    XCTAssertEqual([self getType:info name:@"classValue"] & YYEncodingTypeMask, YYEncodingTypeClass);
+    XCTAssertEqual([self getType:info name:@"selectorValue"] & YYEncodingTypeMask, YYEncodingTypeSEL);
+    XCTAssertEqual([self getType:info name:@"blockValue"] & YYEncodingTypeMask, YYEncodingTypeBlock);
+    XCTAssertEqual([self getType:info name:@"pointerValue"] & YYEncodingTypeMask, YYEncodingTypePointer);
+    XCTAssertEqual([self getType:info name:@"functionPointerValue"] & YYEncodingTypeMask, YYEncodingTypePointer);
+    XCTAssertEqual([self getType:info name:@"structValue"] & YYEncodingTypeMask, YYEncodingTypeStruct);
+    XCTAssertEqual([self getType:info name:@"unionValue"] & YYEncodingTypeMask, YYEncodingTypeUnion);
+    XCTAssertEqual([self getType:info name:@"cStringValue"] & YYEncodingTypeMask, YYEncodingTypeCString);
+    
+    XCTAssertEqual(YYEncodingGetType(@encode(void)) & YYEncodingTypeMask, YYEncodingTypeVoid);
+    XCTAssertEqual(YYEncodingGetType(@encode(int[10])) & YYEncodingTypeMask, YYEncodingTypeCArray);
+    XCTAssertEqual(YYEncodingGetType("") & YYEncodingTypeMask, YYEncodingTypeUnknown);
+    XCTAssertEqual(YYEncodingGetType(".") & YYEncodingTypeMask, YYEncodingTypeUnknown);
+    XCTAssertEqual(YYEncodingGetType("ri") & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierConst);
+    XCTAssertEqual([self getMethodTypeWithName:@"testIn:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierIn);
+    XCTAssertEqual([self getMethodTypeWithName:@"testOut:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierOut);
+    XCTAssertEqual([self getMethodTypeWithName:@"testInout:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierInout);
+    XCTAssertEqual([self getMethodTypeWithName:@"testBycopy:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierBycopy);
+    XCTAssertEqual([self getMethodTypeWithName:@"testByref:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierByref);
+    XCTAssertEqual([self getMethodTypeWithName:@"testOneway:"] & YYEncodingTypeQualifierMask, YYEncodingTypeQualifierOneway);
+    
+    XCTAssert([self getType:info name:@"nonatomicValue"] & YYEncodingTypePropertyMask &YYEncodingTypePropertyNonatomic);
+    XCTAssert([self getType:info name:@"aCopyValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyCopy);
+    XCTAssert([self getType:info name:@"strongValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyRetain);
+    XCTAssert([self getType:info name:@"retainValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyRetain);
+    XCTAssert([self getType:info name:@"weakValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyWeak);
+    XCTAssert([self getType:info name:@"readonlyValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyReadonly);
+    XCTAssert([self getType:info name:@"dynamicValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyDynamic);
+    XCTAssert([self getType:info name:@"getterValue"] & YYEncodingTypePropertyMask &YYEncodingTypePropertyCustomGetter);
+    XCTAssert([self getType:info name:@"setterValue"] & YYEncodingTypePropertyMask & YYEncodingTypePropertyCustomSetter);
+}
+
+- (YYEncodingType)getType:(YYClassInfo *)info name:(NSString *)name {
+    return ((YYClassPropertyInfo *)info.propertyInfos[name]).type;
+}
+
+- (YYEncodingType)getMethodTypeWithName:(NSString *)name {
+    YYTestPropertyModel *model = [YYTestPropertyModel new];
+    NSMethodSignature *sig = [model methodSignatureForSelector:NSSelectorFromString(name)];
+    const char *typeName = [sig getArgumentTypeAtIndex:2];
+    return YYEncodingGetType(typeName);
+}
+
+@end

+ 177 - 0
YYModelTests/YYTestCopyingAndCoding.m

@@ -0,0 +1,177 @@
+//
+//  YYTestCopyingAndCoding.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+
+typedef struct my_struct {
+    int a;
+    double b;
+    long double c;
+} my_struct;
+
+@interface YYTestModelHashModel : NSObject <NSCopying, NSCoding>
+@property bool boolValue;
+@property BOOL BOOLValue;
+@property char charValue;
+@property unsigned char unsignedCharValue;
+@property short shortValue;
+@property unsigned short unsignedShortValue;
+@property int intValue;
+@property unsigned int unsignedIntValue;
+@property long longValue;
+@property unsigned long unsignedLongValue;
+@property long long longLongValue;
+@property unsigned long long unsignedLongLongValue;
+@property float floatValue;
+@property double doubleValue;
+@property long double longDoubleValue;
+@property (strong) Class classValue;
+@property SEL selectorValue;
+@property (copy) void (^blockValue)();
+@property void *pointerValue;
+@property CFArrayRef cfArrayValue;
+@property NSValue *valueValue;
+
+@property CGSize sizeValue;
+@property CGPoint pointValue;
+@property CGRect rectValue;
+@property CGAffineTransform transformValue;
+@property UIEdgeInsets insetsValue;
+@property UIOffset offsetValue;
+@property CATransform3D transform3DValue; // invalid for NSKeyedArchiver/Unarchiver
+@property my_struct myStructValue;        // invalid for NSKeyedArchiver/Unarchiver
+
+
+
+@property (nonatomic, strong) NSObject *object;
+@property (nonatomic, strong) NSNumber *number;
+@property (nonatomic, strong) NSDecimalNumber *decimal;
+@property (nonatomic, strong) NSString *string;
+@property (nonatomic, strong) NSMutableString *mString;
+@property (nonatomic, strong) NSData *data;
+@property (nonatomic, strong) NSMutableData *mData;
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, strong) NSValue *value;
+@property (nonatomic, strong) NSURL *url;
+
+@property (nonatomic, strong) NSArray *array;
+@property (nonatomic, strong) NSMutableArray *mArray;
+@property (nonatomic, strong) NSDictionary *dict;
+@property (nonatomic, strong) NSMutableDictionary *mDict;
+@property (nonatomic, strong) NSSet *set;
+@property (nonatomic, strong) NSMutableSet *mSet;
+@end
+
+@implementation YYTestModelHashModel
+- (void)encodeWithCoder:(NSCoder *)aCoder { [self yy_modelEncodeWithCoder:aCoder]; }
+- (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; 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]; }
+@end
+
+
+
+
+@interface YYTestEqualAndHash : XCTestCase
+
+@end
+
+@implementation YYTestEqualAndHash
+
+- (void)testHash {
+    YYTestModelHashModel *model1 = [YYTestModelHashModel new];
+    YYTestModelHashModel *model2 = [YYTestModelHashModel new];
+    
+    XCTAssertTrue([model1 isEqual:model2]);
+    
+    model1.intValue = 1;
+    XCTAssertFalse([model1 isEqual:model2]);
+    
+    model2.intValue = 1;
+    XCTAssertTrue([model1 isEqual:model2]);
+    
+    model1.string = @"Apple";
+    XCTAssertFalse([model1 isEqual:model2]);
+    
+    model2.string = @"Apple";
+    XCTAssertTrue([model1 isEqual:model2]);
+    
+    my_struct my = {0};
+    my.b = 12.34;
+    
+    model1.myStructValue = my;
+    XCTAssertFalse([model1 isEqual:model2]);
+    
+    model2.myStructValue = my;
+    XCTAssertTrue([model1 isEqual:model2]);
+}
+
+- (void)testCopying {
+    YYTestModelHashModel *model1 = [YYTestModelHashModel new];
+    YYTestModelHashModel *model2 = nil;
+    
+    model1.intValue = 1;
+    model1.floatValue = 12.34;
+    model1.myStructValue = (my_struct){.b = 56.78};
+    model1.string = @"Apple";
+    model2 = model1.copy;
+    
+    XCTAssertEqual(model1.intValue, model2.intValue);
+    XCTAssertEqual(model1.floatValue, model2.floatValue);
+    XCTAssertEqual(model1.myStructValue.b, model2.myStructValue.b);
+    XCTAssertTrue([model1.string isEqualToString:model2.string]);
+}
+
+- (void)testCoding {
+    NSData *data = nil;
+    YYTestModelHashModel *model1 = [YYTestModelHashModel new];
+    YYTestModelHashModel *model2 = nil;
+    
+    model1.intValue = 1;
+    model1.floatValue = 12.34;
+    model1.number = @(1234);
+    model1.string = @"Apple";
+    model1.sizeValue = CGSizeMake(12, 34);
+    model1.selectorValue = @selector(stringWithFormat:);
+    model1.myStructValue = (my_struct){.b = 56.78};
+    model1.valueValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
+    
+    data = [NSKeyedArchiver archivedDataWithRootObject:model1];
+    model2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
+    
+    XCTAssertEqual(model1.intValue, model2.intValue);
+    XCTAssertEqual(model1.floatValue, model2.floatValue);
+    XCTAssertTrue([model1.number isEqual:model2.number]);
+    XCTAssertTrue([model1.string isEqualToString:model2.string]);
+    XCTAssertTrue(CGSizeEqualToSize(model1.sizeValue, model2.sizeValue));
+    XCTAssertTrue(model2.selectorValue == @selector(stringWithFormat:));
+    XCTAssertTrue(model2.myStructValue.b == 0); // ignore in NSKeyedArchiver
+    XCTAssertTrue(model2.valueValue == nil);    // ignore in NSKeyedArchiver
+    
+    // for code coverage
+    NSArray *array = @[model1, model2];
+    NSMutableData *mutableData = [NSMutableData new];
+    NSKeyedArchiver *coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:mutableData];
+    [array yy_modelEncodeWithCoder:coder];
+    [coder finishEncoding];
+    XCTAssertTrue(mutableData.length > 0);
+    
+    mutableData = [NSMutableData new];
+    coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:mutableData];
+    [[NSNull null] yy_modelEncodeWithCoder:coder];
+    [coder finishEncoding];
+    XCTAssertTrue(mutableData.length > 0);
+}
+
+@end

+ 104 - 0
YYModelTests/YYTestCustomClass.m

@@ -0,0 +1,104 @@
+//
+//  YYTestCustomClass.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+@interface YYBaseUser : NSObject
+@property uint64_t uid;
+@property NSString *name;
+@end
+
+
+@interface YYLocalUser : YYBaseUser
+@property NSString *localName;
+@end
+@implementation YYLocalUser
+@end
+
+@interface YYRemoteUser : YYBaseUser
+@property NSString *remoteName;
+@end
+@implementation YYRemoteUser
+@end
+
+
+@implementation YYBaseUser
++ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
+    if (dictionary[@"localName"]) {
+        return [YYLocalUser class];
+    } else if (dictionary[@"remoteName"]) {
+        return [YYRemoteUser class];
+    }
+    return [YYBaseUser class];
+}
+@end
+
+@interface YYTestCustomClassModel : NSObject
+@property (nonatomic, strong) NSArray *users;
+@property (nonatomic, strong) NSSet *userSet;
+@property (nonatomic, strong) YYBaseUser *user;
+@end
+
+@implementation YYTestCustomClassModel
+
++ (NSDictionary *)modelContainerPropertyGenericClass {
+    return @{@"users" : YYBaseUser.class,
+             @"userSet" : YYBaseUser.class};
+}
++ (Class)modelCustomClassForDictionary:(NSDictionary*)dictionary {
+    if (dictionary[@"localName"]) {
+        return [YYLocalUser class];
+    } else if (dictionary[@"remoteName"]) {
+        return [YYRemoteUser class];
+    }
+    return nil;
+}
+@end
+
+
+@interface YYTestCustomClass : XCTestCase
+
+@end
+
+@implementation YYTestCustomClass
+
+- (void)test {
+    YYTestCustomClassModel *model;
+    YYBaseUser *user;
+    
+    NSDictionary *jsonUserBase = @{@"uid" : @123, @"name" : @"Harry"};
+    NSDictionary *jsonUserLocal = @{@"uid" : @123, @"name" : @"Harry", @"localName" : @"HarryLocal"};
+    NSDictionary *jsonUserRemote = @{@"uid" : @123, @"name" : @"Harry", @"remoteName" : @"HarryRemote"};
+    
+    user = [YYBaseUser yy_modelWithDictionary:jsonUserBase];
+    XCTAssert([user isMemberOfClass:[YYBaseUser class]]);
+    
+    user = [YYBaseUser yy_modelWithDictionary:jsonUserLocal];
+    XCTAssert([user isMemberOfClass:[YYLocalUser class]]);
+    
+    user = [YYBaseUser yy_modelWithDictionary:jsonUserRemote];
+    XCTAssert([user isMemberOfClass:[YYRemoteUser class]]);
+    
+    
+    model = [YYTestCustomClassModel yy_modelWithJSON:@{@"user" : jsonUserLocal}];
+    XCTAssert([model.user isMemberOfClass:[YYLocalUser class]]);
+    
+    model = [YYTestCustomClassModel yy_modelWithJSON:@{@"users" : @[jsonUserBase, jsonUserLocal, jsonUserRemote]}];
+    XCTAssert([model.users[0] isMemberOfClass:[YYBaseUser class]]);
+    XCTAssert([model.users[1] isMemberOfClass:[YYLocalUser class]]);
+    XCTAssert([model.users[2] isMemberOfClass:[YYRemoteUser class]]);
+    
+    model = [YYTestCustomClassModel yy_modelWithJSON:@{@"userSet" : @[jsonUserBase, jsonUserLocal, jsonUserRemote]}];
+    XCTAssert([model.userSet.anyObject isKindOfClass:[YYBaseUser class]]);
+}
+
+@end

+ 79 - 0
YYModelTests/YYTestCustomTransform.m

@@ -0,0 +1,79 @@
+//
+//  YYTestCustomTransform.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+@interface YYTestCustomTransformModel : NSObject
+@property uint64_t id;
+@property NSString *content;
+@property NSDate *time;
+@end
+
+@implementation YYTestCustomTransformModel
+
+- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic {
+    NSNumber *time = dic[@"time"];
+    if ([time isKindOfClass:[NSNumber class]] && time.unsignedLongLongValue != 0) {
+        _time = [NSDate dateWithTimeIntervalSince1970:time.unsignedLongLongValue / 1000.0];
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic {
+    if (_time) {
+        dic[@"time"] = @((uint64_t)(_time.timeIntervalSince1970 * 1000));
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+@end
+
+
+
+@interface YYTestCustomTransform : XCTestCase
+
+@end
+
+@implementation YYTestCustomTransform
+
+
+- (void)test {
+    NSString *json;
+    YYTestCustomTransformModel *model;
+    NSDictionary *jsonObject;
+    
+    json = @"{\"id\":5472746497,\"content\":\"Hello\",\"time\":1401234567000}";
+    model = [YYTestCustomTransformModel yy_modelWithJSON:json];
+    XCTAssert(model.time != nil);
+    
+    json = @"{\"id\":5472746497,\"content\":\"Hello\"}";
+    model = [YYTestCustomTransformModel yy_modelWithJSON:json];
+    XCTAssert(model == nil);
+    
+    model = [YYTestCustomTransformModel yy_modelWithDictionary:@{@"id":@5472746497,@"content":@"Hello"}];
+    XCTAssert(model == nil);
+    
+    json = @"{\"id\":5472746497,\"content\":\"Hello\",\"time\":1401234567000}";
+    model = [YYTestCustomTransformModel yy_modelWithJSON:json];
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssert([jsonObject[@"time"] isKindOfClass:[NSNumber class]]);
+    
+    model.time = nil;
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssert(jsonObject == nil);
+}
+
+@end

+ 21 - 0
YYModelTests/YYTestHelper.h

@@ -0,0 +1,21 @@
+//
+//  YYTestHelper.h
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/28.
+//  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>
+
+@interface YYTestHelper : NSObject
++ (NSString *)jsonStringFromData:(NSData *)data;
++ (NSString *)jsonStringFromObject:(id)object;
++ (id)jsonObjectFromData:(NSData *)data;
++ (id)jsonObjectFromString:(NSString *)string;
++ (NSData *)jsonDataFromString:(NSString *)string;
++ (NSData *)jsonDataFromObject:(id)object;
+@end

+ 43 - 0
YYModelTests/YYTestHelper.m

@@ -0,0 +1,43 @@
+//
+//  YYTestHelper.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/28.
+//  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 "YYTestHelper.h"
+
+@implementation YYTestHelper
+
++ (NSString *)jsonStringFromData:(NSData *)data {
+    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+}
+
++ (NSString *)jsonStringFromObject:(id)object {
+    NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL];
+    return [self jsonStringFromData:data];
+}
+
++ (id)jsonObjectFromData:(NSData *)data {
+    return [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:NULL];
+}
+
++ (id)jsonObjectFromString:(NSString *)string {
+    NSData *data = [self jsonDataFromString:string];
+    return [self jsonObjectFromData:data];
+}
+
++ (NSData *)jsonDataFromString:(NSString *)string {
+    return [string dataUsingEncoding:NSUTF8StringEncoding];
+}
+
++ (NSData *)jsonDataFromObject:(id)object {
+    NSString *string = [self jsonStringFromObject:object];
+    return [self jsonDataFromString:string];
+}
+
+@end

+ 234 - 0
YYModelTests/YYTestModelMapper.m

@@ -0,0 +1,234 @@
+//
+//  YYTestModelMapper.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/27.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+
+@interface YYTestPropertyMapperModelAuto : NSObject
+@property (nonatomic, assign) NSString *name;
+@property (nonatomic, assign) NSNumber *count;
+@end
+
+@implementation YYTestPropertyMapperModelAuto
+@end
+
+@interface YYTestPropertyMapperModelCustom : NSObject
+@property (nonatomic, assign) NSString *name;
+@property (nonatomic, assign) NSNumber *count;
+@property (nonatomic, assign) NSString *desc1;
+@property (nonatomic, assign) NSString *desc2;
+@property (nonatomic, assign) NSString *desc3;
+@property (nonatomic, assign) NSString *desc4;
+@end
+
+@implementation YYTestPropertyMapperModelCustom
++ (NSDictionary *)modelCustomPropertyMapper {
+    return @{ @"name" : @"n",
+              @"count" : @"ext.c",
+              @"desc1" : @"ext.d", // mapped to same key path
+              @"desc2" : @"ext.d", // mapped to same key path
+              @"desc3" : @"ext.d.e",
+              @"desc4" : @".ext"};
+}
+@end
+
+@interface YYTestPropertyMapperModelWarn : NSObject {
+    NSString *_description;
+}
+@property (nonatomic, strong) NSString *description;
+@property (nonatomic, strong) NSNumber *id;
+@end
+
+@implementation YYTestPropertyMapperModelWarn
+@synthesize description = _description;
+@end
+
+
+@interface YYTestPropertyMapperModelContainer : NSObject
+@property (nonatomic, strong) NSArray *array;
+@property (nonatomic, strong) NSMutableArray *mArray;
+@property (nonatomic, strong) NSDictionary *dict;
+@property (nonatomic, strong) NSMutableDictionary *mDict;
+@property (nonatomic, strong) NSSet *set;
+@property (nonatomic, strong) NSMutableSet *mSet;
+@end
+
+@implementation YYTestPropertyMapperModelContainer
+@end
+
+@interface YYTestPropertyMapperModelContainerGeneric : YYTestPropertyMapperModelContainer
+@end
+
+@implementation YYTestPropertyMapperModelContainerGeneric
++ (NSDictionary *)modelCustomPropertyMapper {
+    return @{ @"mArray" : @"array",
+              @"mDict" : @"dict",
+              @"mSet" : @"set"};
+}
++ (NSDictionary *)modelContainerPropertyGenericClass {
+    return @{@"array" : YYTestPropertyMapperModelAuto.class,
+             @"mArray" : YYTestPropertyMapperModelAuto.class,
+             @"dict" : YYTestPropertyMapperModelAuto.class,
+             @"mDict" : YYTestPropertyMapperModelAuto.class,
+             @"set" : YYTestPropertyMapperModelAuto.class,
+             @"mSet" : YYTestPropertyMapperModelAuto.class};
+}
+@end
+
+
+
+@interface YYTestModelPropertyMapper : XCTestCase
+
+@end
+
+@implementation YYTestModelPropertyMapper
+
+- (void)testAuto {
+    NSString *json;
+    YYTestPropertyMapperModelAuto *model;
+    
+    json = @"{\"name\":\"Apple\",\"count\":12}";
+    model = [YYTestPropertyMapperModelAuto yy_modelWithJSON:json];
+    XCTAssertTrue([model.name isEqualToString:@"Apple"]);
+    XCTAssertTrue([model.count isEqual:@12]);
+    
+    json = @"{\"n\":\"Apple\",\"count\":12, \"description\":\"hehe\"}";
+    model = [YYTestPropertyMapperModelAuto yy_modelWithJSON:json];
+    XCTAssertTrue(model.name == nil);
+    XCTAssertTrue([model.count isEqual:@12]);
+}
+
+- (void)testCustom {
+    NSString *json;
+    YYTestPropertyMapperModelCustom *model;
+    
+    json = @"{\"n\":\"Apple\",\"ext\":{\"c\":12}}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue([model.name isEqualToString:@"Apple"]);
+    XCTAssertTrue([model.count isEqual:@12]);
+    
+    json = @"{\"n\":\"Apple\",\"count\":12}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue(model.count == nil);
+    
+    json = @"{\"n\":\"Apple\",\"ext\":12}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue(model.count == nil);
+    
+    json = @"{\"n\":\"Apple\",\"ext\":@{}}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue(model.count == nil);
+    
+    json = @"{\"ext\":{\"d\":\"Apple\"}}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue([model.desc1 isEqualToString:@"Apple"]);
+    XCTAssertTrue([model.desc2 isEqualToString:@"Apple"]);
+    
+    json = @"{\"ext\":{\"d\":{ \"e\" : \"Apple\"}}}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue([model.desc3 isEqualToString:@"Apple"]);
+    
+    json = @"{\".ext\":\"Apple\"}";
+    model = [YYTestPropertyMapperModelCustom yy_modelWithJSON:json];
+    XCTAssertTrue([model.desc4 isEqualToString:@"Apple"]);
+}
+
+- (void)testWarn {
+    NSString *json = @"{\"description\":\"Apple\",\"id\":12345}";
+    YYTestPropertyMapperModelWarn *model = [YYTestPropertyMapperModelWarn yy_modelWithJSON:json];
+    XCTAssertTrue([model.description isEqualToString:@"Apple"]);
+    XCTAssertTrue([model.id isEqual:@12345]);
+}
+
+- (void)testContainer {
+    NSString *json;
+    NSDictionary *jsonObject = nil;
+    YYTestPropertyMapperModelContainer *model;
+    
+    json = @"{\"array\":[\n  {\"name\":\"Apple\", \"count\":10},\n  {\"name\":\"Banana\", \"count\":11},\n  {\"name\":\"Pear\", \"count\":12},\n  null\n]}";
+    
+    model = [YYTestPropertyMapperModelContainer yy_modelWithJSON:json];
+    XCTAssertTrue([model.array isKindOfClass:[NSArray class]]);
+    XCTAssertTrue(model.array.count == 4);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue([jsonObject[@"array"] isKindOfClass:[NSArray class]]);
+    
+    model = [YYTestPropertyMapperModelContainerGeneric yy_modelWithJSON:json];
+    XCTAssertTrue([model.array isKindOfClass:[NSArray class]]);
+    XCTAssertTrue(model.array.count == 3);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.array[0]).name isEqualToString:@"Apple"]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.array[0]).count isEqual:@10]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.array[2]).name isEqualToString:@"Pear"]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.array[2]).count isEqual:@12]);
+    XCTAssertTrue([model.mArray isKindOfClass:[NSMutableArray class]]);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue([jsonObject[@"array"] isKindOfClass:[NSArray class]]);
+    
+    json = @"{\"dict\":{\n  \"A\":{\"name\":\"Apple\", \"count\":10},\n  \"B\":{\"name\":\"Banana\", \"count\":11},\n  \"P\":{\"name\":\"Pear\", \"count\":12},\n  \"N\":null\n}}";
+    
+    model = [YYTestPropertyMapperModelContainer yy_modelWithJSON:json];
+    XCTAssertTrue([model.dict isKindOfClass:[NSDictionary class]]);
+    XCTAssertTrue(model.dict.count == 4);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue(jsonObject != nil);
+    
+    model = [YYTestPropertyMapperModelContainerGeneric yy_modelWithJSON:json];
+    XCTAssertTrue([model.dict isKindOfClass:[NSDictionary class]]);
+    XCTAssertTrue(model.dict.count == 3);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.dict[@"A"]).name isEqualToString:@"Apple"]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.dict[@"A"]).count isEqual:@10]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.dict[@"P"]).name isEqualToString:@"Pear"]);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.dict[@"P"]).count isEqual:@12]);
+    XCTAssertTrue([model.mDict isKindOfClass:[NSMutableDictionary class]]);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue(jsonObject != nil);
+    
+    json = @"{\"set\":[\n  {\"name\":\"Apple\", \"count\":10},\n  {\"name\":\"Banana\", \"count\":11},\n  {\"name\":\"Pear\", \"count\":12},\n  null\n]}";
+    
+    model = [YYTestPropertyMapperModelContainer yy_modelWithJSON:json];
+    XCTAssertTrue([model.set isKindOfClass:[NSSet class]]);
+    XCTAssertTrue(model.set.count == 4);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue(jsonObject != nil);
+    
+    model = [YYTestPropertyMapperModelContainerGeneric yy_modelWithJSON:json];
+    XCTAssertTrue([model.set isKindOfClass:[NSSet class]]);
+    XCTAssertTrue(model.set.count == 3);
+    XCTAssertTrue([((YYTestPropertyMapperModelAuto *)model.set.anyObject).name isKindOfClass:[NSString class]]);
+    XCTAssertTrue([model.mSet isKindOfClass:[NSMutableSet class]]);
+    
+    jsonObject = [model yy_modelToJSONObject];
+    XCTAssertTrue(jsonObject != nil);
+    
+    model = [YYTestPropertyMapperModelContainerGeneric yy_modelWithJSON:@{@"set" : @[[YYTestPropertyMapperModelAuto new]]}];
+    XCTAssertTrue([model.set isKindOfClass:[NSSet class]]);
+    XCTAssertTrue([[model.set anyObject] isKindOfClass:[YYTestPropertyMapperModelAuto class]]);
+    
+    model = [YYTestPropertyMapperModelContainerGeneric yy_modelWithJSON:@{@"array" : [NSSet setWithArray:@[[YYTestPropertyMapperModelAuto new]]]}];
+    XCTAssertTrue([model.array isKindOfClass:[NSArray class]]);
+    XCTAssertTrue([[model.array firstObject] isKindOfClass:[YYTestPropertyMapperModelAuto class]]);
+    
+    model = [YYTestPropertyMapperModelContainer yy_modelWithJSON:@{@"mArray" : @[[YYTestPropertyMapperModelAuto new]]}];
+    XCTAssertTrue([model.mArray isKindOfClass:[NSMutableArray class]]);
+    XCTAssertTrue([[model.mArray firstObject] isKindOfClass:[YYTestPropertyMapperModelAuto class]]);
+    
+    model = [YYTestPropertyMapperModelContainer yy_modelWithJSON:@{@"mArray" : [NSSet setWithArray:@[[YYTestPropertyMapperModelAuto new]]]}];
+    XCTAssertTrue([model.mArray isKindOfClass:[NSMutableArray class]]);
+    XCTAssertTrue([[model.mArray firstObject] isKindOfClass:[YYTestPropertyMapperModelAuto class]]);
+}
+
+@end

+ 121 - 0
YYModelTests/YYTestModelToJSON.m

@@ -0,0 +1,121 @@
+//
+//  YYTestModelToJSON.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+#import "YYTestHelper.h"
+
+@interface YYTestModelToJSONModel : NSObject
+@property bool boolValue;
+@property BOOL BOOLValue;
+@property char charValue;
+@property unsigned char unsignedCharValue;
+@property short shortValue;
+@property unsigned short unsignedShortValue;
+@property int intValue;
+@property unsigned int unsignedIntValue;
+@property long longValue;
+@property unsigned long unsignedLongValue;
+@property long long longLongValue;
+@property unsigned long long unsignedLongLongValue;
+@property float floatValue;
+@property double doubleValue;
+@property long double longDoubleValue;
+@property (strong) Class classValue;
+@property SEL selectorValue;
+@property (copy) void (^blockValue)();
+@property void *pointerValue;
+@property CGRect structValue;
+@property CGPoint pointValue;
+
+@property (nonatomic, strong) NSObject *object;
+@property (nonatomic, strong) NSNumber *number;
+@property (nonatomic, strong) NSDecimalNumber *decimal;
+@property (nonatomic, strong) NSString *string;
+@property (nonatomic, strong) NSMutableString *mString;
+@property (nonatomic, strong) NSData *data;
+@property (nonatomic, strong) NSMutableData *mData;
+@property (nonatomic, strong) NSDate *date;
+@property (nonatomic, strong) NSValue *value;
+@property (nonatomic, strong) NSURL *url;
+
+@property (nonatomic, strong) NSArray *array;
+@property (nonatomic, strong) NSMutableArray *mArray;
+@property (nonatomic, strong) NSDictionary *dict;
+@property (nonatomic, strong) NSMutableDictionary *mDict;
+@property (nonatomic, strong) NSSet *set;
+@property (nonatomic, strong) NSMutableSet *mSet;
+@end
+
+@implementation YYTestModelToJSONModel
++ (NSDictionary *)modelCustomPropertyMapper {
+    return @{
+             @"intValue" : @"int",
+             @"longValue" : @"long",             // mapped to same key
+             @"unsignedLongLongValue" : @"long", // mapped to same key
+             @"shortValue" : @"ext.short"        // mapped to key path
+             };
+}
+@end
+
+
+@interface YYTestModelToJSON : XCTestCase
+
+@end
+
+@implementation YYTestModelToJSON
+
+
+- (void)testToJSON {
+    YYTestModelToJSONModel *model = [YYTestModelToJSONModel new];
+    model.intValue = 1;
+    model.longValue = 2;
+    model.unsignedLongLongValue = 3;
+    model.shortValue = 4;
+    model.array = @[@1,@"2",[NSURL URLWithString:@"https://github.com"]];
+    model.set = [NSSet setWithArray:model.array];
+    
+    NSDictionary *jsonObject = [model yy_modelToJSONObject];
+    XCTAssert([jsonObject isKindOfClass:[NSDictionary class]]);
+    XCTAssert([jsonObject[@"int"] isEqual:@(1)]);
+    XCTAssert([jsonObject[@"long"] isEqual:@(2)] || [jsonObject[@"long"] isEqual:@(3)]);
+    XCTAssert([ ((NSDictionary *)jsonObject[@"ext"])[@"short"] isEqual:@(4)]);
+    
+    NSString *jsonString = [model yy_modelToJSONString];
+    XCTAssert([[YYTestHelper jsonObjectFromString:jsonString] isKindOfClass:[NSDictionary class]]);
+    
+    NSData *jsonData = [model yy_modelToJSONData];
+    XCTAssert([[YYTestHelper jsonObjectFromData:jsonData] isKindOfClass:[NSDictionary class]]);
+    
+    model = [YYTestModelToJSONModel yy_modelWithJSON:jsonData];
+    XCTAssert(model.intValue == 1);
+}
+
+- (void)testDate {
+    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
+    formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
+    
+    NSDate *date = [NSDate dateWithTimeIntervalSince1970:100000000];
+    NSString *dateString = [formatter stringFromDate:date];
+    
+    YYTestModelToJSONModel *model = [YYTestModelToJSONModel new];
+    model.date = date;
+    
+    NSDictionary *jsonObject = [model yy_modelToJSONObject];
+    XCTAssert([jsonObject[@"date"] isEqual:dateString]);
+    
+    NSString *jsonString = [model yy_modelToJSONString];
+    YYTestModelToJSONModel *newModel = [YYTestModelToJSONModel yy_modelWithJSON:jsonString];
+    XCTAssert([newModel.date isEqualToDate:date]);
+}
+
+@end

+ 58 - 0
YYModelTests/YYTestNestModel.m

@@ -0,0 +1,58 @@
+//
+//  YYTestNestModel.m
+//  YYModel <https://github.com/ibireme/YYModel>
+//
+//  Created by ibireme on 15/11/29.
+//  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 <XCTest/XCTest.h>
+#import "YYModel.h"
+
+
+@interface YYTestNestUser : NSObject
+@property uint64_t uid;
+@property NSString *name;
+@end
+@implementation YYTestNestUser
+@end
+
+@interface YYTestNestRepo : NSObject
+@property uint64_t repoID;
+@property NSString *name;
+@property YYTestNestUser *user;
+@end
+@implementation YYTestNestRepo
+@end
+
+
+
+@interface YYTextNestModel : XCTestCase
+
+@end
+
+@implementation YYTextNestModel
+
+- (void)test {
+    NSString *json = @"{\"repoID\":1234,\"name\":\"YYModel\",\"user\":{\"uid\":5678,\"name\":\"ibireme\"}}";
+    YYTestNestRepo *repo = [YYTestNestRepo yy_modelWithJSON:json];
+    XCTAssert(repo.repoID == 1234);
+    XCTAssert([repo.name isEqualToString:@"YYModel"]);
+    XCTAssert(repo.user.uid == 5678);
+    XCTAssert([repo.user.name isEqualToString:@"ibireme"]);
+    
+    NSDictionary *jsonObject = [repo yy_modelToJSONObject];
+    XCTAssert([((NSString *)jsonObject[@"name"]) isEqualToString:@"YYModel"]);
+    XCTAssert([((NSString *)((NSDictionary *)jsonObject[@"user"])[@"name"]) isEqualToString:@"ibireme"]);
+    
+    [repo yy_modelSetWithJSON:@{@"name" : @"YYImage", @"user" : @{@"name": @"bot"}}];
+    XCTAssert(repo.repoID == 1234);
+    XCTAssert([repo.name isEqualToString:@"YYImage"]);
+    XCTAssert(repo.user.uid == 5678);
+    XCTAssert([repo.user.name isEqualToString:@"bot"]);
+}
+
+@end