mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/readReceiptsManager'
commit
1382270c66
@ -1,15 +0,0 @@
|
|||||||
// Created by Michael Kirk on 9/14/16.
|
|
||||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@class PushManager;
|
|
||||||
|
|
||||||
@interface OWSStaleNotificationObserver : NSObject
|
|
||||||
|
|
||||||
- (instancetype)initWithPushManager:(PushManager *)pushManager NS_DESIGNATED_INITIALIZER;
|
|
||||||
- (void)startObserving;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,71 +0,0 @@
|
|||||||
// Created by Michael Kirk on 9/14/16.
|
|
||||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
|
||||||
|
|
||||||
#import "OWSStaleNotificationObserver.h"
|
|
||||||
#import "PushManager.h"
|
|
||||||
#import <SignalServiceKit/OWSReadReceiptsProcessor.h>
|
|
||||||
#import <SignalServiceKit/TSIncomingMessage.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface OWSStaleNotificationObserver ()
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) PushManager *pushManager;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation OWSStaleNotificationObserver
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init
|
|
||||||
{
|
|
||||||
return [self initWithPushManager:[PushManager sharedManager]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithPushManager:(PushManager *)pushManager
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (!self) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pushManager = pushManager;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)startObserving
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(handleMessageRead:)
|
|
||||||
name:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
|
|
||||||
object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handleMessageRead:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
if ([notification.object isKindOfClass:[TSIncomingMessage class]]) {
|
|
||||||
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
|
|
||||||
|
|
||||||
DDLogDebug(@"%@ canceled notification for message:%@", self.tag, message);
|
|
||||||
[self.pushManager cancelNotificationsWithThreadId:message.uniqueThreadId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (NSString *)tag
|
|
||||||
{
|
|
||||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)tag
|
|
||||||
{
|
|
||||||
return self.class.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,18 +0,0 @@
|
|||||||
// Created by Michael Kirk on 9/24/16.
|
|
||||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@class OWSMessageSender;
|
|
||||||
@class TSIncomingMessage;
|
|
||||||
|
|
||||||
@interface OWSSendReadReceiptsJob : NSObject
|
|
||||||
|
|
||||||
- (instancetype)init NS_UNAVAILABLE;
|
|
||||||
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender NS_DESIGNATED_INITIALIZER;
|
|
||||||
- (void)runWith:(TSIncomingMessage *)message;
|
|
||||||
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,108 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "OWSSendReadReceiptsJob.h"
|
|
||||||
#import "OWSMessageSender.h"
|
|
||||||
#import "OWSReadReceipt.h"
|
|
||||||
#import "OWSReadReceiptsMessage.h"
|
|
||||||
#import "TSContactThread.h"
|
|
||||||
#import "TSIncomingMessage.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface OWSSendReadReceiptsJob ()
|
|
||||||
|
|
||||||
@property (atomic) NSMutableArray<OWSReadReceipt *> *readReceiptsQueue;
|
|
||||||
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
|
||||||
@property BOOL isObserving;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation OWSSendReadReceiptsJob
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (!self) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
_readReceiptsQueue = [NSMutableArray new];
|
|
||||||
_messageSender = messageSender;
|
|
||||||
_isObserving = NO;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)runWith:(TSIncomingMessage *)message
|
|
||||||
{
|
|
||||||
// authorId isn't set on all legacy messages, so we take
|
|
||||||
// extra measures to ensure we obtain a valid value.
|
|
||||||
NSString *messageAuthorId;
|
|
||||||
if (message.authorId) { // Group Thread
|
|
||||||
messageAuthorId = message.authorId;
|
|
||||||
} else { // Contact Thread
|
|
||||||
messageAuthorId = [TSContactThread contactIdFromThreadId:message.uniqueThreadId];
|
|
||||||
}
|
|
||||||
|
|
||||||
OWSReadReceipt *readReceipt = [[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
|
|
||||||
[self.readReceiptsQueue addObject:readReceipt];
|
|
||||||
|
|
||||||
// Wait a bit to bundle up read receipts into one request.
|
|
||||||
__weak typeof(self) weakSelf = self;
|
|
||||||
[weakSelf performSelector:@selector(sendAllReadReceiptsInQueue) withObject:nil afterDelay:2.0];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendAllReadReceiptsInQueue
|
|
||||||
{
|
|
||||||
// Synchronized so we don't lose any read receipts while replacing the queue
|
|
||||||
__block NSArray<OWSReadReceipt *> *_Nullable receiptsToSend;
|
|
||||||
@synchronized(self)
|
|
||||||
{
|
|
||||||
if (self.readReceiptsQueue.count > 0) {
|
|
||||||
receiptsToSend = self.readReceiptsQueue;
|
|
||||||
self.readReceiptsQueue = [NSMutableArray new];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receiptsToSend) {
|
|
||||||
[self sendReadReceipts:receiptsToSend];
|
|
||||||
} else {
|
|
||||||
DDLogVerbose(@"Read receipts queue already drained.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
|
||||||
{
|
|
||||||
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceipts];
|
|
||||||
|
|
||||||
[self.messageSender sendMessage:message
|
|
||||||
success:^{
|
|
||||||
DDLogInfo(@"%@ Successfully sent %ld read receipt", self.tag, (unsigned long)readReceipts.count);
|
|
||||||
}
|
|
||||||
failure:^(NSError *error) {
|
|
||||||
DDLogError(@"%@ Failed to send read receipt with error: %@", self.tag, error);
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Logging
|
|
||||||
|
|
||||||
+ (NSString *)tag
|
|
||||||
{
|
|
||||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)tag
|
|
||||||
{
|
|
||||||
return self.class.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,19 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@class TSStorageManager;
|
|
||||||
@class OWSMessageSender;
|
|
||||||
|
|
||||||
@interface OWSIncomingMessageReadObserver : NSObject
|
|
||||||
|
|
||||||
- (instancetype)init NS_UNAVAILABLE;
|
|
||||||
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender NS_DESIGNATED_INITIALIZER;
|
|
||||||
|
|
||||||
- (void)startObserving;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -1,79 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "OWSIncomingMessageReadObserver.h"
|
|
||||||
#import "NSDate+millisecondTimeStamp.h"
|
|
||||||
#import "OWSSendReadReceiptsJob.h"
|
|
||||||
#import "TSIncomingMessage.h"
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
|
||||||
|
|
||||||
@interface OWSIncomingMessageReadObserver ()
|
|
||||||
|
|
||||||
@property (nonatomic) BOOL isObserving;
|
|
||||||
@property (nonatomic, readonly) OWSSendReadReceiptsJob *sendReadReceiptsJob;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation OWSIncomingMessageReadObserver
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (!self) {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isObserving = NO;
|
|
||||||
_sendReadReceiptsJob = [[OWSSendReadReceiptsJob alloc] initWithMessageSender:messageSender];
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)startObserving
|
|
||||||
{
|
|
||||||
OWSAssert([NSThread isMainThread]);
|
|
||||||
|
|
||||||
if (self.isObserving) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isObserving = true;
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(handleLocalReadNotification:)
|
|
||||||
name:TSIncomingMessageWasReadOnThisDeviceNotification
|
|
||||||
object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)handleLocalReadNotification:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
if (![notification.object isKindOfClass:[TSIncomingMessage class]]) {
|
|
||||||
DDLogError(@"%@ Read receipt notifier got unexpected object: %@", self.tag, notification.object);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
|
|
||||||
[self.sendReadReceiptsJob runWith:message];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Logging
|
|
||||||
|
|
||||||
+ (NSString *)tag
|
|
||||||
{
|
|
||||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)tag
|
|
||||||
{
|
|
||||||
return self.class.tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@class TSIncomingMessage;
|
||||||
|
|
||||||
|
// There are four kinds of read receipts:
|
||||||
|
//
|
||||||
|
// * Read receipts that this client sends to linked
|
||||||
|
// devices to inform them that a message has been read.
|
||||||
|
// * Read receipts that this client receives from linked
|
||||||
|
// devices that inform this client that a message has been read.
|
||||||
|
// * These read receipts are saved so that they can be applied
|
||||||
|
// if they arrive before the corresponding message.
|
||||||
|
// * Read receipts that this client sends to other users
|
||||||
|
// to inform them that a message has been read.
|
||||||
|
// * Read receipts that this client receives from other users
|
||||||
|
// that inform this client that a message has been read.
|
||||||
|
// * These read receipts are saved so that they can be applied
|
||||||
|
// if they arrive before the corresponding message.
|
||||||
|
//
|
||||||
|
// TODO: Merge OWSReadReceiptsProcessor into this class.
|
||||||
|
@interface OWSReadReceiptManager : NSObject
|
||||||
|
|
||||||
|
- (instancetype)init NS_UNAVAILABLE;
|
||||||
|
+ (instancetype)sharedManager;
|
||||||
|
|
||||||
|
// This method can be called from any thread.
|
||||||
|
//
|
||||||
|
// It cues this manager:
|
||||||
|
//
|
||||||
|
// * ...to inform the sender that this message was read (if read receipts
|
||||||
|
// are enabled).
|
||||||
|
// * ...to inform the local user's other devices that this message was read.
|
||||||
|
//
|
||||||
|
// Both types of messages are deduplicated.
|
||||||
|
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,187 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "OWSReadReceiptManager.h"
|
||||||
|
#import "OWSMessageSender.h"
|
||||||
|
#import "OWSReadReceipt.h"
|
||||||
|
#import "OWSReadReceiptsMessage.h"
|
||||||
|
#import "TSContactThread.h"
|
||||||
|
#import "TSDatabaseView.h"
|
||||||
|
#import "TSIncomingMessage.h"
|
||||||
|
#import "TextSecureKitEnv.h"
|
||||||
|
#import "Threading.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface OWSReadReceiptManager ()
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) TSStorageManager *storageManager;
|
||||||
|
@property (nonatomic, readonly) OWSMessageSender *messageSender;
|
||||||
|
|
||||||
|
// A map of "thread unique id"-to-"read receipt" for read receipts that
|
||||||
|
// we will send to our linked devices.
|
||||||
|
//
|
||||||
|
// Should only be accessed while synchronized on the OWSReadReceiptManager.
|
||||||
|
@property (nonatomic, readonly) NSMutableDictionary<NSString *, OWSReadReceipt *> *toLinkedDevicesReadReceiptMap;
|
||||||
|
|
||||||
|
// Should only be accessed while synchronized on the OWSReadReceiptManager.
|
||||||
|
@property (nonatomic) BOOL isProcessing;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
@implementation OWSReadReceiptManager
|
||||||
|
|
||||||
|
+ (instancetype)sharedManager
|
||||||
|
{
|
||||||
|
static OWSReadReceiptManager *sharedMyManager = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
sharedMyManager = [[self alloc] initDefault];
|
||||||
|
});
|
||||||
|
return sharedMyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initDefault
|
||||||
|
{
|
||||||
|
OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
|
||||||
|
|
||||||
|
return [self initWithMessageSender:messageSender];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
|
||||||
|
if (!self) {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
_messageSender = messageSender;
|
||||||
|
|
||||||
|
_toLinkedDevicesReadReceiptMap = [NSMutableDictionary new];
|
||||||
|
|
||||||
|
OWSSingletonAssert();
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(databaseViewRegistrationComplete)
|
||||||
|
name:kNSNotificationName_DatabaseViewRegistrationComplete
|
||||||
|
object:nil];
|
||||||
|
|
||||||
|
// Try to start processing.
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self scheduleProcessing];
|
||||||
|
});
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)databaseViewRegistrationComplete
|
||||||
|
{
|
||||||
|
[self scheduleProcessing];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedules a processing pass, unless one is already scheduled.
|
||||||
|
- (void)scheduleProcessing
|
||||||
|
{
|
||||||
|
DispatchMainThreadSafe(^{
|
||||||
|
@synchronized(self)
|
||||||
|
{
|
||||||
|
if ([TSDatabaseView hasPendingViewRegistrations]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (self.isProcessing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isProcessing = YES;
|
||||||
|
|
||||||
|
// Process read receipts every N seconds.
|
||||||
|
//
|
||||||
|
// We want a value high enough to allow us to effectively deduplicate,
|
||||||
|
// read receipts without being so high that we risk not sending read
|
||||||
|
// receipts due to app exit.
|
||||||
|
const CGFloat kProcessingFrequencySeconds = 3.f;
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kProcessingFrequencySeconds * NSEC_PER_SEC)),
|
||||||
|
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||||
|
^{
|
||||||
|
[self process];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)process
|
||||||
|
{
|
||||||
|
@synchronized(self)
|
||||||
|
{
|
||||||
|
self.isProcessing = NO;
|
||||||
|
|
||||||
|
NSArray<OWSReadReceipt *> *readReceiptsToSend = [self.toLinkedDevicesReadReceiptMap allValues];
|
||||||
|
[self.toLinkedDevicesReadReceiptMap removeAllObjects];
|
||||||
|
if (readReceiptsToSend.count > 0) {
|
||||||
|
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceiptsToSend];
|
||||||
|
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self.messageSender sendMessage:message
|
||||||
|
success:^{
|
||||||
|
DDLogInfo(@"%@ Successfully sent %zd read receipt to linked devices.",
|
||||||
|
self.tag,
|
||||||
|
readReceiptsToSend.count);
|
||||||
|
}
|
||||||
|
failure:^(NSError *error) {
|
||||||
|
DDLogError(@"%@ Failed to send read receipt to linked devices with error: %@", self.tag, error);
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
|
||||||
|
{
|
||||||
|
@synchronized(self)
|
||||||
|
{
|
||||||
|
NSString *threadUniqueId = message.uniqueThreadId;
|
||||||
|
OWSAssert(threadUniqueId.length > 0);
|
||||||
|
|
||||||
|
NSString *messageAuthorId = message.messageAuthorId;
|
||||||
|
OWSAssert(messageAuthorId.length > 0);
|
||||||
|
|
||||||
|
OWSReadReceipt *newReadReceipt =
|
||||||
|
[[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
|
||||||
|
|
||||||
|
OWSReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId];
|
||||||
|
if (oldReadReceipt && oldReadReceipt.timestamp > newReadReceipt.timestamp) {
|
||||||
|
// If there's an existing read receipt for the same thread with
|
||||||
|
// a newer timestamp, discard the new read receipt.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
|
||||||
|
|
||||||
|
[self scheduleProcessing];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Logging
|
||||||
|
|
||||||
|
+ (NSString *)tag
|
||||||
|
{
|
||||||
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)tag
|
||||||
|
{
|
||||||
|
return self.class.tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
Loading…
Reference in New Issue