2
0

Avoid using object.managedObjectContext - when no strong reference exists to the MOC, it may yield nil.

[FIXED]     Unexpected nil MOCs.
This commit is contained in:
Maarten Billemont 2013-04-30 01:49:53 -04:00
parent 0ee1e176ed
commit 40f34f3d77
15 changed files with 329 additions and 92 deletions

View File

@ -25,7 +25,7 @@
@required @required
- (NSUInteger)version; - (NSUInteger)version;
- (BOOL)migrateUser:(MPUserEntity *)user; - (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit; - (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName; - (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;

View File

@ -31,12 +31,12 @@
return 0; return 0;
} }
- (BOOL)migrateUser:(MPUserEntity *)user { - (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
NSError *error = nil; NSError *error = nil;
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user]; migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
NSArray *migrationElements = [user.managedObjectContext executeFetchRequest:migrationRequest error:&error]; NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
if (!migrationElements) { if (!migrationElements) {
err(@"While looking for elements to migrate: %@", error); err(@"While looking for elements to migrate: %@", error);
return NO; return NO;

View File

@ -10,7 +10,7 @@
@interface MPAppDelegate_Shared(Key) @interface MPAppDelegate_Shared(Key)
- (BOOL)signInAsUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password; - (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password;
- (void)signOutAnimated:(BOOL)animated; - (void)signOutAnimated:(BOOL)animated;
- (void)storeSavedKeyFor:(MPUserEntity *)user; - (void)storeSavedKeyFor:(MPUserEntity *)user;

View File

@ -77,7 +77,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated" : @(animated) }]; [[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated" : @(animated) }];
} }
- (BOOL)signInAsUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password { - (BOOL)signInAsUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc usingMasterPassword:(NSString *)password {
if (password) if (password)
NSAssert(![NSThread isMainThread], @"Computing key may not happen from the main thread."); NSAssert(![NSThread isMainThread], @"Computing key may not happen from the main thread.");

View File

@ -493,6 +493,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]]; NSString *uses = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:2]];
NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]]; NSString *type = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:3]];
NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]]; NSString *version = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:4]];
if ([version length])
version = [version substringFromIndex:1]; // Strip the leading colon.
NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]]; NSString *name = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:5]];
NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]]; NSString *exportContent = [importedSiteLine substringWithRange:[siteElements rangeAtIndex:6]];
@ -509,6 +511,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[elementsToDelete addObjectsFromArray:existingSites]; [elementsToDelete addObjectsFromArray:existingSites];
[importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]]; [importedSiteElements addObject:@[ lastUsed, uses, type, version, name, exportContent ]];
dbg(@"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, name=%@, exportContent=%@",
lastUsed, uses, type, version, name, exportContent);
} }
} }

View File

@ -139,12 +139,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if (sender == self.rememberPasswordItem) if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]]; [MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]];
if (sender == self.savePasswordItem) { if (sender == self.savePasswordItem) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserForThread]; NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady];
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc];
if ((activeUser.saveKey = !activeUser.saveKey)) if ((activeUser.saveKey = !activeUser.saveKey))
[[MPMacAppDelegate get] storeSavedKeyFor:activeUser]; [[MPMacAppDelegate get] storeSavedKeyFor:activeUser];
else else
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[activeUser.managedObjectContext saveToStore]; [moc saveToStore];
} }
if (sender == self.dialogStyleRegular) if (sender == self.dialogStyleRegular)
[MPMacConfig get].dialogStyleHUD = @NO; [MPMacConfig get].dialogStyleHUD = @NO;

View File

@ -256,11 +256,12 @@
- (MPElementEntity *)activeElementForThread { - (MPElementEntity *)activeElementForThread {
if (!_activeElementOID) return [self activeElementInContext:[MPMacAppDelegate managedObjectContextForThreadIfReady]];
return nil; }
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady]; - (MPElementEntity *)activeElementInContext:(NSManagedObjectContext *)moc {
if (!moc)
if (!_activeElementOID)
return nil; return nil;
NSError *error; NSError *error;
@ -297,9 +298,10 @@
return; return;
} }
MPElementEntity *activeElement = [self activeElementForThread]; NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady];
MPElementEntity *activeElement = [self activeElementInContext:moc];
[activeElement use]; [activeElement use];
[activeElement.managedObjectContext saveToStore]; [moc saveToStore];
} }
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{

View File

@ -50,6 +50,10 @@
element.version = MPAlgorithmDefaultVersion; element.version = MPAlgorithmDefaultVersion;
[moc saveToStore]; [moc saveToStore];
NSError *error = nil;
if (element.objectID.isTemporaryID && ![moc obtainPermanentIDsForObjects:@[ element ] error:&error])
err(@"Failed to obtain a permanent object ID after creating new element: %@", error);
NSManagedObjectID *elementOID = [element objectID]; NSManagedObjectID *elementOID = [element objectID];
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{
MPElementEntity *element_ = (MPElementEntity *)[[MPiOSAppDelegate managedObjectContextForThreadIfReady] MPElementEntity *element_ = (MPElementEntity *)[[MPiOSAppDelegate managedObjectContextForThreadIfReady]
@ -292,12 +296,18 @@
forRowAtIndexPath:(NSIndexPath *)indexPath { forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) { if (editingStyle == UITableViewCellEditingStyleDelete) {
MPElementEntity *element = [self elementForTableIndexPath:indexPath]; NSManagedObjectID *elementOID = [self elementForTableIndexPath:indexPath].objectID;
[element.managedObjectContext performBlockAndWait:^{ [MPiOSAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSError *error = nil;
MPElementEntity *element = (MPElementEntity *)[context existingObjectWithID:elementOID error:&error];
if (!element) {
err(@"Failed to retrieve element to delete: %@", error);
return;
}
inf(@"Deleting element: %@", element.name); inf(@"Deleting element: %@", element.name);
[element.managedObjectContext deleteObject:element]; [context deleteObject:element];
[element.managedObjectContext saveToStore]; [context saveToStore];
MPCheckpoint( MPCheckpointDeleteElement, @{ MPCheckpoint( MPCheckpointDeleteElement, @{
@"type" : element.typeName, @"type" : element.typeName,

View File

@ -71,7 +71,17 @@
- (IBAction)mail:(UIBarButtonItem *)sender { - (IBAction)mail:(UIBarButtonItem *)sender {
[[MPiOSAppDelegate get] openFeedbackWithLogs:YES forVC:self]; if ([[MPiOSConfig get].traceMode boolValue]) {
[PearlAlert showAlertWithTitle:@"Hiding Trace Messages" message:
@"Trace-level log messages will not be mailed. "
@"These messages contain sensitive and personal information."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[[MPiOSAppDelegate get] openFeedbackWithLogs:YES forVC:self];
} cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
}
else
[[MPiOSAppDelegate get] openFeedbackWithLogs:YES forVC:self];
} }
@end @end

View File

@ -146,15 +146,15 @@
// Needed for when we appear after a modal VC dismisses: // Needed for when we appear after a modal VC dismisses:
// We can't present until the other modal VC has been fully dismissed and presenting in -viewWillAppear: will fail. // We can't present until the other modal VC has been fully dismissed and presenting in -viewWillAppear: will fail.
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{ [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
if ([MPAlgorithmDefault migrateUser:activeUser] && !self.suppressOutdatedAlert) if ([MPAlgorithmDefault migrateUser:activeUser inContext:moc] && !self.suppressOutdatedAlert)
[UIView animateWithDuration:0.3f animations:^{ [UIView animateWithDuration:0.3f animations:^{
self.outdatedAlertContainer.alpha = 1; self.outdatedAlertContainer.alpha = 1;
self.suppressOutdatedAlert = YES; self.suppressOutdatedAlert = YES;
}]; }];
[activeUser.managedObjectContext saveToStore]; [moc saveToStore];
} ); }];
if (![[MPiOSConfig get].actionsTipShown boolValue]) if (![[MPiOSConfig get].actionsTipShown boolValue])
[UIView animateWithDuration:animated? 0.3f: 0 animations:^{ [UIView animateWithDuration:animated? 0.3f: 0 animations:^{
@ -506,7 +506,7 @@
@"If you continue, a new password will be generated for this site. " @"If you continue, a new password will be generated for this site. "
@"You will then need to update your account's old password to this newly generated password.\n\n" @"You will then need to update your account's old password to this newly generated password.\n\n"
@"You can reset the counter by holding down on this button." @"You can reset the counter by holding down on this button."
do:^BOOL(MPElementEntity *activeElement) { do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
if (![activeElement isKindOfClass:[MPElementGeneratedEntity class]]) { if (![activeElement isKindOfClass:[MPElementGeneratedEntity class]]) {
// Not of a type that supports a password counter. // Not of a type that supports a password counter.
err(@"Cannot increment password counter: Element is not generated: %@", activeElement.name); err(@"Cannot increment password counter: Element is not generated: %@", activeElement.name);
@ -545,7 +545,7 @@
@"You are resetting the site's password counter.\n\n" @"You are resetting the site's password counter.\n\n"
@"If you continue, the site's password will change back to its original value. " @"If you continue, the site's password will change back to its original value. "
@"You will then need to update your account's password back to this original value." @"You will then need to update your account's password back to this original value."
do:^BOOL(MPElementEntity *activeElement_) { do:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
inf(@"Resetting password counter for: %@", activeElement_.name); inf(@"Resetting password counter for: %@", activeElement_.name);
((MPElementGeneratedEntity *)activeElement_).counter = 1; ((MPElementGeneratedEntity *)activeElement_).counter = 1;
@ -576,7 +576,8 @@
} ); } );
} }
- (void)changeActiveElementWithWarning:(NSString *)warning do:(BOOL (^)(MPElementEntity *activeElement))task; { - (void)changeActiveElementWithWarning:(NSString *)warning
do:(BOOL (^)(MPElementEntity *activeElement, NSManagedObjectContext *context))task {
[PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault [PearlAlert showAlertWithTitle:@"Password Change" message:warning viewStyle:UIAlertViewStyleDefault
initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) { initAlert:nil tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
@ -587,7 +588,7 @@
} cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil]; } cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:[PearlStrings get].commonButtonContinue, nil];
} }
- (void)changeActiveElementWithoutWarningDo:(BOOL (^)(MPElementEntity *activeElement))task; { - (void)changeActiveElementWithoutWarningDo:(BOOL (^)(MPElementEntity *, NSManagedObjectContext *context))task {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *activeElement = [self activeElementInContext:context]; MPElementEntity *activeElement = [self activeElementInContext:context];
@ -595,7 +596,7 @@
return; return;
NSString *oldPassword = [activeElement.content description]; NSString *oldPassword = [activeElement.content description];
if (!task( activeElement )) if (!task( activeElement, context ))
return; return;
NSString *newPassword = [activeElement.content description]; NSString *newPassword = [activeElement.content description];
@ -669,7 +670,7 @@
@"This upgrade improves the site's compatibility with the latest version of Master Password."; @"This upgrade improves the site's compatibility with the latest version of Master Password.";
[self changeActiveElementWithWarning:warning do: [self changeActiveElementWithWarning:warning do:
^BOOL(MPElementEntity *activeElement_) { ^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
inf(@"Explicitly migrating element: %@", activeElement_); inf(@"Explicitly migrating element: %@", activeElement_);
[activeElement_ migrateExplicitly:YES]; [activeElement_ migrateExplicitly:YES];
@ -785,12 +786,12 @@
@"You are about to change the type of this password.\n\n" @"You are about to change the type of this password.\n\n"
@"If you continue, the password for this site will change. " @"If you continue, the password for this site will change. "
@"You will need to update your account's old password to the new one." @"You will need to update your account's old password to the new one."
do:^BOOL(MPElementEntity *activeElement) { do:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
if ([activeElement.algorithm classOfType:type] != activeElement.typeClass) { if ([activeElement.algorithm classOfType:type] != activeElement.typeClass) {
// Type requires a different class of element. Recreate the element. // Type requires a different class of element. Recreate the element.
MPElementEntity *newElement MPElementEntity *newElement
= [NSEntityDescription insertNewObjectForEntityForName:[activeElement.algorithm classNameOfType:type] = [NSEntityDescription insertNewObjectForEntityForName:[activeElement.algorithm classNameOfType:type]
inManagedObjectContext:activeElement.managedObjectContext]; inManagedObjectContext:context];
newElement.name = activeElement.name; newElement.name = activeElement.name;
newElement.user = activeElement.user; newElement.user = activeElement.user;
newElement.uses = activeElement.uses; newElement.uses = activeElement.uses;
@ -798,11 +799,10 @@
newElement.version = activeElement.version; newElement.version = activeElement.version;
newElement.loginName = activeElement.loginName; newElement.loginName = activeElement.loginName;
[activeElement.managedObjectContext deleteObject:activeElement]; [context deleteObject:activeElement];
NSError *error; NSError *error;
if (![newElement.managedObjectContext obtainPermanentIDsForObjects:@[ newElement ] if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
error:&error])
err(@"Failed to obtain a permanent object ID after changing object type: %@", error); err(@"Failed to obtain a permanent object ID after changing object type: %@", error);
_activeElementOID = newElement.objectID; _activeElementOID = newElement.objectID;
@ -819,15 +819,11 @@
- (void)didSelectElement:(MPElementEntity *)element { - (void)didSelectElement:(MPElementEntity *)element {
inf(@"Selected: %@", element.name); inf(@"Selected: %@", element.name);
NSError *error = nil;
if (element.objectID.isTemporaryID && ![element.managedObjectContext obtainPermanentIDsForObjects:@[ element ] error:&error])
err(@"Failed to obtain a permanent object ID after setting active element: %@", error);
_activeElementOID = element.objectID; _activeElementOID = element.objectID;
[self closeAlert]; [self closeAlert];
if (element) { if (element) {
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
if ([activeElement use] == 1) if ([activeElement use] == 1)
[self showAlertWithTitle:@"New Site" message: [self showAlertWithTitle:@"New Site" message:
PearlString( @"You've just created a password for %@.\n\n" PearlString( @"You've just created a password for %@.\n\n"
@ -890,7 +886,7 @@
// Content hasn't changed. // Content hasn't changed.
return; return;
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_) { [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
((MPElementStoredEntity *)activeElement_).content = self.contentField.text; ((MPElementStoredEntity *)activeElement_).content = self.contentField.text;
return YES; return YES;
}]; }];
@ -903,7 +899,7 @@
[MPiOSConfig get].loginNameTipShown = @YES; [MPiOSConfig get].loginNameTipShown = @YES;
} }
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement) { [self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement, NSManagedObjectContext *context) {
if ([self.loginNameField.text length]) if ([self.loginNameField.text length])
activeElement.loginName = self.loginNameField.text; activeElement.loginName = self.loginNameField.text;
else else

View File

@ -47,9 +47,13 @@
} options:0]; } options:0];
[avatar onSelect:^(BOOL selected) { [avatar onSelect:^(BOOL selected) {
if (selected) { if (selected) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return;
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
activeUser.avatar = (unsigned)avatar.tag; activeUser.avatar = (unsigned)avatar.tag;
[activeUser.managedObjectContext saveToStore]; [moc saveToStore];
} }
} options:0]; } options:0];
avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForThread].avatar); avatar.selected = (a == [[MPiOSAppDelegate get] activeUserForThread].avatar);
@ -129,8 +133,12 @@
[[MPiOSAppDelegate get] export]; [[MPiOSAppDelegate get] export];
else if (cell == self.changeMPCell) { else if (cell == self.changeMPCell) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser inContext:activeUser.managedObjectContext didResetBlock:nil]; if (!moc)
return;
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
[[MPiOSAppDelegate get] changeMasterPasswordFor:activeUser saveInContext:moc didResetBlock:nil];
} }
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
@ -140,9 +148,13 @@
- (void)didSelectType:(MPElementType)type { - (void)didSelectType:(MPElementType)type {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return;
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
activeUser.defaultType = type; activeUser.defaultType = type;
[activeUser.managedObjectContext saveToStore]; [moc saveToStore];
self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType]; self.defaultTypeLabel.text = [[MPiOSAppDelegate get].key.algorithm shortNameOfType:activeUser.defaultType];
} }
@ -156,12 +168,16 @@
- (IBAction)didToggleSwitch:(UISwitch *)sender { - (IBAction)didToggleSwitch:(UISwitch *)sender {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForThread]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return;
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:moc];
if ((activeUser.saveKey = sender.on)) if ((activeUser.saveKey = sender.on))
[[MPiOSAppDelegate get] storeSavedKeyFor:activeUser]; [[MPiOSAppDelegate get] storeSavedKeyFor:activeUser];
else else
[[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser]; [[MPiOSAppDelegate get] forgetSavedKeyFor:activeUser];
[activeUser.managedObjectContext saveToStore]; [moc saveToStore];
} }
@end @end

View File

@ -345,20 +345,19 @@
[self.avatarToUserOID setObject:NilToNSNull([user objectID]) forKey:[NSValue valueWithNonretainedObject:avatar]]; [self.avatarToUserOID setObject:NilToNSNull([user objectID]) forKey:[NSValue valueWithNonretainedObject:avatar]];
if ([_selectedUserOID isEqual:[user objectID]]) { if ([_selectedUserOID isEqual:[user objectID]])
self.selectedUser = user;
avatar.selected = YES; avatar.selected = YES;
}
return avatar; return avatar;
} }
- (void)didToggleUserSelection { - (void)didToggleUserSelection {
MPUserEntity *selectedUser = [self selectedUserForThread]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
MPUserEntity *selectedUser = [self selectedUserInContext:moc];
if (!selectedUser) if (!selectedUser)
[self.passwordField resignFirstResponder]; [self.passwordField resignFirstResponder];
else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser inContext:selectedUser.managedObjectContext usingMasterPassword:nil]) { else if ([[MPiOSAppDelegate get] signInAsUser:selectedUser saveInContext:moc usingMasterPassword:nil]) {
[self performSegueWithIdentifier:@"MP_Unlock" sender:self]; [self performSegueWithIdentifier:@"MP_Unlock" sender:self];
return; return;
} }
@ -450,7 +449,12 @@
} }
// Confirm // Confirm
[moc saveToStore]; [moc performBlockAndWait:^{
[moc saveToStore];
NSError *error = nil;
if (![moc obtainPermanentIDsForObjects:@[newUser] error:&error])
err(@"Failed to obtain permanent object ID for new user: %@", error);
}];
completion( YES ); completion( YES );
[self updateUsers]; [self updateUsers];
@ -535,7 +539,7 @@
UIButton *targetedAvatar = selectedAvatar; UIButton *targetedAvatar = selectedAvatar;
if (!targetedAvatar) { if (!targetedAvatar) {
targetedAvatar = [self findTargetedAvatar]; targetedAvatar = [self findTargetedAvatar];
targetedUser = [self userForAvatar:targetedAvatar]; targetedUser = [self userForAvatar:targetedAvatar inContext:[MPiOSAppDelegate managedObjectContextForThreadIfReady]];
} }
[self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) { [self.avatarsView enumerateSubviews:^(UIView *subview, BOOL *stop, BOOL *recurse) {
@ -619,7 +623,7 @@
[self setSpinnerActive:YES]; [self setSpinnerActive:YES];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
BOOL unlocked = [[MPiOSAppDelegate get] signInAsUser:[self selectedUserInContext:moc] inContext:moc BOOL unlocked = [[MPiOSAppDelegate get] signInAsUser:[self selectedUserInContext:moc] saveInContext:moc
usingMasterPassword:self.passwordField.text]; usingMasterPassword:self.passwordField.text];
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{
@ -656,11 +660,7 @@
return avatar; return avatar;
} }
- (MPUserEntity *)userForAvatar:(UIButton *)avatar { - (MPUserEntity *)userForAvatar:(UIButton *)avatar inContext:(NSManagedObjectContext *)moc {
NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
if (!moc)
return nil;
NSManagedObjectID *userOID = NSNullToNil([self.avatarToUserOID objectForKey:[NSValue valueWithNonretainedObject:avatar]]); NSManagedObjectID *userOID = NSNullToNil([self.avatarToUserOID objectForKey:[NSValue valueWithNonretainedObject:avatar]]);
if (!userOID) if (!userOID)
@ -984,11 +984,11 @@
if ([self selectedUserForThread]) if ([self selectedUserForThread])
return; return;
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar]]; NSManagedObjectContext *moc = [MPiOSAppDelegate managedObjectContextForThreadIfReady];
MPUserEntity *targetedUser = [self userForAvatar:[self findTargetedAvatar] inContext:moc];
if (!targetedUser) if (!targetedUser)
return; return;
NSManagedObjectContext *moc = targetedUser.managedObjectContext;
[PearlSheet showSheetWithTitle:targetedUser.name [PearlSheet showSheetWithTitle:targetedUser.name
viewStyle:UIActionSheetStyleBlackTranslucent viewStyle:UIActionSheetStyleBlackTranslucent
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) { initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
@ -1008,7 +1008,7 @@
} }
if (buttonIndex == [sheet firstOtherButtonIndex]) if (buttonIndex == [sheet firstOtherButtonIndex])
[[MPiOSAppDelegate get] changeMasterPasswordFor:targetedUser inContext:moc didResetBlock:^{ [[MPiOSAppDelegate get] changeMasterPasswordFor:targetedUser saveInContext:moc didResetBlock:^{
dispatch_async( dispatch_get_main_queue(), ^{ dispatch_async( dispatch_get_main_queue(), ^{
[[self avatarForUser:targetedUser] setSelected:YES]; [[self avatarForUser:targetedUser] setSelected:YES];
} ); } );

View File

@ -20,6 +20,6 @@
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController; - (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
- (void)export; - (void)export;
- (void)changeMasterPasswordFor:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset; - (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset;
@end @end

View File

@ -470,9 +470,9 @@
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController { - (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
NSString *userName = [[MPiOSAppDelegate get] activeUserForThread].name; NSString *userName = [[MPiOSAppDelegate get] activeUserForThread].name;
PearlLogLevel logLevel = [[MPiOSConfig get].sendInfo boolValue]? PearlLogLevelDebug: PearlLogLevelInfo; PearlLogLevel logLevel = PearlLogLevelInfo;
if ([[MPiOSConfig get].traceMode boolValue]) if (logs && ([[MPiOSConfig get].sendInfo boolValue] || [[MPiOSConfig get].traceMode boolValue]))
logLevel = PearlLogLevelTrace; logLevel = PearlLogLevelDebug;
[[[PearlEMail alloc] initForEMailTo:@"Master Password Development <masterpassword@lyndir.com>" [[[PearlEMail alloc] initForEMailTo:@"Master Password Development <masterpassword@lyndir.com>"
subject:PearlString( @"Feedback for Master Password [%@]", subject:PearlString( @"Feedback for Master Password [%@]",
@ -567,7 +567,7 @@
nil]; nil];
} }
- (void)changeMasterPasswordFor:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset { - (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void (^)(void))didReset {
[PearlAlert showAlertWithTitle:@"Changing Master Password" [PearlAlert showAlertWithTitle:@"Changing Master Password"
message: message:

View File

@ -492,28 +492,28 @@ Your passwords will be AES-encrypted with your master password.</string>
<rect key="frame" x="0.0" y="0.0" width="320" height="372"/> <rect key="frame" x="0.0" y="0.0" width="320" height="372"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<mutableString key="text">119-20:51:52 MPiOSAppDelegate.m:36 | INFO : Initializing TestFlight <string key="text">119-20:51:52 MPiOSAppDelegate.m:36 | INFO : Initializing TestFlight
119-20:51:52 MPiOSAppDelegate.m:70 | INFO : Initializing Google+ 119-20:51:52 MPiOSAppDelegate.m:70 | INFO : Initializing Google+
119-20:51:52 MPiOSAppDelegate.m:80 | INFO : Initializing Crashlytics 119-20:51:52 MPiOSAppDelegate.m:80 | INFO : Initializing Crashlytics
119-20:51:52 MPiOSAppDelegate.m:109 | INFO : Initializing Localytics 119-20:51:52 MPiOSAppDelegate.m:109 | INFO : Initializing Localytics
119-20:51:53 PearlAppDelegate.m:71 | INFO : Master Password (MasterPassword) 1.4 (1.4.0) (GIT: 1.4-0-g8a4eecd-dirty) 119-20:51:53 PearlAppDelegate.m:71 | INFO : Master Password (MasterPassword) 1.4 (1.4.0) (GIT: 1.4-0-g8a4eecd-dirty)
119-20:51:53 MPiOSAppDelegate.m:257 | INFO : Started up with device identifier: A8C51CDA-6F60-4F0C-BFC9-68A08F2F2DD7 119-20:51:53 MPiOSAppDelegate.m:257 | INFO : Started up with device identifier: A8C51CDA-6F60-4F0C-BFC9-68A08F2F2DD7
119-20:51:59 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] (Re)loading store... 119-20:51:59 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] (Re)loading store...
119-20:51:59 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Will load cloud store: 0B3CA2DF-5796-44DF-B5E0-121EC3846464 (definite). 119-20:51:59 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Will load cloud store: 0B3CA2DF-5796-44DF-B5E0-121EC3846464 (definite).
119-20:51:59 PearlConfig.m:193 | INFO : Lock screen will appear 119-20:51:59 PearlConfig.m:193 | INFO : Lock screen will appear
119-20:51:59 MPiOSAppDelegate.m:412 | INFO : Re-activated 119-20:51:59 MPiOSAppDelegate.m:412 | INFO : Re-activated
119-20:51:59 PearlConfig.m:180 | DEBUG : MPiOSConfig.launchCount = [70 ->] 71 119-20:51:59 PearlConfig.m:180 | DEBUG : MPiOSConfig.launchCount = [70 ->] 71
119-20:52:02 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Clearing stores... 119-20:52:02 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Clearing stores...
119-20:52:03 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Loading store without seeding. 119-20:52:03 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Loading store without seeding.
119-20:52:09 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Cloud enabled and successfully loaded cloud store. 119-20:52:09 MPAppDelegate_Store.m:278 | DEBUG : [StoreManager] Cloud enabled and successfully loaded cloud store.
119-20:52:09 MPAppDelegate_Store.m:299 | INFO : Using iCloud? 1 119-20:52:09 MPAppDelegate_Store.m:299 | INFO : Using iCloud? 1
119-20:52:12 MPAppDelegate_Key.m:28 | INFO : Found key in keychain for: b55911588b178466be1d6392597e899b8de46f9a 119-20:52:12 MPAppDelegate_Key.m:28 | INFO : Found key in keychain for: b55911588b178466be1d6392597e899b8de46f9a
119-20:52:12 MPAppDelegate_Key.m:132 | INFO : Logged in: b55911588b178466be1d6392597e899b8de46f9a 119-20:52:12 MPAppDelegate_Key.m:132 | INFO : Logged in: b55911588b178466be1d6392597e899b8de46f9a
119-20:52:13 MPUnlockViewController.m:229 | INFO : Lock screen will disappear 119-20:52:13 MPUnlockViewController.m:229 | INFO : Lock screen will disappear
119-20:52:13 MPMainViewController.m:142 | INFO : Main will appear 119-20:52:13 MPMainViewController.m:142 | INFO : Main will appear
119-20:52:16 MPMainViewController.m:734 | INFO : Action: Preferences 119-20:52:16 MPMainViewController.m:734 | INFO : Action: Preferences
119-20:52:17 MPMainViewController.m:187 | INFO : Main will disappear. 119-20:52:17 MPMainViewController.m:187 | INFO : Main will disappear.
</mutableString> </string>
<color key="textColor" cocoaTouchSystemColor="lightTextColor"/> <color key="textColor" cocoaTouchSystemColor="lightTextColor"/>
<fontDescription key="fontDescription" name="AmericanTypewriter" family="American Typewriter" pointSize="9"/> <fontDescription key="fontDescription" name="AmericanTypewriter" family="American Typewriter" pointSize="9"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
@ -2916,6 +2916,204 @@ However, it means that anyone who finds your device unlocked can do the same.</s
<image name="ui_textfield.png" width="158" height="34"/> <image name="ui_textfield.png" width="158" height="34"/>
<image name="unlocked.png" width="84" height="80"/> <image name="unlocked.png" width="84" height="80"/>
</resources> </resources>
<classes>
<class className="IASKAppSettingsViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/IASKAppSettingsViewController.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
</relationships>
</class>
<class className="MPAppViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPAppViewController.h"/>
<relationships>
<relationship kind="action" name="deblock:" candidateClass="UIButton"/>
<relationship kind="action" name="gorillas:" candidateClass="UIButton"/>
</relationships>
</class>
<class className="MPAppsViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPAppsViewController.h"/>
<relationships>
<relationship kind="action" name="exit"/>
<relationship kind="outlet" name="pagePositionView" candidateClass="UIImageView"/>
</relationships>
</class>
<class className="MPElementListAllViewController" superclassName="MPElementListController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListAllViewController.h"/>
<relationships>
<relationship kind="action" name="add:"/>
<relationship kind="action" name="close:"/>
<relationship kind="outlet" name="navigationBar" candidateClass="UINavigationBar"/>
</relationships>
</class>
<class className="MPElementListController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListController.h"/>
<relationships>
<relationship kind="outlet" name="delegate"/>
</relationships>
</class>
<class className="MPElementListSearchController" superclassName="MPElementListController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPElementListSearchController.h"/>
<relationships>
<relationship kind="outlet" name="searchDisplayController" candidateClass="UISearchDisplayController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPGuideViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPGuideViewController.h"/>
<relationships>
<relationship kind="action" name="close"/>
<relationship kind="action" name="play"/>
<relationship kind="outlet" name="content" candidateClass="UIView"/>
<relationship kind="outlet" name="contentButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="contentText" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTip" candidateClass="UIView"/>
<relationship kind="outlet" name="contentTipText" candidateClass="UILabel"/>
<relationship kind="outlet" name="largePlayButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="progress" candidateClass="UIProgressView"/>
<relationship kind="outlet" name="siteNameTip" candidateClass="UIView"/>
<relationship kind="outlet" name="smallPlayButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="toolButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="toolTip" candidateClass="UIView"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTip" candidateClass="UIView"/>
<relationship kind="outlet" name="usernameButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="usernameTip" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPLogsViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPLogsViewController.h"/>
<relationships>
<relationship kind="action" name="close:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="mail:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="refresh:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="toggleLevelControl:" candidateClass="UISegmentedControl"/>
<relationship kind="outlet" name="levelControl" candidateClass="UISegmentedControl"/>
<relationship kind="outlet" name="logView" candidateClass="UITextView"/>
</relationships>
</class>
<class className="MPMainViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPMainViewController.h"/>
<relationships>
<relationship kind="action" name="action:" candidateClass="UIBarButtonItem"/>
<relationship kind="action" name="closeAlert"/>
<relationship kind="action" name="closeOutdatedAlert"/>
<relationship kind="action" name="copyContent"/>
<relationship kind="action" name="editLoginName:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="editPassword"/>
<relationship kind="action" name="incrementPasswordCounter"/>
<relationship kind="action" name="infoOutdatedAlert"/>
<relationship kind="action" name="panHelpDown:" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="action" name="panHelpUp:" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="action" name="resetPasswordCounter:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="searchOutdatedElements"/>
<relationship kind="action" name="toggleUser"/>
<relationship kind="action" name="upgradePassword"/>
<relationship kind="outlet" name="actionsTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertBody" candidateClass="UITextView"/>
<relationship kind="outlet" name="alertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="alertTitle" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="contentField" candidateClass="UITextField"/>
<relationship kind="outlet" name="contentTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="contentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="displayContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="helpView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="loginNameContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="loginNameField" candidateClass="UITextField"/>
<relationship kind="outlet" name="loginNameTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="loginNameTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="outdatedAlertBack" candidateClass="UIImageView"/>
<relationship kind="outlet" name="outdatedAlertCloseButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="outdatedAlertContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="passwordCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordEdit" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordIncrementer" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordUpgrade" candidateClass="UIButton"/>
<relationship kind="outlet" name="passwordUser" candidateClass="UIButton"/>
<relationship kind="outlet" name="pullDownGesture" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="outlet" name="pullDownView" candidateClass="UIImageView"/>
<relationship kind="outlet" name="pullUpGesture" candidateClass="UIPanGestureRecognizer"/>
<relationship kind="outlet" name="pullUpView" candidateClass="UIImageView"/>
<relationship kind="outlet" name="searchDelegate" candidateClass="MPElementListSearchController"/>
<relationship kind="outlet" name="searchTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="siteName" candidateClass="UILabel"/>
<relationship kind="outlet" name="toolTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="toolTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="toolTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPPreferencesViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPPreferencesViewController.h"/>
<relationships>
<relationship kind="action" name="didToggleSwitch:" candidateClass="UISwitch"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="changeMPCell" candidateClass="UITableViewCell"/>
<relationship kind="outlet" name="defaultTypeLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="exportCell" candidateClass="UITableViewCell"/>
<relationship kind="outlet" name="savePasswordSwitch" candidateClass="UISwitch"/>
</relationships>
</class>
<class className="MPSetupViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPSetupViewController.h"/>
<relationships>
<relationship kind="action" name="close:" candidateClass="UIBarButtonItem"/>
<relationship kind="outlet" name="cloudSwitch" candidateClass="UISwitch"/>
<relationship kind="outlet" name="rememberLoginSwitch" candidateClass="UISwitch"/>
</relationships>
</class>
<class className="MPTypeViewController" superclassName="UITableViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPTypeViewController.h"/>
<relationships>
<relationship kind="outlet" name="recommendedTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPUnlockViewController" superclassName="UIViewController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/MPUnlockViewController.h"/>
<relationships>
<relationship kind="action" name="add:" candidateClass="UIButton"/>
<relationship kind="action" name="emergencyClose:" candidateClass="UIButton"/>
<relationship kind="action" name="emergencyCopy:" candidateClass="UIButton"/>
<relationship kind="action" name="facebook:" candidateClass="UIButton"/>
<relationship kind="action" name="google:" candidateClass="UIButton"/>
<relationship kind="action" name="mail:" candidateClass="UIButton"/>
<relationship kind="action" name="targetedUserAction:" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="action" name="twitter:" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarTemplate" candidateClass="UIButton"/>
<relationship kind="outlet" name="avatarsView" candidateClass="UIScrollView"/>
<relationship kind="outlet" name="createPasswordTipView" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyActivity" candidateClass="UIActivityIndicatorView"/>
<relationship kind="outlet" name="emergencyContentTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyCounter" candidateClass="UILabel"/>
<relationship kind="outlet" name="emergencyCounterStepper" candidateClass="UIStepper"/>
<relationship kind="outlet" name="emergencyGeneratorContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="emergencyMasterPassword" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyName" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyPassword" candidateClass="UIButton"/>
<relationship kind="outlet" name="emergencySite" candidateClass="UITextField"/>
<relationship kind="outlet" name="emergencyTypeControl" candidateClass="UISegmentedControl"/>
<relationship kind="outlet" name="nameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="newsView" candidateClass="UIWebView"/>
<relationship kind="outlet" name="oldNameLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordField" candidateClass="UITextField"/>
<relationship kind="outlet" name="passwordFieldLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordTipLabel" candidateClass="UILabel"/>
<relationship kind="outlet" name="passwordTipView" candidateClass="UIView"/>
<relationship kind="outlet" name="passwordView" candidateClass="UIView"/>
<relationship kind="outlet" name="spinner" candidateClass="UIImageView"/>
<relationship kind="outlet" name="targetedUserActionGesture" candidateClass="UILongPressGestureRecognizer"/>
<relationship kind="outlet" name="tip" candidateClass="UILabel"/>
<relationship kind="outlet" name="uiContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="wordWall" candidateClass="UIView"/>
</relationships>
</class>
<class className="PearlNavigationController" superclassName="UINavigationController">
<source key="sourceIdentifier" type="project" relativePath="./Classes/PearlNavigationController.h"/>
</class>
</classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics"> <simulatedMetricsContainer key="defaultSimulatedMetrics">
<nil key="statusBar"/> <nil key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/> <simulatedOrientationMetrics key="orientation"/>