Skip to content

Commit

Permalink
Swizzle object access methods of NSUserDefaults for auto (de)serialis…
Browse files Browse the repository at this point in the history
…ation

This category will automatically (de)serialize all NSObjects conforming
to NSSecureCoding protocol when accessing any NSUserDefaults db.
This works automagically and has only one small downside: NSData objects
won't be saved as NSData objects but serialized into an NSData object.
Since we control both, setters and getters, this doesn't matter (I will
only matter if setting NSData values in the underlying property list
using an external tool).
  • Loading branch information
tmolitor-stud-tu committed Aug 28, 2024
1 parent a6dcf66 commit 8337ad3
Showing 1 changed file with 62 additions and 0 deletions.
62 changes: 62 additions & 0 deletions Monal/Classes/HelperTools.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ @interface MLDelayableTimer()
-(void) invalidate;
@end

@interface NSUserDefaults (SerializeNSObject)
-(id) swizzled_objectForKey:(NSString*) defaultName;
-(void) swizzled_setObject:(id) value forKey:(NSString*) defaultName;
@end

static char* _crashBundleName = "UnifiedReport";
static NSString* _processID;
static DDFileLogger* _fileLogger = nil;
Expand Down Expand Up @@ -291,6 +296,62 @@ -(id) initWithObj:(id) obj
}
@end

@implementation NSUserDefaults (SerializeNSObject)
-(id) swizzled_objectForKey:(NSString*) defaultName
{
//this will call the original not this one, because of swizzling!
id data = [self swizzled_objectForKey:defaultName];
//always unserialize this: every real NSData should be serialized to NSData (e.g. an NSData containing a serialized NSData)
//and therefore any exception thrown by unserialize of not serialized data should never happen as it is an implementation error in Monal
if([data isKindOfClass:[NSData class]])
{
@try {
return [HelperTools unserializeData:data];
} @catch (NSException* exception) {
NSMutableDictionary* userInfo = [NSMutableDictionary dictionaryWithDictionary:nilDefault(exception.userInfo, @{})];
[userInfo addEntriesFromDictionary:@{@"userDefaultsName":defaultName}];
@throw [NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo];
}
}
return data;
}

-(void) swizzled_setObject:(id) value forKey:(NSString*) defaultName
{
id toSave = value;
//these are the default datatypes/class clusters already handled by NSUserDefaults
//(NSData gets a special handling by us and is therefore not listed here)
if(
[value isKindOfClass:[NSString class]] ||
[value isKindOfClass:[NSNumber class]] ||
[value isKindOfClass:[NSURL class]] ||
[value isKindOfClass:[NSDictionary class]] ||
[value isKindOfClass:[NSMutableDictionary class]] ||
[value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSMutableArray class]]
)
; //do nothing, already handled by original NSUserDefaults method
//every NSData should be double serialized (see swizzled_objectForKey: above for a detailed explanation)
//everything else will just be (single) serialized to NSData
else
toSave = [HelperTools serializeObject:value];
return [self swizzled_setObject:toSave forKey:defaultName];
}

//see https://stackoverflow.com/a/13326633 and https://fek.io/blog/method-swizzling-in-obj-c-and-swift/
+(void) load
{
if(self == NSUserDefaults.self)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzle([self class], @selector(objectForKey:), @selector(swizzled_objectForKey:));
swizzle([self class], @selector(setObject:forKey:), @selector(swizzled_setObject:forKey:));
});
}
}
@end

@implementation HelperTools

+(void) initialize
Expand Down Expand Up @@ -817,6 +878,7 @@ +(id) unserializeData:(NSData*) data
[NSURL class],
[OmemoState class],
[MLContactSoftwareVersionInfo class],
[Quicksy_Country class],
]] fromData:data error:&error];
if(error)
@throw [NSException exceptionWithName:@"NSError" reason:[NSString stringWithFormat:@"%@", error] userInfo:@{@"error": error}];
Expand Down

0 comments on commit 8337ad3

Please sign in to comment.