Handling Messages from PubNub History as a “Global

2020-07-29 02:06发布

问题:

I'm trying to figure out the smartest, most reliable way to make my retrieved PubNub history accessible for several view controllers. As i see there are multiple ways to do that, but after a bunch of questions and articles i've read i can't decide which solution would be the best. Actually i think dependency injection would be the right idea in my case, but i'm not sure because i saw different solutions in the sample PubNub apps and overall i never heard about that so i would avoid it, if it's possible.

The possible ways:

1. Using AppDelegate

It could be the easiest way, but a lot of developers said, that AppDelegate is really not the best place for storing global data. So i wouldn't like to do this way.

2. Using Singleton

As an instance,i saw this solution in the PubNub's iPad demo app (PNDataManager file). I was sure this is the best way, but after i read Stephen Poletto's article on objc.io about singletons i've changed my mind, because he pointed to some issues, that can be substantial in my case.

It's a really useful article, that's worth reading, but actually i will grab only the thoughts that i found important in my situation.

"suppose we’re building an app where users can see a list of their friends. Each of their friends has a profile picture, and we want the app to be able to download and cache those images on the device. With the dispatch_once snippet handy, we might find ourselves writing an SPThumbnailCache singleton"

Suppose we want to cache the message array instead of images, that we retrieved with this method [PubNub requestFullHistoryForChannel: withCompletionBlock:^(NSArray *message, PNChannel *channel, PNDate *fromDate, PNDate *toDate, PNError *error)}]; in our root view controller called ViewController1.

"We continue building out the app, and all seems well in the world, until one day, when we decide it’s time to implement the ‘log out’ functionality, so users can switch accounts inside the app. Suddenly, we have a nasty problem on our hands: user-specific state is stored in a global singleton. When the user signs out of the app, we want to be able to clean up all persistent states on disk. Otherwise, we’ll leave behind orphaned data on the user’s device, wasting precious disk space. In case the user signs out and then signs into a new account, we also want to be able to have a new SPThumbnailCache for the new user. The problem here is that singletons, by definition, are assumed to be “create once, live forever” instances. You could imagine a few solutions to the problem outlined above. Perhaps we could tear down the singleton instance when the user signs out

The problem here is that singletons, by definition, are assumed to be “create once, live forever” instances. You could imagine a few solutions to the problem outlined above. Perhaps we could tear down the singleton instance when the user signs out..We could certainly make this solution work, but the cost is far too great. For one, we’ve lost the simplicity of the dispatch_once solution, a solution which guarantees thread safety and that all code calling [SPThumbnailCache sharedThumbnailCache] only ever gets the same instance. We now need to be extremely careful about the order of code execution for code that utilizes the thumbnail cache.... Since there’s no distinct owner for the singleton instance (i.e. the singleton manages its own lifecycle), it becomes very difficult to ever ‘shut down’ a singleton. The lesson here is that singletons should be preserved only for state that is global, and not tied to any scope. If state is scoped to any session shorter than “a complete lifecycle of my app,” that state should not be managed by a singleton.A singleton that’s managing user-specific state is a code smell, and you should critically reevaluate the design of your object graph."

When the user signs in to a new account, we should be able to construct and interact with a brand new SPThumbnailCache, with no attention paid to the destruction of the old thumbnail cache. The old view controllers and old thumbnail cache should be cleaned up lazily in the background on their own accord, based on the typical rules of object management. In short, we should isolate the state associated with user A from the state associated with user B.

I know one NSArray with PNMessage objects, that doesn't contain images is not the same and it's easier to handle them, but i'm really confused how should i deal with them.

Originally i would pass the message variable inside the requestFullHistoryForChannel: withCompletionBlock: to an instance variable called self.messageGlobal (that i can use with a singleton in every views) and reset it when the user logs out.

//inside the viewWillAppear
[PubNub requestFullHistoryForChannel: withCompletionBlock:^(NSArray *message, PNChannel *channel, PNDate *fromDate, PNDate *toDate, PNError *error)

     // self.messageGlobal should be managed by a singleton or appdelegate or use the dependency injection??
     self.messageGlobal = message;

    }];

- (IBAction)logOutButton:(id)sender {

      //disconnect from the PubNub network
      [PubNub unsubscribeFromChannel:currentUserChannel];

      //prepape ivar for the new message history
      [self.messageGlobal removeAllObjects];

      // log out user (i'm using parse.com for managing users) 
      [PFUser logOut] 
} 

After Stephen's article i feel i'm lost, it would be amazing if somebody could explain the right direction.

My plan is to create a method in the ViewController1, that handles the history.

-(void)downloadPubNubHistory {

 [PubNub requestFullHistoryForChannel: withCompletionBlock:^(NSArray *message, PNChannel *channel, PNDate *fromDate, PNDate *toDate, PNError *error)

 // self.messageGlobal should be managed by a singleton or appdelegate or use the dependency injection??
 self.messageGlobal = message;

}];

}

In the AppDelegate or ViewController1 i would place an observer to get notified when the current users channel recieved a new message, it's important because i would like the call downloadPubNubHistory everytime he or she receives a new message and update the current view with the new content.

[[PNObservationCenter defaultCenter] addMessageReceiveObserver:currentUserChannel withBlock:^(PNMessage * msg) { 

  [ViewController1 downloadPubNubHistory]; 

 } }]; 

For example in ViewController3 i list every message that was sent by sampleUserB to the channel of sampleUserA (current user), if sampleUserA's channel receives a new message from sampleUserB on her/his channel i need to reload downloadPubNubHistory, and also reload ViewController3, because i'm filtering the self.messageGlobal in the ViewController3 to get messages only from sampleUserB.

Any opinion and suggestion welcomed, it's possible that i misunderstood the whole concept and i could basically use singleton and reset it everytime when the user log out without any problems, but i would like to utilize the most lightweight reliable solution. If there is any other recommended technique i would love to hear it too.


Original source of the article

回答1:

For me, singleton always was a tool which is served for concrete purpose. I don't see any troubles event with multiuser and their switch based on singleton (you can always reset state of singleton and not try to destroy it).
Because we work a lot with data, we can't afford to initialize some data model all the time when we need to use it, because there will be spend additional time on: allocation & initialization, presetting initial state, maybe loading some data from file system or network. If we will perform all this steps in each view or data object where we would like to manipulate with data - it will kill our app responsiveness and user will have some bad experience.
So, it all depends on how you will code your singleton and how you will use it. If you have multiuser app and some sensitive data should be keep away from another user you can create some User entities and store all required data in it (in your case messages can be passed to it). This entity will be some simple data model or data object and still you will be required to have some class responsible for it. Here you can create some singleton which will be responsible for managing current user and his data (when you specify current user, old one can be deallocated and all data can be pushed into some local storage like CoreData on sqlite).
As I understand, you chat application want to pull out user history after login... You can do it in class which will manage User entity and fetch history as soon as current user will be changed (in you example you call history API each time, when new message arrive to the user - bad idea because history API may be heavy). Your PFUser instance can store all required data as for channels on which it should be subscribed or unsubscribed and they can be used during logOut process.