LiveChat for Mac & iOS behind the scenes
The purpose of this article is to present several technical design concepts implemented and proven to work well for over one year, since the first release of the Mac version of LiveChat operator application.
Back in 2010 when I started implementing the Mac version, the main task was to reach the feature level of the Windows version (developed for several years) as fast as possible, and also to prepare ground for an upcoming iOS version (available now in App Store). At that time I neither had access to the Windows version’s source code, nor was going to reuse any of it. Therefore I have settled the following constraints that yielded the following results (described in detail in following sections):

1. Source code must be divided into a framework part and GUI part
Where the framework part should contain most of the application logic and use API available on both Mac & iOS platforms, while the GUI part should be reduced to a minimum. Altogether it should match Model-View-Controller architecture.
Protocol.framework is now 60% of both platforms total source code, manages server communication and models such as
LCVisitororLCChat.Protocol.framework does not use any platform specific API and relies only on Foundation.framework.
Mac & iOS use separate source code for views & controllers driving GUI, however these contain almost no application logic.
2. Source code should be self-describing and object-oriented
Utilizing (where possible) dynamic facilities of Objective-C.
All model classes visible to controllers are Objective-C classes, controllers have no direct access to protocol.
Application logic talks with the server through an OO proxy object using dynamic message dispatch to translate messages back and forth into text protocol commands. This makes the protocol layer really small in comparison to the whole application logic.
Protocol.framework
Protocol.framework contains most of the application logic, manages creating and
releasing model objects of LCVisitor, LCOperator & LCChat classes, and
supplies GUI controllers with them. It contains LCServer private class which is
responsible for “message to protocol command mapping”. Applications’
controllers are delegates of LCAccount class.
Mac version may dynamically follow model object’s state updates via Cocoa bindings (not available for iOS) without a need to manually process delegate methods. Therefore, for example inspector pane uses only bindings to update its contents, without actually knowing that it is part of complicated program logic. Inspector view contains just a layout and names for model properties presented to the user.
Strengths:
Putting whole application logic into Protocol.framework makes platform dependent GUI code reduced to minimum and responsible only for seamless translation of logical objects into GUI views.
Protocol.framework relies only on Foundation.framework that is a OO bridge for CoreFoundation and CFNetwork C libraries.
These libraries were published open-source and are known to build on several other platforms than OSX & iOS. Therefore in theory this code is portable to other platforms as well. Unfortunately source code of CFNetwork is no longer updated. Last available version matches OSX 10.4.11 Tiger.
Moreover CoreFoundation on other platforms is stripped from some functionality (named as CFLite) and latest releases have serious build problem on non-OSX platforms. There are several efforts to bring OSX API to other platforms, i.e.: OpenCFLite & PureFoundation fixing and extending CF & CFNetwork sources, Cocotron redoing whole Cocoa API.
Weaknesses:
Application must present same logic on all platforms. It is hard to code any exceptions.
Reusing Platform.framework to other platforms is possible in theory but problematic in practice. If all LiveChat applications were to use single protocol framework, C++ would be a better choice.
Server OO Proxy
While LCAccount translates messages received into model objects, LCServer
class is responsible for translating textual protocol commands to Objective-C
messages sent and received by LCAccount. LCServer class implementation is
really small in comparison to other classes.
Once LCAccount wants to initiate server communication using LCServer, it
needs first to provide protocol command to selector maps: (1) incoming map
mapping to LCAccount methods, and (2) outgoing map mapping to
LCOutgoingMethods protocol. LCServer is the lowest level class that works
directly with sockets. Without going into details LiveChat protocol resembles
MSN and thanks to its linear (non-structural) construction it is possible to
implement such mapping.
Command Reception
Given that following incoming map is provided:
static LCCommandMapping incomingMap[] = {
// …
{ @"R0004", @selector(didReceiveFromClientWithIdentifier:message:senderNick:conferenceIdentifier:hidden:)},
// …
{ nil }
};Upon each command reception LCServer, parses NSMethodSignature of mapped
method and marshals all incoming command arguments into method arguments doing
NSString to argument type conversion.
It supports all basic types such as BOOL, NSInteger and NSString. It also
supports NSArrays for more advanced callback based commands (beyond the scope
of this article). Altogether, calls when receiving following message:
R0004|12341|Hello!|Joe|2134|0<LF>
[account didReceiveFromClientWithIdentifier:12341
message:@"Hello!"
senderNick:@"Joe"
conferenceIdentifier:2134
hidden:NO];Where called method has the following signature:
- (void)didReceiveFromClientWithIdentifier:(NSInteger)clientIdentifier
message:(NSString *)message
senderNick:(NSString *)senderNick
conferenceIdentifier:(NSInteger)conferenceIdentifier
hidden:(BOOL)hiddenOf course this method is not called directly (like presented above), but in
fact LCServer:
Constructs proper NSInvocation object via
[NSInvocation invocationWithMethodSignature:methodSignature]Does marshaling and type conversion of received string arguments via
[methodSignature getArgumentTypeAtIndex:index]example forNSUIntegerargument:if (!strcmp(type, "Q")) { NSUInteger value = [string integerValue]; [invocation setArgument:&value atIndex:index + 2]; }finally calls
[invocation invoke].
LCServer also handles situations when there are too many or too few arguments
received from the server:
In case there are too few, we may be working with an older server and all missing method arguments get nil, numeric get 0.
When there are too many arguments, then we are working with a server more recent than the application. In such case the application ignores extra arguments and emits a warning in the log.
Command Issue
Sending protocol commands to server is done in an opposite way. This time
LCServer acts as OO proxy to LCAccount that provides methods mapped to
protocol command and handled via Objective-C message forwarding mechanism
implemented by:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
struct objc_method_description mdesc =
protocol_getMethodDescription(outgoingProtocol, selector, YES, YES);
if (mdesc.types == NULL) {
return nil;
}
return [NSMethodSignature signatureWithObjCTypes:mdesc.types];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *commandName =
(NSString *)CFDictionaryGetValue(outgoingCommandMap,
(const void *)invocation.selector);
// … converts arguments to string representation and send command over socket
}Once LCAccount provides LCServer an outgoing map and proxy interface:
static LCCommandMapping outgoingMap[] = {
// …
{ @"S0004", @selector(sendFromIdentifier:toChatWithIdentifier:message:) },
// …
{ nil }
};
@protocol LCOutgoingCommands
// …
- (void)sendFromIdentifier:(NSInteger)senderIdentifier
toChatWithIdentifier:(NSInteger)chatIdentifier
message:(NSString *)message;
// …
@endIt can cast internally LCServer to id<LCOutgoingCommands> and send message
to LCServer:
[server sendFromIdentifier:431
toChatWithIdentifier:5488
message:@"Hello to you too!"];Strengths:
Simplicity: No need to maintain long switch & case statements.
Separation: Only simple LCServer private class has direct access to the protocol.
Argument type casting done automatically at method invocation level.
Easy to debug: If program crashes we can deduce command sent by the server from stack backtrace.
Weaknesses:
- All protocol commands must follow the same rules for formatting and escaping arguments. It is really problematic to handle any exceptions in this model.
Protocol commands themselves must be linear (non-structural). Some recently added LiveChat protocol commands have structural arguments that must be parsed separately via external parsers, like XML parser for add-on action commands.
Conclusion
I believe it would not be possible to easily implement solutions described in this article with other API and languages such as C++ or Java. Unfortunately I observe that Objective-C dynamic facilities are less and less utilized by recent Mac OS X (Cocoa) and iOS (Mobile Cocoa) releases and newcomer developers. For example iOS (Mobile Cocoa), which is kind of a rewrite of OSX Cocoa does not implement bindings, like if they were considered deprecated.
Altogether sparse documentation for Objective-C principles, lack of solid examples (for bindings) and finally company politics that intentionally turn this powerful, dynamic language & its APIs into close environment supporting selected products only, deny original ideas of keeping NeXTStep and Objective-C solutions for modern applications running on all platforms.