Got this lengthy section of code shown below and I've hit a brick wall. Basically the code runs perfectly and does exactly what I want it to do. However, it needs to finish running all the code within this section before printing the "Finished" at the end. However adding semaphores or another dispatch group forces a breakpoint. Might be obvious, but could someone give me a bit of advice on this please?
Note: I cannot use that dispatch at the bottom to call another method. Remember its within a loop.
for (id i in arr) {
searchByName = nil;
if ([i containsString:#"word1"] || [i containsString:#"word2"]) {
NSRange searchFromRange = [i rangeOfString:#"ong>"];
NSRange searchToRange = [i rangeOfString:#"</str"];
NSString *substring = [i substringWithRange:NSMakeRange(searchFromRange.location+searchFromRange.length, searchToRange.location-searchFromRange.location-searchFromRange.length)];
[allergens addObject:substring];
if ([substring isEqualToString:#"Examee"] && veg_lac_ovoSafe == TRUE) {
veg_ovoSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Example"] && veg_lac_ovoSafe == TRUE) { //USE OF HEURISTICS
veg_lacSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Exam"]) {
pescetarianSafe = TRUE;
vegSafe = FALSE;
veg_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_lac_ovoSafe = FALSE;
pollotarianSafe = FALSE;
}
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
substring = [[substring componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[substring componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
else {
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
NSString *searchItem = [[i componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[searchItem componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
if (![searchByName isEqualToString:#" "]) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
dispatch_sync(queue, ^{
//Hi
});
});
}
}
NSLog(#"Finished");
Since you're calling dispatch_group_wait() inside a dispatch_async() block, it appears you want to run the completion block asynchronously on the global dispatch queue. The proper way to do this is to use dispatch_group_notify() instead:
dispatch_group_notify(groupSearch, queue, ^{
NSLog(#"Finished")
});
This will run the block on the specified queue once all the blocks submitted to the group have finished, so no dispatch_async() or dispatch_sync() is needed. Additionally, waiting inside dispatch_async() will block a thread in the GCD pool, which is a really bad idea since there are a finite number of them. This is also something that using dispatch_group_notify() instead will avoid.
EDIT: The paragraph below is no longer relevant to your updated code:
Also, as the other answerer pointed out, you probably want to put the call to dispatch_group_notify() after the loop finishes, assuming that you want one completion block to run at the very end of your work. If what you want actually is a whole bunch of separate notifications, one for each run through the loop, then you should probably create a new group within the loop and use that instead. Using one group for the whole shebang and then setting up notifications within the loop is going to cause each notification to wait not only for the dispatch_group_leave() calls for that particular run through the loop, but for all the dispatch_group_enter() calls that have been made on any run through the loop to be balanced. So you'll get nothing until all your work is done, and then you'll suddenly spam a whole bunch of completion blocks all at once, with an accompanying thread explosion since they'll all be submitted on the global queue. This is probably not what you want.
Your use of dispatch_group_wait is in the wrong place. It needs to after the for loop, not inside it. And you can create queue before the loop and reuse it as needed.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
for (id i in arr) {
searchByName = nil;
// Lots of other code here
if (![searchByName isEqualToString:#" "]) {
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
}
}
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
NSLog(#"Finished");
Related
I am looking for some help with dropping/skipping FFmpeg frames. The project I am working on streams live video which when the app goes into the background, upon returning to an active state the video stream spends a long time catching up by fast forwarding itself to the current frame. This isn't ideal and what I am aiming to achieve is have the app immediately jump to the most recent frame.
What I need to do is drop the amount of frames that are being fast-forwarded in order to catch up to the most recent frame. Is this possible? Here is my current code which decodes the frames:
- (NSArray *) decodeFrames: (CGFloat) minDuration
{
NSMutableArray *result = [NSMutableArray array];
#synchronized (lock) {
if([_reading integerValue] != 1){
_reading = [NSNumber numberWithInt:1];
#synchronized (_seekPosition) {
if([_seekPosition integerValue] != -1 && _seekPosition){
[self seekDecoder:[_seekPosition longLongValue]];
_seekPosition = [NSNumber numberWithInt:-1];
}
}
if (_videoStream == -1 &&
_audioStream == -1)
return nil;
AVPacket packet;
CGFloat decodedDuration = 0;
CGFloat totalDuration = [TimeHelper calculateTimeDifference];
do {
BOOL finished = NO;
int count = 0;
while (!finished) {
if (av_read_frame(_formatCtx, &packet) < 0) {
_isEOF = YES;
[self endOfFileReached];
break;
}
[self frameRead];
if (packet.stream_index ==_videoStream) {
int pktSize = packet.size;
while (pktSize > 0) {
int gotframe = 0;
int len = avcodec_decode_video2(_videoCodecCtx,
_videoFrame,
&gotframe,
&packet);
if (len < 0) {
LoggerVideo(0, #"decode video error, skip packet");
break;
}
if (gotframe) {
if (!_disableDeinterlacing &&
_videoFrame->interlaced_frame) {
avpicture_deinterlace((AVPicture*)_videoFrame,
(AVPicture*)_videoFrame,
_videoCodecCtx->pix_fmt,
_videoCodecCtx->width,
_videoCodecCtx->height);
}
KxVideoFrame *frame = [self handleVideoFrame];
if (frame) {
[result addObject:frame];
_position = frame.position;
decodedDuration += frame.duration;
if (decodedDuration > minDuration)
finished = YES;
}
} else {
count++;
}
if (0 == len)
break;
pktSize -= len;
}
}
av_free_packet(&packet);
}
} while (totalDuration > 0);
_reading = [NSNumber numberWithInt:0];
return result;
}
}
return result;
This code works fine, but im trying to implement a resume action after an NSStreamEventErrorOccurred, for example after internet connection lost.
What i have done is to save the number of _totalBytesSend and redo the upload only with the remaining bytes, but im facing that only the remaining bytes will be stored instead of the complete file.
I couldn't able to figure out how to resume file upload or if is there a way to merge the streams when an error ocurred?
- (void)enviar:(NSData *)aDatos pathArchivo:(NSString *)path bytesEnviados:(size_t *)aBytesEnviados
{
BOOL success;
NSURL *url;
long liBytesInicio = aBytesEnviados;
NSData *newData = [aDatos subdataWithRange:NSMakeRange(liBytesInicio, [aDatos length] - liBytesInicio)];
_currentBytesEnviados = aBytesEnviados;
_currentDatos = aDatos;
_currentPathArchivos = path;
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// First get and check the URL.
url = [[NetworkManager sharedInstance] smartURLForString:[Sesion sharedSesion].configuracion.servidorFTP];
success = (url != nil);
if (success) {
// Add the last part of the file name to the end of the URL to form the final
// URL that we're going to put to.
url = CFBridgingRelease(CFURLCreateCopyAppendingPathComponent(NULL, (__bridge CFURLRef) url, (__bridge CFStringRef)path, false));
success = (url != nil);
}
// If the URL is bogus, let the user know. Otherwise kick off the connection.
// Open a stream for the file we're going to send. We do not open this stream;
// NSURLConnection will do it for us.
self.fileStream = [NSInputStream inputStreamWithData:newData];
assert(self.fileStream != nil);
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url));
assert(self.networkStream != nil);
success = [self.networkStream setProperty:[Sesion sharedSesion].configuracion.cuentaFTP forKey:(id)kCFStreamPropertyFTPUserName];
assert(success);
success = [self.networkStream setProperty:[Sesion sharedSesion].configuracion.passwordFTP forKey:(id)kCFStreamPropertyFTPPassword];
assert(success);
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
// Tell the UI we're sending.
}
- (void)detenerEnvio:(NSString *)estado {
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self envioDetenido:estado];
}
- (void)continuaEnvio
{
double delayInSeconds = 12.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"After Delay code");
if ([self.networkStream streamStatus] != NSStreamStatusOpen) {
PutController *ftp = [[PutController alloc] init];
ftp.delegate = self;
_currentBytesEnviados = _totalBytesSend;//_currentDatos.length;
NSLog(#"Current total bytes ** %i", _currentBytesEnviados);
[ftp enviar:_currentDatos pathArchivo:_currentPathArchivos bytesEnviados:_currentBytesEnviados];
self.ftpController = ftp;
}
});
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self iniciarEnvio:#"Abriendo conexion"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self detenerEnvio:#"Error al leer archivo."];
} else if (bytesRead == 0) {
[self detenerEnvio:nil];
break;
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self detenerEnvio:#"Error al escribir."];
} else {
self.bufferOffset += bytesWritten;
_totalBytesSend += bytesWritten;
[self actualizaEnvio:bytesWritten];
}
}
} break;
case NSStreamEventErrorOccurred: {
//**** Here is where im trying to resume the upload *****
[self detenerEnvio:#"Error al abrir el stream."];
[self continuaEnvio];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
Thanks in advance!
I am writing a simple timer program for myself in objective c for my mac. The timer counts down properly, but I get the spinning pinwheel of death. How can I make the pinwheel go away? I think its because I have an infinite loop but there must be someway to bypass it.
I have an IBAction that triggered on a button click (the button is start). And from there, it calls another function that does the work.
Here is my IBAction:
- (IBAction)timerStart:(id)sender {
self.timerDidPause = NO;
[self timerRunning];
}
And here is timerRunning:
- (void)timerRunning {
for (;;) {
usleep(1000000);
if (self.timerDidPause == YES) {
}
else {
if (self.seconds == 0) {
if (self.minutes == 0) {
[self timerDone];
break;
}
else {
self.seconds = 59;
self.minutes = self.minutes - 1;
[self formatTimerLabel:self.hours :self.minutes :self.seconds];
}
}
else {
self.seconds = self.seconds - 1;
[self formatTimerLabel:self.hours :self.minutes :self.seconds];
}
}
}
}
In this function, the function formatTimerLabel is called so here is that:
- (void)formatTimerLabel:(int)hours
:(int)minutes
:(int)seconds {
NSString *minuteString = [[NSString alloc] init];
NSString *secondString = [[NSString alloc] init];
if (minutes < 10) {
minuteString = [NSString stringWithFormat:#"0%d", minutes];
}
else {
minuteString = [NSString stringWithFormat:#"%d", minutes];
}
if (seconds < 10) {
secondString = [NSString stringWithFormat:#"0%d", seconds];
}
else {
secondString = [NSString stringWithFormat:#"%d", seconds];
}
[self.timerLabel setStringValue:[NSString stringWithFormat:#"%d:%#:%#", hours, minuteString, secondString]];
[self.timerLabel display];
}
You're causing the UI thread to hang with your loop. After a couple of seconds of that, the OS switches the cursor to a pinwheel.
You need to look into NSTimer and the Timer Programming Guide to schedule the timer to run outside of the UI thread.
My app crashes on the following line:
sqlite3_prepare_v2(db, [sql UTF8String], -1, &pStmt, 0);
in the method of the FMDB sqlite wrapper:
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args {
if (![self databaseExists]) {
return 0x00;
}
if (inUse) {
[self warnInUse];
return 0x00;
}
[self setInUse:YES];
FMResultSet *rs = nil;
int rc = 0x00;
sqlite3_stmt *pStmt = 0x00;
FMStatement *statement = 0x00;
if (traceExecution && sql) {
NSLog(#"%# executeQuery: %#", self, sql);
}
if (shouldCacheStatements) {
statement = [self cachedStatementForQuery:sql];
pStmt = statement ? [statement statement] : 0x00;
}
int numberOfRetries = 0;
BOOL retry = NO;
if (!pStmt) {
do {
retry = NO;
const char *sqlStatement = [sql UTF8String];
rc = sqlite3_prepare_v2(db, sqlStatement, -1, &pStmt, 0);
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
retry = YES;
usleep(20);
if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) {
NSLog(#"%s:%d Database busy (%#)", __FUNCTION__, __LINE__, [self databasePath]);
NSLog(#"Database busy");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
else if (SQLITE_OK != rc) {
if (logsErrors) {
NSLog(#"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
NSLog(#"DB Query: %#", sql);
#ifndef NS_BLOCK_ASSERTIONS
if (crashOnErrors) {
NSAssert2(false, #"DB Error: %d \"%#\"", [self lastErrorCode], [self lastErrorMessage]);
}
#endif
}
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
}
while (retry);
}
id obj;
int idx = 0;
int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
while (idx < queryCount) {
if (arrayArgs) {
obj = [arrayArgs objectAtIndex:idx];
}
else {
obj = va_arg(args, id);
}
if (traceExecution) {
NSLog(#"obj: %#", obj);
}
idx++;
[self bindObject:obj toColumn:idx inStatement:pStmt];
}
if (idx != queryCount) {
NSLog(#"Error: the bind count is not correct for the # of variables (executeQuery)");
sqlite3_finalize(pStmt);
[self setInUse:NO];
return nil;
}
[statement retain]; // to balance the release below
if (!statement) {
statement = [[FMStatement alloc] init];
[statement setStatement:pStmt];
if (shouldCacheStatements) {
[self setCachedStatement:statement forQuery:sql];
}
}
// the statement gets closed in rs's dealloc or [rs close];
rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
[rs setQuery:sql];
NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
[openResultSets addObject:openResultSet];
statement.useCount = statement.useCount + 1;
[statement release];
[self setInUse:NO];
return rs;
}
The app crashes with EXC_BAD_ACCESS. I have tried to find out why by debugging with NSZombieEnabled and malloc_history, but it does not give me any answers. Also - the debugger tells me that the sql variable has a retain count of a very large number (which is probably because it is a static NSString) - so the EXC_BAD_ACCESS should not be because of the sql object being over-relesed.
Does anyone have any ideas on how to further debug this to find out what the problem is?
Solution: The problem was that my database was accessed by several threads. And even if all threads had synchronized access to the database handle, for sqlite versions prior to 3.3.1 (iOS uses 3.0) you can not safely use the same database handle across threads.
My solution was to create on-demand handles to the database for each thread that tries to access the database, like this:
- (ADatabaseConnection *)databaseConnection {
NSDictionary *dictionary = [[NSThread currentThread] threadDictionary];
NSString *key = #"aDatabaseConnection";
ADatabaseConnection *connection = [dictionary objectForKey:key];
if (connection == nil) {
connection = [[[ADatabaseConnection alloc] initWithDatabase:self] autorelease];
[dictionary setValue:connection forKey:key];
}
return connection;
}
Note that for sqlite versions >= 3.3.1, this is not needed as the same handle can be used across threads.
Another important thing to remember is that even if you use this approach to safely use the same database across threads, it might be wise to synchronize access to the database so that you do not access it simultaneously anyway to avoid database lock errors. I do both, use one handle for each thread and synchronize on the database.
It's not safe to use FMDatabase from multiple threads at the same time- so I've started work on a new class to help you make queries and updates from multiple threads using a pool. Right now it's on a branch, which you can view here:
https://github.com/ccgus/fmdb/tree/threadtests
Read the section titled "Using FMDatabasePool and Thread Safety."
I need to send email in background, so I have to use the library named: SMTP. And the main class I used is: SKPSMTPMessage. The problem is "ccEmail", when I add more than 2 recipients, it can't send email. (that takes too long time to go to delegate methods). It works well with recipient <= 2.
smtpEmail.ccEmail = #"xyz#gmail.com, xyz1#gmail.com, xyz2#gmail.com";
Anyone knows this, please help me. Thanks you so much !
There is my changes in the parseBuffer function:
case kSKPSMTPWaitingFromReply:
{
if ([tmpLine hasPrefix:#"250 "]) {
if (!multipleRcptTo) {
NSMutableString *multipleRcptToString = [NSMutableString string];
[multipleRcptToString appendString:[self formatAddresses:toEmail]];
[multipleRcptToString appendString:[self formatAddresses:ccEmail]];
[multipleRcptToString appendString:[self formatAddresses:bccEmail]];
multipleRcptTo = [[multipleRcptToString componentsSeparatedByString:#"\r\n"] mutableCopy];
[multipleRcptTo removeLastObject];
}
if ([multipleRcptTo count] > 0) {
NSString *rcptTo = [NSString stringWithFormat:#"%#\r\n", [multipleRcptTo objectAtIndex:0]];
[multipleRcptTo removeObjectAtIndex:0];
//DEBUGLOG(#"C: %#", rcptTo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[rcptTo UTF8String], [rcptTo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
if ([multipleRcptTo count] == 0) {
sendState = kSKPSMTPWaitingToReply;
}
}
break;
}
and add this into header:
NSMutableArray *multipleRcptTo;
EDIT : Also change below method as multipleRcptTo is used as NSMutableString which is local declaration :
- (NSString *)formatAddresses:(NSString *)addresses {
NSCharacterSet *splitSet = [NSCharacterSet characterSetWithCharactersInString:#";,"];
NSMutableString *multipleRcpt = [NSMutableString string];
if ((addresses != nil) && (![addresses isEqualToString:#""])) {
if( [addresses rangeOfString:#";"].location != NSNotFound || [addresses rangeOfString:#","].location != NSNotFound ) {
NSArray *addressParts = [addresses componentsSeparatedByCharactersInSet:splitSet];
for( NSString *address in addressParts ) {
[multipleRcpt appendString:[self formatAnAddress:address]];
}
}
else {
[multipleRcpt appendString:[self formatAnAddress:addresses]];
}
}
return(multipleRcpt);
}
SKPSMTPMessage sends to the SMTP address all at once, and must send one by one.