diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m
index 137228bcf..acd419397 100644
--- a/Signal/src/ViewControllers/NewContactThreadViewController.m
+++ b/Signal/src/ViewControllers/NewContactThreadViewController.m
@@ -148,7 +148,7 @@ NS_ASSUME_NONNULL_BEGIN
     OWSAssert([NSThread isMainThread]);
 
     [self.contactsViewHelper.contactsManager
-        fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotifyWithCompletion:^(NSError *_Nullable error) {
+        userRequestedSystemContactsRefreshWithCompletion:^(NSError *_Nullable error) {
             if (error) {
                 DDLogError(@"%@ refreshing contacts failed with error: %@", self.logTag, error);
             }
diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h
index 01d56dc35..2e829309d 100644
--- a/Signal/src/contact/OWSContactsManager.h
+++ b/Signal/src/contact/OWSContactsManager.h
@@ -54,9 +54,9 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
 - (void)fetchSystemContactsOnceIfAlreadyAuthorized;
 
 // This variant will fetch system contacts if contact access has already been granted,
-// but not prompt for contact access. Also, it will always fire a notification.
-- (void)fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotifyWithCompletion:
-    (void (^)(NSError *_Nullable error))completionHandler;
+// but not prompt for contact access. Also, it will always notify delegates, even if
+// contacts haven't changed, and will clear out any stale cached SignalAccount's
+- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler;
 
 #pragma mark - Util
 
diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m
index ac3c27a7a..05a037731 100644
--- a/Signal/src/contact/OWSContactsManager.m
+++ b/Signal/src/contact/OWSContactsManager.m
@@ -77,6 +77,17 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
     [self updateSignalAccounts:signalAccounts];
 }
 
+- (dispatch_queue_t)serialQueue
+{
+    static dispatch_queue_t _serialQueue;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        _serialQueue = dispatch_queue_create("org.whispersystems.contacts.buildSignalAccount", DISPATCH_QUEUE_SERIAL);
+    });
+
+    return _serialQueue;
+}
+
 #pragma mark - System Contact Fetching
 
 // Request contacts access if you haven't asked recently.
@@ -95,10 +106,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
     [self.systemContactsFetcher fetchOnceIfAlreadyAuthorized];
 }
 
-- (void)fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotifyWithCompletion:
-    (void (^)(NSError *_Nullable error))completionHandler
+- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler
 {
-    [self.systemContactsFetcher fetchIfAlreadyAuthorizedAndAlwaysNotifyWithCompletion:completionHandler];
+    [self.systemContactsFetcher userRequestedRefreshWithCompletion:completionHandler];
 }
 
 - (BOOL)isSystemContactsAuthorized
@@ -120,8 +130,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
 
 - (void)systemContactsFetcher:(SystemContactsFetcher *)systemsContactsFetcher
               updatedContacts:(NSArray<Contact *> *)contacts
+                userRequested:(BOOL)userRequested
 {
-    [self updateWithContacts:contacts];
+    [self updateWithContacts:contacts clearStaleCache:userRequested];
 }
 
 #pragma mark - Intersection
@@ -179,10 +190,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
     [self.avatarCache removeAllImagesForKey:recipientId];
 }
 
-- (void)updateWithContacts:(NSArray<Contact *> *)contacts
+- (void)updateWithContacts:(NSArray<Contact *> *)contacts clearStaleCache:(BOOL)clearStaleCache
 {
-    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-
+    dispatch_async(self.serialQueue, ^{
         NSMutableDictionary<NSString *, Contact *> *allContactsMap = [NSMutableDictionary new];
         for (Contact *contact in contacts) {
             for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
@@ -200,22 +210,15 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
             [self.avatarCache removeAllImages];
 
             [self intersectContactsWithCompletion:^(NSError *_Nullable error) {
-                [self buildSignalAccounts];
+                [self buildSignalAccountsAndClearStaleCache:clearStaleCache];
             }];
         });
     });
 }
 
-- (void)buildSignalAccounts
+- (void)buildSignalAccountsAndClearStaleCache:(BOOL)clearStaleCache;
 {
-    // Ensure we're not running concurrently since one invocation could affect the other.
-    static dispatch_queue_t _serialQueue;
-    static dispatch_once_t onceToken;
-    dispatch_once(&onceToken, ^{
-        _serialQueue = dispatch_queue_create("org.whispersystems.contacts.buildSignalAccount", DISPATCH_QUEUE_SERIAL);
-    });
-
-    dispatch_async(_serialQueue, ^{
+    dispatch_async(self.serialQueue, ^{
         NSMutableArray<SignalAccount *> *signalAccounts = [NSMutableArray new];
         NSArray<Contact *> *contacts = self.allContacts;
 
@@ -291,16 +294,43 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
 
         // Update cached SignalAccounts on disk
         [self.dbWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
-            DDLogInfo(@"%@ Saving %lu new SignalAccounts", self.logTag, (unsigned long)accountsToSave.count);
+            DDLogInfo(@"%@ Saving %lu SignalAccounts", self.logTag, (unsigned long)accountsToSave.count);
             for (SignalAccount *signalAccount in accountsToSave) {
-                DDLogVerbose(@"%@ Adding new SignalAccount: %@", self.logTag, signalAccount);
+                DDLogVerbose(@"%@ Saving SignalAccount: %@", self.logTag, signalAccount);
                 [signalAccount saveWithTransaction:transaction];
             }
 
-            DDLogInfo(@"%@ Removing %lu old SignalAccounts.", self.logTag, (unsigned long)oldSignalAccounts.count);
-            for (SignalAccount *signalAccount in oldSignalAccounts.allValues) {
-                DDLogVerbose(@"%@ Removing old SignalAccount: %@", self.logTag, signalAccount);
-                [signalAccount removeWithTransaction:transaction];
+            if (clearStaleCache) {
+                DDLogInfo(@"%@ Removing %lu old SignalAccounts.", self.logTag, (unsigned long)oldSignalAccounts.count);
+                for (SignalAccount *signalAccount in oldSignalAccounts.allValues) {
+                    DDLogVerbose(@"%@ Removing old SignalAccount: %@", self.logTag, signalAccount);
+                    [signalAccount removeWithTransaction:transaction];
+                }
+            } else {
+                // In theory we want to remove SignalAccounts if the user deletes the corresponding system contact.
+                // However, as of iOS11.2 CNContactStore occasionally gives us only a subset of the system contacts.
+                // Because of that, it's not safe to clear orphaned accounts.
+                // Because we still want to give users a way to clear their stale accounts, if they pull-to-refresh
+                // their contacts we'll clear the cached ones.
+                // RADAR: https://bugreport.apple.com/web/?problemID=36082946
+                if (oldSignalAccounts.allValues.count > 0) {
+                    DDLogWarn(@"%@ NOT Removing %lu old SignalAccounts.",
+                        self.logTag,
+                        (unsigned long)oldSignalAccounts.count);
+                    for (SignalAccount *signalAccount in oldSignalAccounts.allValues) {
+                        DDLogVerbose(
+                            @"%@ Ensuring old SignalAccount is not inadvertently lost: %@", self.logTag, signalAccount);
+                        [signalAccounts addObject:signalAccount];
+                    }
+
+                    // re-sort signal accounts since we've appended some orphans
+                    [signalAccounts sortUsingComparator:^NSComparisonResult(SignalAccount *left, SignalAccount *right) {
+                        NSString *leftName = [self comparableNameForSignalAccount:left];
+                        NSString *rightName = [self comparableNameForSignalAccount:right];
+
+                        return [leftName compare:rightName];
+                    }];
+                }
             }
         }];
 
diff --git a/Signal/src/contact/SystemContactsFetcher.swift b/Signal/src/contact/SystemContactsFetcher.swift
index b1621c57f..41baba6b8 100644
--- a/Signal/src/contact/SystemContactsFetcher.swift
+++ b/Signal/src/contact/SystemContactsFetcher.swift
@@ -185,11 +185,11 @@ class AddressBookContactStoreAdaptee: ContactStoreAdaptee {
             }
         }
 
-        return Contact(contactWithFirstName: firstName,
-                       andLastName: lastName,
-                       andUserTextPhoneNumbers: phoneNumbers,
-                       andImage: addressBookRecord.image,
-                       andContactID: addressBookRecord.recordId)
+        return Contact(firstName: firstName,
+                       lastName: lastName,
+                       userTextPhoneNumbers: phoneNumbers,
+                       imageData: addressBookRecord.imageData,
+                       contactID: addressBookRecord.recordId)
     }
 }
 
@@ -243,14 +243,14 @@ struct OWSABRecord {
         }
     }
 
-    var image: UIImage? {
+    var imageData: Data? {
         guard ABPersonHasImageData(abRecord) else {
             return nil
         }
         guard let data = ABPersonCopyImageData(abRecord)?.takeRetainedValue() else {
             return nil
         }
-        return UIImage(data: data as Data)
+        return data as Data
     }
 
     private func extractProperty<T>(_ propertyName: ABPropertyID) -> T? {
@@ -316,7 +316,7 @@ class ContactStoreAdapter: ContactStoreAdaptee {
 }
 
 @objc protocol SystemContactsFetcherDelegate: class {
-    func systemContactsFetcher(_ systemContactsFetcher: SystemContactsFetcher, updatedContacts contacts: [Contact])
+    func systemContactsFetcher(_ systemContactsFetcher: SystemContactsFetcher, updatedContacts contacts: [Contact], userRequested: Bool)
 }
 
 @objc
@@ -361,7 +361,7 @@ class SystemContactsFetcher: NSObject {
         hasSetupObservation = true
         self.contactStoreAdapter.startObservingChanges { [weak self] in
             DispatchQueue.main.async {
-                self?.updateContacts(completion: nil, alwaysNotify: false)
+                self?.updateContacts(completion: nil, userRequested: false)
             }
         }
     }
@@ -431,19 +431,20 @@ class SystemContactsFetcher: NSObject {
             return
         }
 
-        updateContacts(completion: nil, alwaysNotify: false)
+        updateContacts(completion: nil, userRequested: false)
     }
 
-    public func fetchIfAlreadyAuthorizedAndAlwaysNotify(completion: ((Error?) -> Void)?) {
+    public func userRequestedRefresh(completion: @escaping (Error?) -> Void) {
         AssertIsOnMainThread()
         guard authorizationStatus == .authorized else {
+            owsFail("should have already requested contact access")
             return
         }
 
-        updateContacts(completion: completion, alwaysNotify: true)
+        updateContacts(completion: completion, userRequested: true)
     }
 
-    private func updateContacts(completion completionParam: ((Error?) -> Void)?, alwaysNotify: Bool = false) {
+    private func updateContacts(completion completionParam: ((Error?) -> Void)?, userRequested: Bool = false) {
         AssertIsOnMainThread()
 
         // Ensure completion is invoked on main thread.
@@ -483,8 +484,8 @@ class SystemContactsFetcher: NSObject {
                 if self.lastContactUpdateHash != contactsHash {
                     Logger.info("\(self.TAG) contact hash changed. new contactsHash: \(contactsHash)")
                     shouldNotifyDelegate = true
-                } else if alwaysNotify {
-                    Logger.info("\(self.TAG) ignoring debounce.")
+                } else if userRequested {
+                    Logger.info("\(self.TAG) ignoring debounce due to user request")
                     shouldNotifyDelegate = true
                 } else {
 
@@ -516,7 +517,7 @@ class SystemContactsFetcher: NSObject {
                 self.lastDelegateNotificationDate = Date()
                 self.lastContactUpdateHash = contactsHash
 
-                self.delegate?.systemContactsFetcher(self, updatedContacts: contacts)
+                self.delegate?.systemContactsFetcher(self, updatedContacts: contacts, userRequested: userRequested)
                 completion(nil)
             }
         }
diff --git a/SignalServiceKit/src/Contacts/Contact.h b/SignalServiceKit/src/Contacts/Contact.h
index 94fbf42b4..8c7729aec 100644
--- a/SignalServiceKit/src/Contacts/Contact.h
+++ b/SignalServiceKit/src/Contacts/Contact.h
@@ -44,11 +44,11 @@ NS_ASSUME_NONNULL_BEGIN
 
 #if TARGET_OS_IOS
 
-- (instancetype)initWithContactWithFirstName:(nullable NSString *)firstName
-                                 andLastName:(nullable NSString *)lastName
-                     andUserTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
-                                    andImage:(nullable UIImage *)image
-                                andContactID:(ABRecordID)record;
+- (instancetype)initWithFirstName:(nullable NSString *)firstName
+                         lastName:(nullable NSString *)lastName
+             userTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
+                        imageData:(nullable NSData *)imageData
+                        contactID:(ABRecordID)record;
 
 - (instancetype)initWithSystemContact:(CNContact *)contact;
 
diff --git a/SignalServiceKit/src/Contacts/Contact.m b/SignalServiceKit/src/Contacts/Contact.m
index af98a4cdc..c0c291c16 100644
--- a/SignalServiceKit/src/Contacts/Contact.m
+++ b/SignalServiceKit/src/Contacts/Contact.m
@@ -27,13 +27,14 @@ NS_ASSUME_NONNULL_BEGIN
 @synthesize fullName = _fullName;
 @synthesize comparableNameFirstLast = _comparableNameFirstLast;
 @synthesize comparableNameLastFirst = _comparableNameLastFirst;
+@synthesize image = _image;
 
 #if TARGET_OS_IOS
-- (instancetype)initWithContactWithFirstName:(nullable NSString *)firstName
-                                 andLastName:(nullable NSString *)lastName
-                     andUserTextPhoneNumbers:(NSArray *)phoneNumbers
-                                    andImage:(nullable UIImage *)image
-                                andContactID:(ABRecordID)record
+- (instancetype)initWithFirstName:(nullable NSString *)firstName
+                         lastName:(nullable NSString *)lastName
+             userTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
+                        imageData:(nullable NSData *)imageData
+                        contactID:(ABRecordID)record
 {
     self = [super init];
     if (!self) {
@@ -47,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
     _userTextPhoneNumbers = phoneNumbers;
     _phoneNumberNameMap = [NSMutableDictionary new];
     _parsedPhoneNumbers = [self parsedPhoneNumbersFromUserTextPhoneNumbers:phoneNumbers phoneNumberNameMap:@{}];
-    _image = image;
+    _imageData = imageData;
     // Not using emails for old AB style contacts.
     _emails = [NSMutableArray new];
 
@@ -128,27 +129,24 @@ NS_ASSUME_NONNULL_BEGIN
     _emails = [emailAddresses copy];
 
     if (contact.thumbnailImageData) {
-        _image = [UIImage imageWithData:contact.thumbnailImageData];
+        _imageData = contact.thumbnailImageData;
     }
 
     return self;
 }
 
-- (instancetype)initWithCoder:(NSCoder *)coder
+- (nullable UIImage *)image
 {
-    self = [super initWithCoder:coder];
-    if (_imageData) {
-        _image = [UIImage imageWithData:_imageData];
+    if (_image) {
+        return _image;
     }
-    return self;
-}
 
-- (void)encodeWithCoder:(NSCoder *)coder
-{
-    if (_image) {
-        _imageData = UIImagePNGRepresentation(_image);
+    if (!self.imageData) {
+        return nil;
     }
-    [super encodeWithCoder:coder];
+
+    _image = [UIImage imageWithData:self.imageData];
+    return _image;
 }
 
 - (NSString *)trimName:(NSString *)name