2
0

Persistence fixes and improvements.

[UPDATED]   Pearl to ARC.
[FIXED]     Crash related to persistence changes that caused UI updates
            while other UI changes were ongoing.
[IMPROVED]  Real description of MPElementEntities and use content
            whenever the password is requested.
[IMPROVED]  iOS: Handling of search result fetching and table reloading.
This commit is contained in:
Maarten Billemont 2012-05-07 01:15:19 +02:00
parent f5d9334b06
commit 98080ceb51
9 changed files with 61 additions and 59 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit e23abc67cd80ec03543eed48ffd97b30439b5c84 Subproject commit 9336e50d3b896bac1478fe4ac8313387a85114c2

View File

@ -770,8 +770,6 @@
DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45F315039823003ABA7C /* PearlStrings.m */; }; DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45F315039823003ABA7C /* PearlStrings.m */; };
DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45F415039823003ABA7C /* PearlStringUtils.h */; }; DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45F415039823003ABA7C /* PearlStringUtils.h */; };
DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45F515039823003ABA7C /* PearlStringUtils.m */; }; DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45F515039823003ABA7C /* PearlStringUtils.m */; };
DAFE4A3115039824003ABA7C /* PearlWebUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45F615039823003ABA7C /* PearlWebUtils.h */; };
DAFE4A3215039824003ABA7C /* PearlWebUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45F715039823003ABA7C /* PearlWebUtils.m */; };
DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */; }; DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */; };
DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */; }; DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DAFE45FE15039823003ABA7C /* PearlCryptUtils.h */; };
DAFE4A3515039824003ABA7C /* PearlCryptUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */; }; DAFE4A3515039824003ABA7C /* PearlCryptUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFE45FF15039823003ABA7C /* PearlCryptUtils.m */; };
@ -1634,8 +1632,6 @@
DAFE45F315039823003ABA7C /* PearlStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlStrings.m; sourceTree = "<group>"; }; DAFE45F315039823003ABA7C /* PearlStrings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlStrings.m; sourceTree = "<group>"; };
DAFE45F415039823003ABA7C /* PearlStringUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlStringUtils.h; sourceTree = "<group>"; }; DAFE45F415039823003ABA7C /* PearlStringUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlStringUtils.h; sourceTree = "<group>"; };
DAFE45F515039823003ABA7C /* PearlStringUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlStringUtils.m; sourceTree = "<group>"; }; DAFE45F515039823003ABA7C /* PearlStringUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlStringUtils.m; sourceTree = "<group>"; };
DAFE45F615039823003ABA7C /* PearlWebUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlWebUtils.h; sourceTree = "<group>"; };
DAFE45F715039823003ABA7C /* PearlWebUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlWebUtils.m; sourceTree = "<group>"; };
DAFE45F815039823003ABA7C /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = "<group>"; }; DAFE45F815039823003ABA7C /* README */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README; sourceTree = "<group>"; };
DAFE45FB15039823003ABA7C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Pearl.strings; sourceTree = "<group>"; }; DAFE45FB15039823003ABA7C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Pearl.strings; sourceTree = "<group>"; };
DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Crypto.h"; sourceTree = "<group>"; }; DAFE45FD15039823003ABA7C /* Pearl-Crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Pearl-Crypto.h"; sourceTree = "<group>"; };
@ -2782,8 +2778,6 @@
DAFE45F315039823003ABA7C /* PearlStrings.m */, DAFE45F315039823003ABA7C /* PearlStrings.m */,
DAFE45F415039823003ABA7C /* PearlStringUtils.h */, DAFE45F415039823003ABA7C /* PearlStringUtils.h */,
DAFE45F515039823003ABA7C /* PearlStringUtils.m */, DAFE45F515039823003ABA7C /* PearlStringUtils.m */,
DAFE45F615039823003ABA7C /* PearlWebUtils.h */,
DAFE45F715039823003ABA7C /* PearlWebUtils.m */,
DAFE45F815039823003ABA7C /* README */, DAFE45F815039823003ABA7C /* README */,
DAFE45F915039823003ABA7C /* Resources */, DAFE45F915039823003ABA7C /* Resources */,
); );
@ -2927,7 +2921,6 @@
DAFE4A2C15039824003ABA7C /* PearlResettable.h in Headers */, DAFE4A2C15039824003ABA7C /* PearlResettable.h in Headers */,
DAFE4A2D15039824003ABA7C /* PearlStrings.h in Headers */, DAFE4A2D15039824003ABA7C /* PearlStrings.h in Headers */,
DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */, DAFE4A2F15039824003ABA7C /* PearlStringUtils.h in Headers */,
DAFE4A3115039824003ABA7C /* PearlWebUtils.h in Headers */,
DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */, DAFE4A3315039824003ABA7C /* Pearl-Crypto.h in Headers */,
DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */, DAFE4A3415039824003ABA7C /* PearlCryptUtils.h in Headers */,
DAFE4A3615039824003ABA7C /* PearlKeyChain.h in Headers */, DAFE4A3615039824003ABA7C /* PearlKeyChain.h in Headers */,
@ -3910,7 +3903,6 @@
DAFE4A2B15039824003ABA7C /* PearlObjectUtils.m in Sources */, DAFE4A2B15039824003ABA7C /* PearlObjectUtils.m in Sources */,
DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */, DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */,
DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */, DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */,
DAFE4A3215039824003ABA7C /* PearlWebUtils.m in Sources */,
DAFE4A3515039824003ABA7C /* PearlCryptUtils.m in Sources */, DAFE4A3515039824003ABA7C /* PearlCryptUtils.m in Sources */,
DAFE4A3715039824003ABA7C /* PearlKeyChain.m in Sources */, DAFE4A3715039824003ABA7C /* PearlKeyChain.m in Sources */,
DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */, DAFE4A3915039824003ABA7C /* PearlRSAKey.m in Sources */,
@ -3987,6 +3979,7 @@
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@ -4043,6 +4036,7 @@
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
@ -4158,6 +4152,7 @@
CLANG_WARN_CXX0X_EXTENSIONS = YES; CLANG_WARN_CXX0X_EXTENSIONS = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES;
CLANG_WARN_OBJCPP_ARC_ABI = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
@ -4228,7 +4223,7 @@
DA95D60B14DF3F3B008D1B94 /* AppStore */ = { DA95D60B14DF3F3B008D1B94 /* AppStore */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_ARC = YES;
DSTROOT = /tmp/Pearl.dst; DSTROOT = /tmp/Pearl.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch"; GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";
@ -4325,7 +4320,7 @@
DAC77CB5148291A600BCF976 /* Debug */ = { DAC77CB5148291A600BCF976 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_ARC = YES;
DSTROOT = /tmp/Pearl.dst; DSTROOT = /tmp/Pearl.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch"; GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";
@ -4339,7 +4334,7 @@
DAC77CB6148291A600BCF976 /* AdHoc */ = { DAC77CB6148291A600BCF976 /* AdHoc */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_ARC = YES;
DSTROOT = /tmp/Pearl.dst; DSTROOT = /tmp/Pearl.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch"; GCC_PREFIX_HEADER = "Pearl/Pearl-Prefix.pch";

View File

@ -77,6 +77,15 @@
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "AdHoc" buildConfiguration = "AdHoc"
debugDocumentVersioning = "YES"> debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "MasterPassword.app"
BlueprintName = "MasterPassword"
ReferencedContainer = "container:MasterPassword-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction> </ProfileAction>
<AnalyzeAction <AnalyzeAction
buildConfiguration = "Debug"> buildConfiguration = "Debug">

View File

@ -81,7 +81,8 @@ static NSDictionary *keyHashQuery() {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator) { if (coordinator) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; // Put concurrency type on main queue, because otherwise updates break updating the search table UI.
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType/*NSPrivateQueueConcurrencyType*/];
_managedObjectContext.persistentStoreCoordinator = coordinator; _managedObjectContext.persistentStoreCoordinator = coordinator;
[[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification [[NSNotificationCenter defaultCenter] addObserverForName:NSPersistentStoreDidImportUbiquitousContentChangesNotification

View File

@ -30,7 +30,7 @@
- (NSString *)description { - (NSString *)description {
return [[self content] description]; return str(@"%@:%@", [self class], [self name]);
} }
- (NSString *)debugDescription { - (NSString *)debugDescription {

View File

@ -121,7 +121,7 @@
} }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [result description]; NSString *description = [result.content description];
[result use]; [result use];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@ -143,7 +143,7 @@
element.name = siteName; element.name = siteName;
element.mpHashHex = [MPAppDelegate get].keyHashHex; element.mpHashHex = [MPAppDelegate get].keyHashHex;
NSString *description = [element description]; NSString *description = [element.content description];
[element use]; [element use];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{

View File

@ -177,7 +177,7 @@
self.contentField.text = @""; self.contentField.text = @"";
if (self.activeElement.name && ![self.activeElement isDeleted]) if (self.activeElement.name && ![self.activeElement isDeleted])
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *description = [self.activeElement description]; NSString *description = [self.activeElement.content description];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
self.contentField.text = description; self.contentField.text = description;
@ -343,9 +343,9 @@
- (void)changeElementWithoutWarningDo:(void (^)(void))task; { - (void)changeElementWithoutWarningDo:(void (^)(void))task; {
// Update element, keeping track of the old password. // Update element, keeping track of the old password.
NSString *oldPassword = self.activeElement.description; NSString *oldPassword = [self.activeElement.content description];
task(); task();
NSString *newPassword = self.activeElement.description; NSString *newPassword = [self.activeElement.content description];
[self updateAnimated:YES]; [self updateAnimated:YES];
// Show new and old password. // Show new and old password.
@ -461,7 +461,7 @@
[TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]]; [TestFlight passCheckpoint:[NSString stringWithFormat:MPTestFlightCheckpointSelectType, NSStringFromMPElementType(type)]];
if (type & MPElementTypeClassStored && ![self.activeElement.description length]) if (type & MPElementTypeClassStored && ![[self.activeElement.content description] length])
[self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon]; [self showContentTip:@"Tap to set a password." withIcon:self.contentTipEditIcon];
}]; }];
} }

View File

@ -31,10 +31,16 @@
return nil; return nil;
self.dateFormatter = [NSDateFormatter new]; self.dateFormatter = [NSDateFormatter new];
self.dateFormatter.timeStyle = NSDateFormatterNoStyle;
self.dateFormatter.dateStyle = NSDateFormatterShortStyle; self.dateFormatter.dateStyle = NSDateFormatterShortStyle;
self.query = @""; self.query = @"";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
return self; return self;
} }
@ -101,31 +107,21 @@
[self update]; [self update];
return YES; return NO;
} }
- (void)update { - (void)update {
assert(self.query); assert(self.query);
assert([MPAppDelegate get].keyHashHex); assert([MPAppDelegate get].keyHashHex);
NSFetchRequest *fetchRequest = [[MPAppDelegate get].managedObjectModel
fetchRequestFromTemplateWithName:@"MPElements"
substitutionVariables:[NSDictionary dictionaryWithObjectsAndKeys:
self.query, @"query",
[MPAppDelegate get].keyHashHex, @"mpHashHex",
nil]];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"uses" ascending:NO]]];
self.fetchedResultsController.delegate = nil; self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND mpHashHex == %@",
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest self.query, self.query, [MPAppDelegate get].keyHashHex];
managedObjectContext:[MPAppDelegate managedObjectContext]
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error; NSError *error;
if (![self.fetchedResultsController performFetch:&error]) if (![self.fetchedResultsController performFetch:&error])
err(@"Couldn't fetch elements: %@", error); err(@"Couldn't fetch elements: %@", error);
[self.searchDisplayController.searchResultsTableView reloadData];
} }
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
@ -192,8 +188,9 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section < (signed)[[self.fetchedResultsController sections] count]) NSArray *sections = [self.fetchedResultsController sections];
return (signed)[[[self.fetchedResultsController sections] objectAtIndex:(unsigned)section] numberOfObjects]; if (section < (signed)[sections count])
return (signed)[[sections objectAtIndex:(unsigned)section] numberOfObjects];
return 1; return 1;
} }
@ -259,7 +256,7 @@
return; return;
[self.fetchedResultsController.managedObjectContext performBlock:^{ [self.fetchedResultsController.managedObjectContext performBlock:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class]) MPElementGeneratedEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; inManagedObjectContext:self.fetchedResultsController.managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]); assert([element isKindOfClass:ClassFromMPElementType((unsigned)element.type)]);
assert([MPAppDelegate get].keyHashHex); assert([MPAppDelegate get].keyHashHex);