Files
keyboard/keyBoard/Class/AiTalk/VM/AiVM.m
2026-01-29 16:03:21 +08:00

812 lines
28 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AiVM.m
// keyBoard
//
// Created by Mac on 2026/1/22.
//
#import "AiVM.h"
#import "KBAPI.h"
#import "KBNetworkManager.h"
#import "KBCommentModel.h"
#import "KBLikedCompanionModel.h"
#import "KBChattedCompanionModel.h"
#import "KBChatSessionResetModel.h"
#import "KBAICompanionDetailModel.h"
#import <MJExtension/MJExtension.h>
@implementation KBAiSyncData
- (void)setAudioBase64:(NSString *)audioBase64 {
if (![audioBase64 isKindOfClass:[NSString class]]) {
_audioBase64 = nil;
self.audioData = nil;
return;
}
_audioBase64 = [audioBase64 copy];
if (_audioBase64.length == 0) {
self.audioData = nil;
return;
}
NSString *cleanBase64 = _audioBase64;
NSRange commaRange = [cleanBase64 rangeOfString:@","];
if ([cleanBase64 hasPrefix:@"data:"] && commaRange.location != NSNotFound) {
cleanBase64 = [cleanBase64 substringFromIndex:commaRange.location + 1];
}
self.audioData = [[NSData alloc]
initWithBase64EncodedString:cleanBase64
options:NSDataBase64DecodingIgnoreUnknownCharacters];
}
@end
@implementation KBAiSyncResponse
@end
@implementation KBAiMessageData
@end
@implementation KBAiMessageResponse
@end
@implementation KBAiSpeechTranscribeData
@end
@implementation KBAiSpeechTranscribeResponse
@end
@implementation AiVM
- (void)syncChatWithTranscript:(NSString *)transcript
completion:(AiVMSyncCompletion)completion {
if (transcript.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"transcript is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSDictionary *params = @{ @"transcript" : transcript ?: @"" };
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSLog(@"[AiVM] /chat/sync request: %@", params);
[[KBNetworkManager shared]
POST:API_AI_CHAT_SYNC
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
CFAbsoluteTime elapsed =
(CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
if (error) {
NSLog(@"[AiVM] /chat/sync failed: %@",
error.localizedDescription ?: @"");
NSLog(@"[AiVM] /chat/sync duration: %.0f ms", elapsed);
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /chat/sync response received");
NSLog(@"[AiVM] /chat/sync duration: %.0f ms", elapsed);
KBAiSyncResponse *model =
[KBAiSyncResponse mj_objectWithKeyValues:json];
if (completion) {
completion(model, nil);
}
}];
}
- (void)requestChatMessageWithContent:(NSString *)content
companionId:(NSInteger)companionId
completion:(AiVMMessageCompletion)completion {
if (content.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"content is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSString *encodedContent =
[content stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *path = [NSString
stringWithFormat:@"%@?content=%@&companionId=%ld", API_AI_CHAT_MESSAGE,
encodedContent ?: @"", (long)companionId];
NSDictionary *params = @{
@"content" : content ?: @"",
@"companionId" : @(companionId)
};
[[KBNetworkManager shared]
POST:path
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
KBAiMessageResponse *model =
[KBAiMessageResponse mj_objectWithKeyValues:json];
if (error) {
if (completion) {
completion(model, error);
}
return;
}
id dataObj = json[@"data"];
if (!model.data && [dataObj isKindOfClass:[NSString class]]) {
KBAiMessageData *data = [[KBAiMessageData alloc] init];
data.content = (NSString *)dataObj;
model.data = data;
}
if (completion) {
completion(model, nil);
}
}];
}
- (void)requestAudioWithAudioId:(NSString *)audioId
completion:(AiVMAudioURLCompletion)completion {
if (audioId.length == 0) {
NSError *error = [NSError
errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"audioId is empty"}];
if (completion) {
completion(nil, error);
}
return;
}
NSString *path = [NSString stringWithFormat:@"/chat/audio/%@", audioId];
[[KBNetworkManager shared]
GET:path
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
// 解析返回的 URL
NSString *audioURL = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
// 返回格式:{"code": 0, "data": {"audioUrl": "http://...", "url": "http://..."}}
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
NSDictionary *dataDict = (NSDictionary *)dataObj;
// 优先使用 audioUrl兼容 url
id audioUrlObj = dataDict[@"audioUrl"] ?: dataDict[@"url"];
// 检查是否为 NSNull
if (audioUrlObj && ![audioUrlObj isKindOfClass:[NSNull class]] && [audioUrlObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)audioUrlObj;
}
} else if ([dataObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)dataObj;
}
// 或者直接返回 URL 字符串
if (!audioURL) {
id audioUrlObj = json[@"audioUrl"] ?: json[@"url"];
if (audioUrlObj && ![audioUrlObj isKindOfClass:[NSNull class]] && [audioUrlObj isKindOfClass:[NSString class]]) {
audioURL = (NSString *)audioUrlObj;
}
}
}
// 如果 audioURL 为空或 nil返回 nil不是错误表示音频还未生成
if (!audioURL || audioURL.length == 0) {
if (completion) {
completion(nil, nil); // 返回 nil 表示音频未就绪,需要重试
}
return;
}
if (completion) {
completion(audioURL, nil);
}
}];
}
- (void)uploadAudioFileAtURL:(NSURL *)fileURL
completion:(AiVMUploadAudioCompletion)completion {
if (!fileURL || !fileURL.isFileURL) {
NSError *error = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"invalid fileURL"}];
if (completion) {
completion(nil, error);
}
return;
}
[[KBNetworkManager shared] uploadFile:API_AI_AUDIO_UPLOAD
fileURL:fileURL
name:@"file"
mimeType:@"audio/m4a"
parameters:nil
headers:nil
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
NSString *fileURLString = nil;
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSString class]]) {
fileURLString = (NSString *)dataObj;
} else if ([dataObj isKindOfClass:[NSDictionary class]]) {
id urlObj = dataObj[@"url"] ?: dataObj[@"audioUrl"];
if ([urlObj isKindOfClass:[NSString class]]) {
fileURLString = (NSString *)urlObj;
}
}
if (completion) {
completion(fileURLString, nil);
}
}];
}
- (void)transcribeAudioFileAtURL:(NSURL *)fileURL
completion:(AiVMSpeechTranscribeCompletion)completion {
if (!fileURL || !fileURL.isFileURL) {
NSError *error = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey : @"invalid fileURL"}];
if (completion) {
completion(nil, error);
}
return;
}
[[KBNetworkManager shared] uploadFile:API_AI_SPEECH_TRANSCRIBE
fileURL:fileURL
name:@"file"
mimeType:@"audio/m4a"
parameters:nil
headers:nil
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
if (completion) {
completion(nil, error);
}
return;
}
KBAiSpeechTranscribeResponse *model =
[KBAiSpeechTranscribeResponse mj_objectWithKeyValues:json];
if (completion) {
completion(model, nil);
}
}];
}
#pragma mark - 人设相关接口
- (void)fetchPersonasWithPageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBPersonaPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"pageNum": @(pageNum),
@"pageSize": @(pageSize)
};
NSLog(@"[AiVM] /ai-companion/page request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/page"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/page failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/page response: %@", json);
// 解析响应
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
// 转换为模型
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBPersonaPageModel *pageModel = [KBPersonaPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
#pragma mark - 聊天记录相关接口
- (void)fetchChatHistoryWithCompanionId:(NSInteger)companionId
pageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBChatHistoryPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId),
@"pageNum": @(pageNum),
@"pageSize": @(pageSize)
};
NSLog(@"[AiVM] /chat/history request: %@", params);
[[KBNetworkManager shared]
POST:@"/chat/history"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /chat/history failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /chat/history response: %@", json);
// 解析响应
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
// 转换为模型
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBChatHistoryPageModel *pageModel = [KBChatHistoryPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
#pragma mark - 评论相关接口
- (void)addCommentWithCompanionId:(NSInteger)companionId
content:(NSString *)content
parentId:(nullable NSNumber *)parentId
rootId:(nullable NSNumber *)rootId
completion:(void (^)(KBCommentItem * _Nullable, NSInteger, NSError * _Nullable))completion {
if (content.length == 0) {
NSError *error = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"评论内容不能为空"}];
if (completion) {
completion(nil, -1, error);
}
return;
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[@"companionId"] = @(companionId);
params[@"content"] = content;
if (rootId) {
params[@"rootId"] = rootId;
}
if (parentId) {
params[@"parentId"] = parentId;
}
NSLog(@"[AiVM] /ai-companion/comment/add request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/add"
jsonBody:[params copy]
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/add failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, -1, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/add response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, code, bizError);
}
return;
}
KBCommentItem *newItem = nil;
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
newItem = [KBCommentItem mj_objectWithKeyValues:(NSDictionary *)dataObj];
} else if ([dataObj isKindOfClass:[NSNumber class]]) {
KBCommentItem *tmp = [[KBCommentItem alloc] init];
tmp.commentId = [(NSNumber *)dataObj integerValue];
tmp.companionId = companionId;
tmp.content = content;
tmp.parentId = parentId;
tmp.rootId = rootId;
newItem = tmp;
}
if (completion) {
completion(newItem, code, nil);
}
}];
}
- (void)fetchCommentsWithCompanionId:(NSInteger)companionId
pageNum:(NSInteger)pageNum
pageSize:(NSInteger)pageSize
completion:(void (^)(KBCommentPageModel * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId),
@"pageNum": @(pageNum > 0 ? pageNum : 1),
@"pageSize": @(pageSize > 0 ? pageSize : 20)
};
NSLog(@"[AiVM] /ai-companion/comment/page request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/page"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/page failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/page response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBCommentPageModel *pageModel = [KBCommentPageModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(pageModel, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
- (void)likeCommentWithCommentId:(NSInteger)commentId
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"commentId": @(commentId)
};
NSLog(@"[AiVM] /ai-companion/comment/like request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/comment/like"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/comment/like failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/comment/like response: %@", json);
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
if (completion) {
completion(likeResponse, nil);
}
}];
}
- (void)likeCompanionWithCompanionId:(NSInteger)companionId
completion:(void (^)(KBCommentLikeResponse * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId)
};
NSLog(@"[AiVM] /ai-companion/like request: %@", params);
[[KBNetworkManager shared]
POST:@"/ai-companion/like"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/like failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/like response: %@", json);
KBCommentLikeResponse *likeResponse = [KBCommentLikeResponse mj_objectWithKeyValues:json];
if (completion) {
completion(likeResponse, nil);
}
}];
}
#pragma mark - 点赞列表接口
- (void)fetchLikedCompanionsWithCompletion:(void (^)(NSArray<KBLikedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
NSLog(@"[AiVM] /ai-companion/liked request");
[[KBNetworkManager shared]
GET:@"/ai-companion/liked"
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/liked failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/liked response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
NSArray *dataArray = json[@"data"];
if (![dataArray isKindOfClass:[NSArray class]]) {
dataArray = @[];
}
NSArray<KBLikedCompanionModel *> *list = [KBLikedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
if (completion) {
completion(list, nil);
}
}];
}
- (void)fetchChattedCompanionsWithCompletion:(void (^)(NSArray<KBChattedCompanionModel *> * _Nullable, NSError * _Nullable))completion {
NSLog(@"[AiVM] /ai-companion/chatted request");
[[KBNetworkManager shared]
GET:@"/ai-companion/chatted"
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /ai-companion/chatted failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /ai-companion/chatted response: %@", json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
NSArray *dataArray = json[@"data"];
if (![dataArray isKindOfClass:[NSArray class]]) {
dataArray = @[];
}
NSArray<KBChattedCompanionModel *> *list = [KBChattedCompanionModel mj_objectArrayWithKeyValuesArray:dataArray];
if (completion) {
completion(list, nil);
}
}];
}
#pragma mark - 会话管理接口
- (void)resetChatSessionWithCompanionId:(NSInteger)companionId
completion:(void (^)(KBChatSessionResetResponse * _Nullable, NSError * _Nullable))completion {
NSDictionary *params = @{
@"companionId": @(companionId)
};
NSLog(@"[AiVM] /chat/session/reset request: %@", params);
[[KBNetworkManager shared]
POST:@"/chat/session/reset"
jsonBody:params
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] /chat/session/reset failed: %@", error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] /chat/session/reset response: %@", json);
KBChatSessionResetResponse *resetResponse = [KBChatSessionResetResponse mj_objectWithKeyValues:json];
if (completion) {
completion(resetResponse, nil);
}
}];
}
#pragma mark - AI 角色详情接口
- (void)fetchCompanionDetailWithCompanionId:(NSInteger)companionId
completion:(void (^)(KBAICompanionDetailModel * _Nullable, NSError * _Nullable))completion {
NSString *path = [NSString stringWithFormat:@"/ai-companion/%ld", (long)companionId];
NSLog(@"[AiVM] %@ request", path);
[[KBNetworkManager shared]
GET:path
parameters:nil
headers:nil
autoShowBusinessError:NO
completion:^(NSDictionary *_Nullable json,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
NSLog(@"[AiVM] %@ failed: %@", path, error.localizedDescription ?: @"");
if (completion) {
completion(nil, error);
}
return;
}
NSLog(@"[AiVM] %@ response: %@", path, json);
NSInteger code = [json[@"code"] integerValue];
if (code != 0) {
NSString *message = json[@"message"] ?: @"请求失败";
NSError *bizError = [NSError errorWithDomain:@"AiVM"
code:code
userInfo:@{NSLocalizedDescriptionKey: message}];
if (completion) {
completion(nil, bizError);
}
return;
}
id dataObj = json[@"data"];
if ([dataObj isKindOfClass:[NSDictionary class]]) {
KBAICompanionDetailModel *detail = [KBAICompanionDetailModel mj_objectWithKeyValues:dataObj];
if (completion) {
completion(detail, nil);
}
} else {
NSError *parseError = [NSError errorWithDomain:@"AiVM"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"数据格式错误"}];
if (completion) {
completion(nil, parseError);
}
}
}];
}
@end