2
This commit is contained in:
@@ -14,6 +14,9 @@
|
||||
@property (nonatomic, assign) NSInteger decodedPrefixBytes; // 已解码并写入 sseTextBuffer 的字节数(SSE)
|
||||
@property (nonatomic, assign) NSInteger deliveredCharCount; // 已回传的字符数(非 SSE,用于做增量)
|
||||
@property (nonatomic, assign) BOOL hasEmitted; // 是否已经输出过正文(用于“首段删 1 个 \t”)
|
||||
@property (nonatomic, strong) NSMutableArray<NSString *> *pendingQueue; // 待回调的分片(节流输出)
|
||||
@property (nonatomic, strong) NSTimer *flushTimer; // 定时从队列取出一条回调
|
||||
@property (nonatomic, strong, nullable) NSError *finishError; // 结束时的错误(需要等队列清空再回调)
|
||||
@end
|
||||
|
||||
// 计算数据中以 UTF-8 编码可完整解码的“前缀字节长度”,避免切断多字节字符
|
||||
@@ -53,6 +56,9 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
_textEncoding = NSUTF8StringEncoding;
|
||||
_buffer = [NSMutableData data];
|
||||
_sseTextBuffer = [NSMutableString string];
|
||||
_pendingQueue = [NSMutableArray array];
|
||||
_flushInterval = 0.10;
|
||||
_splitLargeDeltasOnWhitespace = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -84,6 +90,9 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
self.decodedPrefixBytes = 0;
|
||||
self.deliveredCharCount = 0;
|
||||
self.hasEmitted = NO;
|
||||
[self.pendingQueue removeAllObjects];
|
||||
[self.flushTimer invalidate]; self.flushTimer = nil;
|
||||
self.finishError = nil;
|
||||
|
||||
self.task = [self.session dataTaskWithRequest:req];
|
||||
[self.task resume];
|
||||
@@ -99,6 +108,9 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
self.decodedPrefixBytes = 0;
|
||||
self.deliveredCharCount = 0;
|
||||
self.hasEmitted = NO;
|
||||
[self.pendingQueue removeAllObjects];
|
||||
[self.flushTimer invalidate]; self.flushTimer = nil;
|
||||
self.finishError = nil;
|
||||
}
|
||||
|
||||
#pragma mark - NSURLSessionDataDelegate
|
||||
@@ -172,9 +184,7 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
if (payload.length > 0 && [payload hasSuffix:@"\n"]) {
|
||||
[payload deleteCharactersInRange:NSMakeRange(payload.length - 1, 1)];
|
||||
}
|
||||
if (payload.length > 0) {
|
||||
[self emitChunk:payload];
|
||||
}
|
||||
if (payload.length > 0) { [self enqueueChunk:payload]; }
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -186,7 +196,21 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
if (self.deliveredCharCount < (NSInteger)prefix.length) {
|
||||
NSString *delta = [prefix substringFromIndex:self.deliveredCharCount];
|
||||
self.deliveredCharCount = prefix.length;
|
||||
[self emitChunk:delta];
|
||||
if (self.splitLargeDeltasOnWhitespace && delta.length > 16) {
|
||||
// 按空格切词逐条回调(保留空格,使观感更自然)
|
||||
NSArray<NSString *> *parts = [delta componentsSeparatedByString:@" "];
|
||||
for (NSUInteger i = 0; i < parts.count; i++) {
|
||||
NSString *w = parts[i];
|
||||
if (w.length == 0) { [self enqueueChunk:@" "]; continue; }
|
||||
if (i + 1 < parts.count) {
|
||||
[self enqueueChunk:[w stringByAppendingString:@" "]];
|
||||
} else {
|
||||
[self enqueueChunk:w];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[self enqueueChunk:delta];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +236,14 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
}
|
||||
}
|
||||
|
||||
if (self.onFinish) dispatch_async(dispatch_get_main_queue(), ^{ self.onFinish(error); });
|
||||
[self cancel];
|
||||
// 若队列还有待输出内容,等队列清空再回调 finish
|
||||
if (self.pendingQueue.count > 0) {
|
||||
self.finishError = error;
|
||||
[self startFlushTimerIfNeeded];
|
||||
} else {
|
||||
if (self.onFinish) dispatch_async(dispatch_get_main_queue(), ^{ self.onFinish(error); });
|
||||
[self cancel];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
@@ -243,5 +273,34 @@ static NSUInteger kb_validUTF8PrefixLen(NSData *data) {
|
||||
self.hasEmitted = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
#pragma mark - Queue/Flush
|
||||
|
||||
- (void)enqueueChunk:(NSString *)s {
|
||||
if (s.length == 0) return;
|
||||
[self.pendingQueue addObject:s];
|
||||
[self startFlushTimerIfNeeded];
|
||||
}
|
||||
|
||||
- (void)startFlushTimerIfNeeded {
|
||||
if (self.flushTimer) return;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
self.flushTimer = [NSTimer scheduledTimerWithTimeInterval:MAX(0.01, self.flushInterval)
|
||||
repeats:YES
|
||||
block:^(NSTimer * _Nonnull t) {
|
||||
__strong typeof(weakSelf) self = weakSelf; if (!self) { [t invalidate]; return; }
|
||||
if (self.pendingQueue.count == 0) {
|
||||
[t invalidate]; self.flushTimer = nil;
|
||||
if (self.finishError || self.finishError == nil) {
|
||||
NSError *err = self.finishError; self.finishError = nil;
|
||||
if (self.onFinish) dispatch_async(dispatch_get_main_queue(), ^{ self.onFinish(err); });
|
||||
[self cancel];
|
||||
}
|
||||
return;
|
||||
}
|
||||
NSString *first = self.pendingQueue.firstObject;
|
||||
[self.pendingQueue removeObjectAtIndex:0];
|
||||
[self emitChunk:first];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Reference in New Issue
Block a user