How to Make Long‑TCP Connections as Simple as Short Requests on iOS
This article explains a technique for writing long‑TCP connection business code on mobile devices that is as efficient and concise as short‑link code, using request_id mapping, protobuf messages, and a lightweight SendCore layer with timeout handling.
Recently, under pressure to deliver quickly, the author revisited an old code repository and found a practical method for making long‑TCP link business code as efficient and concise as short‑link code on mobile platforms.
The article does not dive into TCP protocol details, socket libraries, or data transmission formats; instead it focuses on writing high‑performance, clean mobile code for TCP‑based services.
Short‑Link Example
Using AFNetworking in Objective‑C, a typical short‑link request looks like this:
NSString *url = @"http://api.xxx.com/method";
[[self shareAFNManager] GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"responseObject-->%@", responseObject);
UpdateUI();
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"error-->%@", error);
}];The block syntax acts like a function pointer, allowing direct UI updates after the network response, embodying the short‑link principle R:Q = N:N = 1.
Long‑Link Challenges
Long‑link code must handle socket management, thread switching, UI lifecycle, data notifications, and more, often leading to complex or error‑prone implementations.
Solution
By abstracting the request/response relationship and using a unique request_id, long‑link handling can be simplified to the short‑link style.
Refine request & response, enforce R:Q = N:N = 1.
Define protobuf messages that pair each request with a reply:
message ping{
string request_id = 1;
}
message ping_reply{
string request_id = 1;
}Implement a SendCore singleton to manage sending, mapping, and callbacks:
typedef void (^SocketDidReadBlock)(NSError * __nullable error, id __nullable data);
@interface SendCore : NSObject
+ (nullable SendCore *)sharedManager;
- (void)sendEnterChatRoom:(nullable EnterChatRoom *)data completion:(__nullable SocketDidReadBlock)callback;
- (void)CloseSocket;
@endExample method for entering a chat room creates a request_id, stores the callback, and sends the protocol:
- (void)sendEnterChatRoom:(nullable EnterChatRoom *)data completion:(__nullable SocketDidReadBlock)callback {
if (data == nil) { return; }
NSString *blockRequestID = [self createRequestID];
data.requestId = blockRequestID;
if (callback) { [self addRequestIDToMap:blockRequestID block:callback]; }
[self sendProtocolWithCmd:CmdType_Enter1V1MovieRoom withCmdData:[data data] completion:callback];
}When a response arrives, the handler extracts the request_id, retrieves the stored block, executes it, and cleans up:
- (void)handerProtocol:(CmdType)protocolID packet:(NSData *)packet {
NSError *error = nil;
id resultData = nil;
NSString *requestId = nil;
switch (protocolID) {
case CmdType_Enter1V1MovieRoomReply:
error = nil;
SyncObjInfo *syncObjInfo = [SyncObjInfo parseFromData:packet error:&error];
resultData = syncObjInfo;
requestId = syncObjInfo.requestId;
SocketDidReadBlock didReadBlock = [self getBlockWith:requestId];
break;
}
if (didReadBlock) { didReadBlock(error, resultData); }
if (requestId && requestId.length > 0) { [self removeRequestIDFormMap:requestId]; }
}Timeout handling uses a second map storing request timestamps; a periodic timer checks for overdue requests and invokes their callbacks with a timeout error:
- (void)checkRequestProcessTimeout {
NSError *timeoutError = [NSError errorWithDomain:@"_Socket_WAIT_PROCESS_TIMEOUT_SECOND_" code:408 userInfo:nil];
NSMutableArray *timeoutRequestIDs = [NSMutableArray array];
NSDate *now = [NSDate date];
for (NSString *requestID in [self.requestsTimeMap allKeys]) {
NSDate *fireDate = [self.requestsTimeMap objectForKey:requestID];
NSDate *timeOutDate = [NSDate dateWithTimeInterval:_Socket_WAIT_PROCESS_TIMEOUT_SECOND_ sinceDate:fireDate];
if ([timeOutDate compare:now] == NSOrderedAscending) { [timeoutRequestIDs addObject:requestID]; }
}
for (NSString *requestID in timeoutRequestIDs) {
SocketDidReadBlock block = [self getBlockWith:requestID];
block(timeoutError, nil);
[self removeRequestIDFormMap:requestID];
}
}Generating a unique request_id is straightforward:
- (NSString *)createRequestID {
NSInteger timeInterval = [NSDate date].timeIntervalSince1970 * 1000000;
NSString *randomRequestID = [NSString stringWithFormat:@"%ld%d", timeInterval, arc4random() % 100000];
return randomRequestID;
}With this architecture, long‑link business code becomes as clean and efficient as short‑link code, achieving the goal of indistinguishable implementation while retaining TCP’s advantages.
Conclusion
All functionalities are completed; the business code shows no difference between long‑link and short‑link implementations, delivering the expected simplicity and performance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BaiPing Technology
Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
