246 lines
7.7 KiB
Objective-C
246 lines
7.7 KiB
Objective-C
//
|
|
// NSObject+AutoCoding.m
|
|
// YYMobileFramework
|
|
//
|
|
// Created by wuwei on 14/6/13.
|
|
// Copyright (c) 2014年 YY Inc. All rights reserved.
|
|
//
|
|
|
|
#import "NSObject+AutoCoding.h"
|
|
#import <objc/runtime.h>
|
|
|
|
#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
|