How to call specific function for a global class? - ios

I have some Realm models in my app that all use a base class. In this class I wrote some generic functions like the one below:
- (void)save {
self.updatedAt = [NSDate date];
[self.realm beginWriteTransaction];
[self.realm addOrUpdateObject:self];
[self.realm commitWriteTransaction];
[[SyncEngine sharedInstance] store:self];
}
Now, I also wrote a class called SyncEngine, which checks if some available synchronization methods are enabled and then calls them:
- (void)store:(id)object {
if ([Preferences CloudKitEnabled]) {
[self.cloudKit store:object];
}
}
This is where my problem arises. I have written a base class called CloudKitManager which has some generic functions. I then create a specific CloudKitClass for every model in my app, so I'll end up with CloudKitRestaurant and CloudKitTable. All of these will contain a function (void)store:(id)sender. What would be the best way to call the store function of a specific CloudKit class, based on the class that is being stored in Realm?
Ideally, I'd like for RLMRestaurant to automatically use CloudKitRestaurant and not have to use and if else or switch statement.
For further clarity, this is how SyncEngine works.
#interface SyncEngine()
#property (nonatomic, strong) CloudKitManager *cloudKitManager;
#end
#implementation SyncEngine
static SyncEngine *sharedInstance = nil;
+ (SyncEngine *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[self alloc] init];
}
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
self.cloudKitManager = [[CloudKitManager alloc] init];
}
return self;
}
#end

In my opinion, you should keep type of CloudKitManager class inside RLMBase object. And when you need to call [[CloudKitManager sharedInstance] store:object], call [[object.cloudKitClass sharedInstance] store:object].
Try my code below.
#interface RLMBase : NSObject
- (Class)cloudKitClass;
#end
#implementation RLMBase
- (Class)cloudKitClass {
// Must be overridden in subclass.
return CloudKitManager.class;
}
#end
#interface RLMRestaurant : RLMBase
#end
#implementation RLMRestaurant
- (Class)cloudKitClass {
return CloudKitRestaurant.class;
}
#end
- (void)store:(RLMBase *)object {
if ([Preferences CloudKitEnabled]) {
[[object.cloudKitClass sharedInstance] store:object];
}
}
ANOTHER WAY
Put store: method from SyncEngine to RLMBase object.
#interface RLMBase : NSObject
- (Class)cloudKitClass;
- (void)store;
#end
#implementation RLMBase
- (Class)cloudKitClass {
// Must be overridden in subclass.
return CloudKitManager.class;
}
- (void)store {
if ([Preferences CloudKitEnabled]) {
[[self.cloudKitClass sharedInstance] store:self];
}
}
#end
And save method will become
- (void)save {
self.updatedAt = [NSDate date];
[self.realm beginWriteTransaction];
[self.realm addOrUpdateObject:self];
[self.realm commitWriteTransaction];
[self store];
}

Related

performSelector not executing

here is my code, i'm new to NSThread, why these code doesn't execute? plz help.
Basically i have a viewcontroller, and i call TESTC() when a button is pressed,
you should know TESTC() is a C function.
Thanks in advance.
#interface Worker : NSObject
#property (nonatomic, strong) NSThread *thread;
- (void)performTask1 : (void *)context;
#end
#implementation Worker
- (instancetype)init
{
if (self = [super init]) {
_thread = [[NSThread alloc] init];
}
return self;
}
- (void)performTask1 : (void *)context
{
[self performSelector:#selector(Task1) onThread:self.thread withObject:(__bridge id _Nullable)(context) waitUntilDone:YES];
}
- (void)Task1
{
NSLog(#"Task1 proceed");
}
#end
void TESTC()
{
Worker *kelvin = [[Worker alloc] init];
[kelvin performTask1:nil];
}

Send command and wait for reply - Wait for delegate in Obj-C

My goal is to achieve synchronized communication to custom Device i.e. next command can be send only when reply is received. Now I'm doing it in this way
Device class implements DeviceDelegate protocol
//Device.h
#class Device;
#protocol DeviceDelegate <NSObject>
- (void)didReciveReplyWithData:(NSData *)data;
#end
#interface Device : NSObject {}
In DeviceViewController implementation:
#interface DeviceViewController()
{
BOOL waitingForReply = false;
}
#end
#implementation DeviceViewController
- (void)sendCommandWithData:(NSData *)data
{
if ( waitingForReply == false)
{
//send command code
waitingForReply = true;
}
}
- (void)didReciveReplyWithData:(NSData *)data
{
//code
waitingForReply = false;
}
#end
but I wish to do it in more elegant way i.e. by using GCD (semaphores?) with blocks (completionHandler?). Any ideas?
PS. Sorry, but I forgot to mention: all commands sended to device while
waitingForReply = true
should be ignored!!!.
Possibly the best approach here would be to create a queue of commands with NSOperationQueue.
Since, presumably, the communication with the device is asynchronous you will have to subclass NSOperation to encapsulate the communication.
#interface DeviceCommandOperation : NSOperation <DeviceDelegate>
#property (nonatomic, assign) BOOL waitingForReply;
#property (nonatomic, copy) NSData *dataToSend;
#property (nonatomic, copy) NSData *dataReceived;
#end
#implementation DeviceCommandOperation
- (instancetype)initWithData:(NSData *)dataToSend
{
self = [super init];
if (self)
{
_dataToSend = [dataToSend copy];
}
return self;
}
- (void)setWaitingForReply:(BOOL)waitingForReply
{
if (_waitingForReply != waitingForReply)
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_waitingForReply = waitingForReply;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
self.waitingForReply = YES;
// Simulate sending a command and waiting for response.
// You will need to replace this with your actual communication mechanism.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// In reality this call would presumably come from the Device
[self didReceiveReplyWithData:someData];
});
}
- (void)didReceiveReplyWithData:(NSData *)data
{
self.dataReceived = data;
self.waitingForReply = NO;
}
#pragma mark - NSOperation
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
return _waitingForReply;
}
- (BOOL)isFinished
{
return !_waitingForReply;
}
#end
This operation could then be used from your DeviceViewController (it would probably be better architecturally to have this responsibility elsewhere but that's not the topic of this question).
#interface DeviceViewController ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
#implementation DeviceViewController
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [[NSOperationQueue alloc] init];
}
return _operationQueue;
}
- (void)sendNextCommand
{
NSData *data = // Get data for the next command
[self sendCommandWithData:data];
}
- (void)sendCommandWithData:(NSData *)data
{
NSLog(#"Queueing operation");
DeviceCommandOperation *operation = [[DeviceCommandOperation alloc] initWithData:data];
// The operation's completionBlock gets called on a background queue
[operation setCompletionBlock:^{
NSLog(#"DeviceCommandOperation completed");
// Process operation.dataReceived
[self sendNextCommand];
}];
[self.operationQueue addOperation:operation];
}
#end
This approach will allow you to determine what (if any) command to send next, based on the reply to the previous command.
If you know all of the "commands" you will want to send initially and don't need finer grained control you could create instances of DeviceCommandOperation for each command, set the queue's maxConcurrentOperationCount to 1, and add each DeviceCommandOperation to the queue (in the order you want them to be processed).

OCMock unit test error

I use OCMock to test out singleton methods. I get "no such method exists in the mocked class." error for testSingleton method and infinite loop (the screenshot, the spinning indicator) for testSingletonWithBlock method
EDIT:
download sample project here
https://drive.google.com/file/d/0B-iP0P7UfFj0LVFpWWpPb3RDZFU/edit?usp=sharing
Here is my implementation
manager:
#implementation Manager
+ (Manager *)sharedManager {
static Manager *instance;
dispatch_once_t predicate;
dispatch_once(&predicate, ^{
instance = [Manager new];
});
return instance;
}
- (int)getOne {
return 1;
}
- (void)success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
success ? completion() : failure();
}
view controller:
- (void)manager_printOne {
int num = [[Manager sharedManager] getOne];
NSLog(#"number is: %d", num);
}
- (void)manager_success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
[[Manager sharedManager] success:success completion:completion failure:failure];
}
test view controller:
#interface coreDataTestTests : XCTestCase
#property (nonatomic, strong) id mockManager;
#property (nonatomic, strong) ViewController *viewController;
#end
#implementation coreDataTestTests
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
self.mockManager = [Manager createNiceMockManager];
}
- (void)tearDown
{
[super tearDown];
self.viewController = nil;
//Note: singleton need both, retain counts = 2
self.mockManager = nil;
[Manager releaseInstance];
}
- (void)testSingleton {
NSLog(#"testSingleton");
OCMStub([self.mockManager getOne]).andReturn(2);
[self.viewController manager_printOne];
}
- (void)testSingletonWithBlock {
NSLog(#"testSingletonWithBlock");
[[[[self.mockHelper stub] ignoringNonObjectArgs] andDo:^(NSInvocation *invocation) {
void(^block)(void);
[invocation getArgument:&block atIndex:3];
block();
}] success:0 completion:[OCMArg any] failure:[OCMArg any]];
[self.viewController manager_success:NO completion:^{
NSLog(#"completion");
} failure:^{
NSLog(#"failure");
}];
}
#end
manager category for unit test:
static Manager *mockManager = nil;
#implementation Manager
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (Manager *)sharedManager {
if (mockManager) {
return mockManager;
}
return invokeSupersequentNoParameters();
}
#pragma clang diagnostic pop
+(id)createMockManager {
mockManager = [OCMockObject mockForClass:[Manager class]];
return mockManager;
}
+(id)createNiceMockManager {
mockManager = [OCMockObject niceMockForClass:[Manager class]];
return mockManager;
}
+(void)releaseInstance {
mockManager = nil;
}
Rather than creating a category, you could just stub sharedManager and return a nice mock.
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
id classMockManager = OCClassMock([Manager class]);
OCMStub([classMockManager sharedManager]).andReturn(classMockManager);
self.mockManager = classMockManager;
}
I don't have an environment up to test this just this moment, but this strategy should work. Note that this is OCMock3 syntax. See http://ocmock.org/reference/#mocking-class-methods
In your description above you write "manager category for unit test", but the implementation that follows is not a category, it's an actual implementation of the Manager class, i.e. the code reads
#implementation Manager
and not
#implementation Manager(unitTests)
It seems that the test code uses this second implementation of Manager and that implementation does not have a getOne method. So the mock is right to complain; the implementation of Manager it sees does not have the method and hence it can't stub it.
I believe you can fix your test by making the implementation a category. As far as I know it is possible to override a class method (sharedManager in your case) in a category, but it's a bit dicey to do so. The approach described by Ben Flynn is better.

Retrieve data from NSObject's property

I have class called GlobalArray which is an NSObject. It has an NSArray property called globalData.
I'm passing data into globalData inside of my ViewControllerOne.m, it works perfect, i can print the log in the console. The problem is, that i'm unable to retrieve this data in ViewControllerTwo.m.
GlobalArray.h
#import <Foundation/Foundation.h>
#interface GlobalArray : NSObject
#property (nonatomic, retain) NSArray *globalData; // why retain?
GlobalArray.m
#import "GlobalArray.h"
#implementation GlobalArray
- (id) init
{
self = [super init];
if(self)
{
self.globalData = [[NSArray alloc] init];
}
return(self);
}
ViewControllerOne.m (GlobalArray.h imported into .h)
- (void)viewWillAppear:(BOOL)animated {
[PubNub requestHistoryForChannel:my_channel from:nil to:nil limit:100 reverseHistory:NO withCompletionBlock:^(NSArray *message, PNChannel *channel, PNDate *fromDate, PNDate *toDate, PNError *error) {
GlobalArray *fromHistory = [[GlobalArray alloc] init];
fromHistory.globalData = message;
NSLog(#"TEST LOG 1 %#", fromHistory.globalData);
}];
}
I try to retrieve it in ViewControllerTwo.m this way: (ViewController.h and GlobalArray.h is imported)
-(void) viewWillAppear:(BOOL)animated {
GlobalArray *history = [[GlobalArray alloc] init];
NSArray *sampleArr = [[NSArray alloc] init];
sampleArr = history.globalData;
NSLog(#" TEST LOG2 %#", sampleArr);
}
But TEST LOG2 is empty. I think i missed something in the ViewControllerTwo.m, but can't figure it out, for me it seems it's correct.
If you'd like to avoid the classic Singleton pattern, you can bind a session object to the app delegate and implement the methods to login / logout:
#interface XXXAppDelegate : UIResponder <UIApplicationDelegate>
+ (XXXSession *)loginWithUserName:(NSString*)userName password:(NSString*)password;
+ (void)logout;
+ (XXXSession)currentSession;
#end
Then you define the data managed in your session:
#interface XXXSession : NSObject
#property (nonatomic, retain) NSArray *globalData;
#end
Initialize the session object it in application:didiFinishLaunchingWithOptions: or where it is needed in your application:
#implementation XXXAppDelegate {
XXXSession *_currentSession;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self loginWithUserName: #"Test"];
}
#end
In your ViewControllers you can obtain the session as follow:
[XXXAppDelegate currentSession].globalData
This approach is similar to have a singleton object with the difference that the access to the instance is not offered by the singleton class itself (as stated in the definition of this Design Pattern) but it is implemented in the application delegate.
Of course you'll get empty because you are initializing a separate object of type GlobalArray in your ViewControllerTwo.
This is like you do:
Car car1 = [[Car alloc] init];
car1.name = #"BMW";
Car car2 = [[Car alloc] init];
NSLog(#"Car name = %#", car2.name); <--- this will be empty!
You need to keep the GlobalArray variable somewhere to access it later in ViewControllerTwo instead of reinitializing it, or make the GlobalArray class singleton to always return the same instance instead of creating separate instances.

token is saved but unable to bypass the login page

I am trying to bypass the login page to welcome page if the person already have a token. I am using basic auth and afnetworking for the api call. Once the user logs the username&password, I base 64 the info and get a token. I save the token in a nsobject class where I create a token singleton and a simple is logged in method where it checks if the user has the token or not. But for some reason I always see the login page(meaning it skips my condition method). If any one can point out where I am making the mistake, it would be great.
This is class where I create the login singleton
CredentialStore.h
#import <Foundation/Foundation.h>
#import "LoginInfo.h"
#interface CredentialStore : NSObject
-(BOOL)isLoggedIn;
#property (nonatomic) LoginInfo *loginInfo;
+(id)sharedStore;
#end
CredentialStore.m
#import "CredentialStore.h"
static CredentialStore *sharedInsance;
#implementation CredentialStore
+(id)sharedStore
{
if (!sharedInsance) {
sharedInsance = [[CredentialStore alloc] init];
}
return sharedInsance;
}
- (BOOL)isLoggedIn {
return (self.loginInfo.authToken != nil);
}
#end
this is my authenticationapimanager class
AuthAPIManager.h
#import "AFHTTPSessionManager.h"
#interface AuthAPIManager : AFHTTPSessionManager
+ (id)sharedManager;
#end
AuthAPIManager.m
#import "AuthAPIManager.h"
#import "CredentialStore.h"
#define BASE_URL #"http://Url"
#define Base_Proxy #"http://Url"
static AuthAPIManager *sharedManager;
#implementation AuthAPIManager
//Setup the singleton to use throught the life of the application
+(id)sharedManager
{
if (!sharedManager) {
sharedManager = [[AuthAPIManager alloc] initWithBaseURL:[NSURL URLWithString:Base_Proxy]];
}
return sharedManager;
}
-(id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
}
return self;
}
#end
this is the class where I save the login information
LoginInfo.h
#import <Foundation/Foundation.h>
#interface LoginInfo : NSObject
#property(nonatomic,copy)NSNumber *AccountId;
#property(nonatomic,copy)NSString *DeviceType;
#property(nonatomic,copy)NSString *HardwareId;
#property(nonatomic,copy)NSString *NickName;
#property(nonatomic,copy)NSString *authToken;
-(id)initWithDictionary:(NSDictionary *)dictionary;
#end
LoginInfo.m
#import "LoginInfo.h"
#implementation LoginInfo
-(id)initWithDictionary:(NSDictionary *)dictionary
{
self =[super init];
if (self) {
self.AccountId = [dictionary objectForKey:#"AccountId"];
self.DeviceType = [dictionary objectForKey:#"DeviceType"];
self.HardwareId = [dictionary objectForKey:#"HardwareId"];
self.NickName = [dictionary objectForKey:#"NickName"];
}
return self;
}
and in my view controller, in viewwillappearmethod, I check if the user has a token or not but this is the part I am having problems with
LoginViewController.m
#interface LoginViewController ()
#property (nonatomic,strong) CredentialStore *credentialStore;
#end
implementation LoginViewController
#pragma mark - UIViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self checkIfThePersonLoggedIn];
}
-(void)checkIfThePersonLoggedIn
{
CredentialStore *credStore = [CredentialStore sharedStore];
// //check to see if the use has the token already
if (credStore.loginInfo.authToken != nil) {
[self.loginButton setHidden:YES];
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
}
else [self.loginButton setHidden:NO];
NSLog(#"checkForToken - loginviewcontroller");
}
and in my getTokenRequest this is how I save the token in the event of success
NSString *authToken = [responseObject objectForKey:#"Token"];
CredentialStore *credStore = [CredentialStore sharedStore];
LoginInfo * loginInfo = credStore.loginInfo;
loginInfo.authToken = authToken;
NSLog(#"this is the token here %#",authToken);
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
I appreciate the help.
this is where I instantiate login info
- (IBAction)login:(id)sender
{
[_usernameTextField resignFirstResponder];
[SVProgressHUD show];
id LoginParams =#{
#"NickName" : self.usernameTextField.text,
#"password" :_StoreIdentifierForVendor,
#"DeviceType" :_DeviceModel
};
[[AuthAPIManager sharedManager]POST:#"/url" parameters:LoginParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"response: %#", responseObject);
LoginInfo *loginInfo = [[LoginInfo alloc] initWithDictionary:responseObject];
CredentialStore *credStore = [CredentialStore sharedStore];
credStore.loginInfo = loginInfo;
[self getToken];
}failure....

Resources