Sunday, March 3, 2013

Delegate Design Pattern : Objective C vs Java

One of the most used design patterns in iOS is Delegate Design Pattern. The main usage on this design pattern is to delegate some functionality from called component to caller controller. On image below is shown simple diagram of this pattern where from main controller we call custom component with action to show data from concrete page loaded from the model. The custom component loads data and then displays. In case when no data is loaded for requested page the custom component throws notification thru implemented method in main controller from the custom component protocol. We can look on protocols in Objective C as in interfaces  in Java so the method which is defined in the protocol should be implemented in the the controller that expect some event delegated from the custom component, and also set instance on the caller controller to the custom component where the custom component will use method declaration to send some data to the caller controller or will try to execute the method.

       Delegate Design Pattern

To be much easier to understand, we’ll make comparison on implementation on delegate design pattern in iOS and in Java and we’ll see how can we create custom components in both platforms and use that components to return data to controllers where they are called.

Objective C version

ViewController.h

#import
#import "ServiceConsumer.h"

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
ServiceConsumer *consumer = [[ServiceConsumer alloc] init];
consumer.delegate = self;
[consumer returnNumberToDelegate];
}

#pragma mark -
#pragma mark - ServiceConsumerDelegate methods

- (void)executeWithResult:(NSInteger)testValue {
NSLog(@"Returned value from delegate is %d", testValue);
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

@end

In header file we import delegate module but also we conform with the delegate of the module. In implementation we make instance from the service consumer but also we set the delegate to self which mean we need to implement the methods defined in the protocol.

 ServiceConsumer.h

#import

@protocol ServiceConsumerDelegate

- (void)executeWithResult:(NSInteger)testValue;

@end

@interface ServiceConsumer : NSObject

@property (nonatomic, strong) iddelegate;

- (void)returnNumberToDelegate;

@end

ServiceConsumer.m

#import "ServiceConsumer.h"

@implementation ServiceConsumer

@synthesize delegate;

- (void)returnNumberToDelegate {
[self.delegate executeWithResult:32];

}


@end

In ServiceConsumer header we can see the protocol definition but also the most important line, property definition as id – generic object that have to confirm to the protocol defined in here. In implementation we can see the method that call method executeWithResult which is defined in the protocol but it call it from the delegate property which mean that the class that will have implemented the method defined in the protocol and also confirms with the delegate, if that class call the method returnNumberToDelegate from the ServiceConsumer that class will receive the response in implemented method executeWithResult. We can see that the functionality is delegated thru line [self.delegate executeWithResult:32];

Java version

With creation on instance of the ServiceConsumer we initialize engine with the instance that implements IServiceConsumer interface. That is done in constructor here. In this class we also have one method returnConsumerData that same as in Objective C version, if is called from the class that implements that interface and have implementation for method printMessage defined in the interface, this method will call that method thru the instance set to interface in the constructor.

ServiceConsumer.java

public class ServiceConsumer {

private IServiceConsumer consumer = null;

private static final String MESSAGE_0 = "MACHINE IS RUNNING";
public ServiceConsumer(IServiceConsumer consumer) {
super();
this.consumer = consumer;
}
public void returnConsumerData() {
this.consumer.printMessage(MESSAGE_0);
}
}



In Java version instead of protocol in Objective C we need to have interface with defined method.

IServiceConsumer.java

public interface IServiceConsumer {
public String returnConsumerData(String data);
public void printMessage(String messgeData);
}

The class that calls the engine makes instance from the ServiceConsumer class with setting the Machine.this to the constructor of the ServiceConsumer class because it already implements the interface. Also because it implements the interface it should also implement the methods defined in the interface in this case returnConsumerData. Method loadConsumerData is used to call returnConsumerData method implemented in the ServiceConsumer, which after calling return data thru method printMessage.

Machine.java


public class Machine implements IServiceConsumer {
private ServiceConsumer consumer = null;
public Machine() {
this.consumer = new ServiceConsumer(Machine.this);
}
public void loadConsumerData() {
this.consumer.returnConsumerData();
}


@Override
public String returnConsumerData(String data) {
// TODO Auto-generated method stub
return data;
}

@Override
public void printMessage(String messgeData) {
// TODO Auto-generated method stub
System.out.println(messgeData);
}

}


Thursday, August 30, 2012

Wheel of Millionaires v1.0

Because my game Wheel of Fortune was suspended on the Android Market now Google Play because of breaking some rights for the name, now is much better with new UI, faster and more realistic wheel and more words... the new name is Wheel of Millionaires.


Enjoy.


Wednesday, August 22, 2012

Basic Extendable Node.js Push Service

Assuming that you have already installed Node.js and NPM manager, in this article i'll show you basic implementation on the push service based on web sockets. First you should install great web socket library.

https://github.com/Worlize/WebSocket-Node/
npm install websocket

There is also httpclient lib in the package that should be in the same lib folder as userservice.js, messageservice.js and wsmanager.js. This client is used for easy communication with RESTful web services. You can download from the official git repo.

https://github.com/billywhizz/node-httpclient

In the following sources you can see how is easy to implement extendable service on top on Node with using web sockets.

This basic implementation can be used for various realtime services, realtime push notifications, chat and im, multiplayer game servers and so on. In the next post i'll show basic implementation on IM client and server side based on web sockets, implemented with using this concept.

Sources are free for every purpose.

Enjoy.

wsserver.js

var WebSocketServer = require('websocket').server;
var http = require('http');
var sys = require("sys");

var wsManagerLib = require("./lib/wsmanager");
var wsManager = new wsManagerLib.WSManager();

var server = http.createServer(function(request, response) {
    console.log((new Date()) + ' Received request for ' + request.url);
    response.writeHead(404);
    response.end();
});
server.listen(8080, function() {
    console.log((new Date()) + ' Server is listening on port 8080');
});

wsServer = new WebSocketServer({
    httpServer: server,
   
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
  // put logic here to detect whether the specified origin is allowed.
  return true;
}

wsServer.on('request', function(request) {
    if (!originIsAllowed(request.origin)) {     
      request.reject();
      console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
      return;
    }

    var connection = request.accept('', request.origin);

    connection.on('message', function(message) {
        if (message.type === 'utf8') {
                    //console.log('Received Message ' + message);             
            wsManager.protocolValidator(message.utf8Data, connection, request);
        } else if (message.type === 'binary') {
            console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');                                
            connection.sendBytes(message.binaryData);
        }
    });
    connection.on('close', function(reasonCode, description) {
        console.log('closing connection. Reason: '+description);

    });
});

wsmanager.js

var userService = require("./userservice");
var userServiceManager = new userService.UserService();
//userServiceManager.setModel();

var messageService = require("./messagesservice");
var messageServiceManager = new messageService.MessagesService();

function WSManager() {

                        /**
                         * Protocol validator, validate JSON instructions and takes appropriate
                         * actions
                         *
                         * @param protocolMessage
                         * @param connection
                         * @param req
                         * @returns {Number}
                         */                 
                        this.protocolValidator = function(protocolMessage, connection, req) {
                                                try {
                                                                        var protocolObj = JSON.parse(protocolMessage);
                                                } catch (e) {
                                                                        return 0;
                                                }

                                                if(!protocolObj.data || !protocolObj.data.params)
                                                {
                                                                        return 0;
                                                }
                                               
                                                var protocolObjectData = protocolObj.data;
                                                var protocolObjectDataParams = protocolObjectData.params;
                                               
                                                if (protocolObj.caller !== "user" && protocolObj.caller !== "system" && protocolObjectData.userHash !== false) {
                                                                        req.reject();
                                                                        return 0;
                                                }
                                               
                                                console.log("DEBUG:"+protocolObjectData.method);

                                                switch(protocolObj.caller) {
                                                                        case 'system' :                                                                                       
                                                                                                break;
                                                                        case 'user' :
                                                                                                switch(protocolObjectData.method) {                                                                                                        
                                                                                                                        case 'login' :
                                                                                                                                                userServiceManager.createNewConnectionDataObjectIfNOTExist(1, protocolObjectData, connection, req);
                                                                                                                                                break;
                                                                                                                        case 'testhi' :
                                                                                                                                                messageServiceManager.anonymousHi(connection);                                                                                                                       
                                                                                                                                                break;
                                                                                                                        default: break;
                                                                                                }
                                                                                                break;
                                                                        default: break;
                                                }
                        };
                                                                                                                                                                       
}

exports.WSManager = WSManager;


 messageservice.js

function MessagesService() {
                       
                        this.messagesData = {
                                                                        systemMessage : {
                                                                                                caller : "system",
                                                                                                data : {
                                                                                                                        method : "",
                                                                                                                        params : {}
                                                                                                }
                                                                        },
                        };
                       
                        this.systemMsg = {
                                                caller : "system",
                                                data : {
                                                                        method : "",
                                                                        params : {}
                                                }
                        };
                       
                        this.pushMessage = function(userObject, messageType) {                                    
                                                userObject.connection.sendUTF(JSON.stringify(messageType));
                        };
                       
                        /**
                         * Push message notification to client
                         *
                         * @param connection
                         */
                        this.pushMessageNotif = function(connection, count) {
                                                count = typeof count !== 'undefined' ? count : 1 ;
                                                this.systemMsg.data.method = 'pushmessage';
                                                this.systemMsg.data.params = { messageIterator : count };
                                                // console.log(JSON.stringify(this.systemMsg));
                                                connection.sendUTF(JSON.stringify(this.systemMsg));
                        };
                                               
                        /**
                         * Push number of all active users
                         *
                         * @param connection
                         * @param numberOfActiveUsers
                         */
                        this.pushUsersNumberToClient = function(connection, numberOfActiveUsers) {
                                                this.systemMsg.data.method = 'pushnumusers';
                                                this.systemMsg.data.params = { numUsersIterator : numberOfActiveUsers };
                                                // console.log(JSON.stringify(this.systemMsg));
                                                connection.sendUTF(JSON.stringify(this.systemMsg));
                        };
                       
                        /**
                         * Push the number of active users to all active users
                         *
                         */
                        this.pushNumberOfUsersToAllActiveUsers = function(globalBase) {
                                                // var numberOfActiveUsers = this.countAllActiveUsers();
                                               
                                                for (key in globalBase) {
                                                                       
                                                                        var userObj = globalBase[key];
                                                                        if (userObj !== undefined && userObj !== null && userObj.active === true) {
                                                                                                this.pushUsersNumberToClient(userObj.connection, this.numberOfActiveLoggedUsers);
                                                                        }
                                                }                      
                                               
                        };
                       
                        this.sayHi = function(userObject) {
                                                this.systemMsg.data.method = 'systemmsg';
                                                this.systemMsg.data.params = { msg : "Hi from NodeJS "+userObject.userName };
                                                 //console.log(JSON.stringify(this.systemMsg));
                                                userObject.connection.sendUTF(JSON.stringify(this.systemMsg));
                        };
                       
                        this.anonymousHi = function(connection) {
                                                this.systemMsg.data.method = 'systemmsg';
                                                this.systemMsg.data.params = { msg : "Hi from NodeJS "};
                                               
                                                connection.sendUTF(JSON.stringify(this.systemMsg));
                        };
                                               
}

exports.MessagesService = MessagesService;
 

userservice.js

var httpcli = require("./httpclient");

var messageService = require("./messagesservice");
var messageServiceManager = new messageService.MessagesService();

function UserService() {
                       
                        this.globalUsersBase = {};
                        this.numberOfActiveLoggedUsers = 0;
                       
                        /**
                         * Test valid user on REST web service
                         *
                         * @param userHash
                         * @returns
                         */
                        this.testValidUserByHASH = function(userHash) {
                                               
                                                var url = "http://localhost/phd/index.php/usermanager/checkexistinguser?h="+userHash;
                                                var client = new httpcli.httpclient();
                                                var getResult;
                                               
                                                client.perform(url, "GET", function(result) {
                                                                        getResult = result;
                                                });
                                               
                                                return getResult;
                        };
                                                                       
                        /**
                         * Create new user connection object into the global users objects store
                         *
                         * @param callerType
                         * @param protocolObjectData
                         * @param connection
                         * @returns { }
                         */
                        this.createNewConnectionDataObjectIfNOTExist = function(callerType, protocolObjectData, connection, request) {
                                                var dataObj = this.globalUsersBase[protocolObjectData.userHash];

                                                connection.userHash = protocolObjectData.userHash;
                                               
                                                if (!dataObj) {

                                                                        dataObj = {
                                                                                                connectionType : callerType,
                                                                                                userHash : protocolObjectData.userHash,
                                                                                                userName : protocolObjectData.userName,
                                                                                                fullName : protocolObjectData.fullName,
                                                                                                userType : protocolObjectData.params.userType,
                                                                                                ip : connection.remoteAddress,
                                                                                                connection : connection,
                                                                                                userHirearchy : protocolObjectData.params.userHirearchy,                                                                               
                                                                                                activeFriends : [],
                                                                                                messages : [],
                                                                                                emails : [],                                                                                               
                                                                                                unSeenMessages : {
                                                                                                                        messagesFromFriends : [],
                                                                                                                        otherMessages : []
                                                                                                },
                                                                                                active : true
                                                                        };
                                                                                                                                                                                               
                                                                        this.globalUsersBase[protocolObjectData.userHash] = dataObj;
                                                                        this.numberOfActiveLoggedUsers += 1;
                                                                       
                                                } else {
                                                                        if (dataObj.userHash == connection.userHash && dataObj.active === true) {
                                                                                               
                                                                                                dataObj.connection = null;
                                                                                                dataObj.connection = connection;
                                                                        }
                                                }
                                                if (dataObj.active === false) {
                                                                        dataObj.active = true;
                                                                       
                                                                        this.numberOfActiveLoggedUsers += 1;
                                                }
                                               
                                                messageServiceManager.pushNumberOfUsersToAllActiveUsers(this.globalUsersBase);
                                                if (!dataObj.connection || dataObj.connection.state === "closed") {
                                                                        dataObj.connection = null;
                                                                        dataObj.connection = connection;
                                                }
                                               
                                                messageServiceManager.sayHi(dataObj);
                                               
                                                return dataObj;
                        };
                                               
                        /**
                         * Set live user object as disconnected
                         *
                         */
                        this.setActiveUserASDisconnected = function(connection) {      
                                                userHash = connection.userHash;
                                                if (!this.globalUsersBase[userHash] || (this.globalUsersBase[userHash].connection && connection !== this.globalUsersBase[userHash].connection) ) {
                                                                        return 0;
                                                }
                                                if (this.globalUsersBase[userHash].active === true) {
                                                                        this.globalUsersBase[userHash].active = false;
                                                                       
                                                                        this.numberOfActiveLoggedUsers -= 1;
                                                                        messageServiceManager.pushNumberOfUsersToAllActiveUsers();
                                                }
                        };
                       
                        /**
                         * Check if element exist in the array
                         *
                         * @param arrayList
                         * @param obj
                         * @returns
                         */
                        this.containObj = function(arrayList, obj) {
                                                elementExist = false;
                                                for (var element in arrayList) {
                                                                        if (arrayList[element] === obj) {
                                                                                                elementExist = true;
                                                                                                break;
                                                                        }
                                                }
                                                return elementExist;
                        };
                                                                       
                        /**
                         * Remove single element from the array
                         *
                         */
                        this.removeByIndex = function(arrayName,arrayIndex){
                                                arrayName.splice(arrayIndex,1);
                        };
                       
                        /**
                         * Counts and return number of active users in global users object
                         *
                         */
                        this.countAllActiveUsers = function() {
                                                var localCounter = 0;
                                               
                                                for (key in this.globalUsersBase) {
                                                                       
                                                                        var userObj = this.globalUsersBase[key];
                                                                        if (userObj !== undefined || userObj !== null) {
                                                                                                localCounter++;
                                                                        }
                                                }
                                               
                                                return localCounter;
                        };
                       
                        /**
                         * Index of value in array
                         *
                         * @param arrayForSearch
                         * @param valueInArray
                         * @returns
                         */
                        this.indexOf = function(arrayForSearch, valueInArray) {
                                                indexCounter = -1;
                                               
                                                for (var i in arrayForSearch) {
                                                                        if (arrayForSearch[i] === valueInArray) {
                                                                                                indexCounter = i;
                                                                                                break;
                                                                        }
                                                }
                                               
                                                return indexCounter;
                        };
                       
                        /**
                         * Return the size of global users list
                         *
                         */
                        this.sizeOfGlobalUsersObject = function() {
                                                return Object.keys(this.globalUsersBase).length;
                        };
}

exports.UserService = UserService;