// // NSObject+AutoCoding.m // YYMobileFramework // // Created by wuwei on 14/6/13. // Copyright (c) 2014年 YY Inc. All rights reserved. // #import "NSObject+AutoCoding.h" #import #pragma GCC diagnostic ignored "-Wgnu" static NSString *const AutocodingException = @"AutocodingException"; @implementation NSObject (AutoCoding) + (BOOL)supportsSecureCoding { return YES; } + (instancetype)objectWithContentsOfFile:(NSString *)filePath { //load the file NSData *data = [NSData dataWithContentsOfFile:filePath]; //attempt to deserialise data as a plist id object = nil; if (data) { NSPropertyListFormat format; object = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&format error:NULL]; //success? if (object) { //check if object is an NSCoded unarchive if ([object respondsToSelector:@selector(objectForKey:)] && [(NSDictionary *)object objectForKey:@"$archiver"]) { object = [NSKeyedUnarchiver unarchiveObjectWithData:data]; } } else { //return raw data object = data; } } //return object return object; } - (BOOL)writeToFile:(NSString *)filePath atomically:(BOOL)useAuxiliaryFile { //note: NSData, NSDictionary and NSArray already implement this method //and do not save using NSCoding, however the objectWithContentsOfFile //method will correctly recover these objects anyway //archive object NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self]; return [data writeToFile:filePath atomically:useAuxiliaryFile]; } + (NSDictionary *)codableProperties { unsigned int propertyCount; __autoreleasing NSMutableDictionary *codableProperties = [NSMutableDictionary dictionary]; objc_property_t *properties = class_copyPropertyList(self, &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { //get property name objc_property_t property = properties[i]; const char *propertyName = property_getName(property); __autoreleasing NSString *key = @(propertyName); //check if codable //get property type Class propertyClass = nil; char *typeEncoding = property_copyAttributeValue(property, "T"); switch (typeEncoding[0]) { case '@': { if (strlen(typeEncoding) >= 3) { char *className = strndup(typeEncoding + 2, strlen(typeEncoding) - 3); __autoreleasing NSString *name = @(className); NSRange range = [name rangeOfString:@"<"]; if (range.location != NSNotFound) { name = [name substringToIndex:range.location]; } propertyClass = NSClassFromString(name) ?: [NSObject class]; free(className); } break; } case 'c': case 'i': case 's': case 'l': case 'q': case 'C': case 'I': case 'S': case 'L': case 'Q': case 'f': case 'd': case 'B': { propertyClass = [NSNumber class]; break; } case '{': { propertyClass = [NSValue class]; break; } default: break; } free(typeEncoding); if (propertyClass && [propertyClass conformsToProtocol:@protocol(NSSecureCoding)]) { //check if there is a backing ivar char *ivar = property_copyAttributeValue(property, "V"); if (ivar) { //check if ivar has KVC-compliant name __autoreleasing NSString *ivarName = @(ivar); if ([ivarName isEqualToString:key] || [ivarName isEqualToString:[@"_" stringByAppendingString:key]]) { //no setter, but setValue:forKey: will still work codableProperties[key] = propertyClass; } free(ivar); } else { //check if property is dynamic and readwrite char *dynamic = property_copyAttributeValue(property, "D"); char *readonly = property_copyAttributeValue(property, "R"); if (dynamic && !readonly) { //no ivar, but setValue:forKey: will still work codableProperties[key] = propertyClass; } free(dynamic); free(readonly); } } } free(properties); return codableProperties; } - (NSDictionary *)codableProperties { __autoreleasing NSDictionary *codableProperties = objc_getAssociatedObject([self class], _cmd); if (!codableProperties) { codableProperties = [NSMutableDictionary dictionary]; Class subclass = [self class]; while (subclass != [NSObject class]) { [(NSMutableDictionary *)codableProperties addEntriesFromDictionary:[subclass codableProperties]]; subclass = [subclass superclass]; } codableProperties = [NSDictionary dictionaryWithDictionary:codableProperties]; //make the association atomically so that we don't need to bother with an @synchronize objc_setAssociatedObject([self class], _cmd, codableProperties, OBJC_ASSOCIATION_RETAIN); } return codableProperties; } - (NSDictionary *)dictionaryRepresentation { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; for (__unsafe_unretained NSString *key in [self codableProperties]) { id value = [self valueForKey:key]; if (value) dict[key] = value; else dict[key] = @"nil"; } return dict; } - (void)setWithCoder:(NSCoder *)aDecoder { BOOL secureAvailable = [aDecoder respondsToSelector:@selector(decodeObjectOfClass:forKey:)]; BOOL secureSupported = [[self class] supportsSecureCoding]; NSDictionary *properties = [self codableProperties]; for (NSString *key in properties) { id object = nil; Class propertyClass = properties[key]; if (secureAvailable) { if ([propertyClass isEqual:[NSMutableAttributedString class]]) { continue; } object = [aDecoder decodeObjectOfClass:propertyClass forKey:key]; } else { if ([propertyClass isEqual:[NSMutableAttributedString class]]) { continue; } object = [aDecoder decodeObjectForKey:key]; } if (object) { if (secureSupported && ![object isKindOfClass:propertyClass]) { [NSException raise:AutocodingException format:@"Expected '%@' to be a %@, but was actually a %@", key, propertyClass, [object class]]; } [self setValue:object forKey:key]; } } } - (instancetype)initWithCoder:(NSCoder *)aDecoder { [self setWithCoder:aDecoder]; return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { for (NSString *key in [self codableProperties]) { id object = [self valueForKey:key]; if (object) [aCoder encodeObject:object forKey:key]; } } @end