How to implement a while loop with timeout? - objective-c

I want to implement a while loop that exits either when a particular condition is met, or when a timer times out.
If I just start the timer (to set an object variable on timeout), and then start the while loop (checking the object variable), it doesn't work, because the timer never times out.
I've tried 3 of the solutions suggested in How to wait for a thread to finish in Objective-C to make the loop run in a separate function on another thread, but they fail in various different ways. I have not yet managed to get a test run where the timer times out.
The simple implementation was
//object variable
BOOL m_readTimedOut;
- (void) someFunction
{
m_readTimedOut = NO;
float timeoutS = 0.1;
//Start the timer
SEL readTimeoutSelector = sel_registerName("onReadTimeout:");
[NSTimer scheduledTimerWithTimeInterval:timeoutS
target:self
selector:readTimeoutSelector
userInfo:nil
repeats:NO];
int numBytesToRead = 1;
BOOL exit = NO;
int counter = 0;
while ((counter < numBytesToRead) && (exit == NO))
{
#try
{
#synchronized (readBufferLock)
{
//m_readBuffer is an object variable that can be altered on another thread
if ([m_readBuffer length] > 0)
{
//Do stuff here
counter++;
}
} //end synchronised
}
#catch (NSException *exception)
{
//Log exception
}
if (counter == numBytesToRead || m_readTimedOut == YES)
{
exit = YES;
}
} //end while
}
- (void)onReadTimeout:(NSTimer *)timer
{
NSLog(#"Read timer timed out");
m_readTimedOut = YES;
}

Just a try on the timed exit only - how about
NSDate * start = [[NSDate alloc] init]; // When we started
while ( counter < something )
{
// do stuff ...
// Check time
NSDate * now = [[NSDate alloc] init];
// Been here more than 10s since start
if ( [now timeIntervalSinceDate:start] > 10 )
{
// Timed exit
break;
}
}

Related

Calling a method every second doesn't work

I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop

How to perform all other tasks once the asynchronous dispatch queue finishes execution

I have a PDFDocument where it has some actions on pages like delete,crop,rotate etc.
So when I click on the delete button and click on save(current thread : main thread)
-(void)save
{
// -- process deleted pages first
for( NSString* deletedPageId in self.deletedPageIdList )
{
[self.pdfCoordinator removePageWithId:deletedPageId];
}
// -- wait for all pages to delete before continuing
[self.pdfCoordinator waitUntilAllOperationsAreFinished];
// few lines of code after this should run only when the above code finishes its execution
// code to save the changes in the pdf to managedObject context
}
The code for removePageWithId:
- (void) removePageWithId:(NSString*)pageId
{
NRMPDFOperation *op = [[NRMPDFOperation alloc] initWithNRMPDF:self
pageId:pageId
selector:#selector(removePageOpImpl:)
args:nil];
[self addOperation:op];
[op release];
}
above code creates an operation and adds it to the operation queue for each deletion of the page
code for removePageOpImpl:
- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
NSError* error = [self loadDocument];
if( !error )
{
NSUInteger index = [self pageIndexForId:[op pageId]];
if( index < [self pageCount] )
{
[[self pdfDocument] removePageAtIndex:index];
[[self mutablePageIdList] removeObjectAtIndex:index];
[self updatePageLabelsFromIndex:index];
[self updateChangeCount:NSChangeDone];
self.contentsChanged = YES;
}
else
{
// TODO: error
}
}
return error;
}
In the removePageOpImpl: method the line of code
[[self pdfDocument] removePageAtIndex:index]; internally executing some tasks on main thread(but we are making the main thread to wait until this operation finishes).which causes the deadlock.
I tried to execute the code inside removePageOpImpl: in an asynchronous dispatch queue to avoid the deadlock.below is the code for that
- (NSError *)removePageOpImpl:(NRMPDFOperation *)op
{
NSError* error = [self loadDocument];
if( !error )
{
NSUInteger index = [self pageIndexForId:[op pageId]];
if( index < [self pageCount] )
{
dispatch_async(dispatch_get_main_queue(), ^{
[[self pdfDocument] removePageAtIndex:index];
[[self mutablePageIdList] removeObjectAtIndex:index];
[self updatePageLabelsFromIndex:index];
[self updateChangeCount:NSChangeDone];
self.contentsChanged = YES;
});
}
else
{
// TODO: error
}
}
return error;
}
Now I am out from the deadlock. But another issue is by putting the above code into asynchronous block the code which should run after this tasks is executing before this, because of that my app is not behaving as expected.
Code inside waitUntilAllOperationsAreFinished method
- (void) addOperation:(NRMPDFOperation*)operation
{
[_operationSet addObject:operation];
[[self operationQueue] addOperation:operation];
}
- (void) waitUntilAllOperationsAreFinished
{
[[self operationQueue] waitUntilAllOperationsAreFinished];
}
- (NSOperationQueue *)operationQueue
{
if( !_operationQueue )
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
_operationQueue = queue;
}
return _operationQueue;
}
This is how the original Save method looks like :
- (void)saveDocumentWithDelegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo
{
// TODO: don't just ignore saveOperation
__block BOOL success = YES;
__block NSError *error = nil;
/* write back field changes */
if ([item hasChangesInEditFieldsetFor:#"values"] )
{
//Some code
}
if( self.isPDFEdited )
{
// -- process deleted pages first
for( NSString* deletedPageId in self.deletedPageIdList )
{
[self.itemPDFCoordinator removePageWithId:deletedPageId];
}
// -- wait for all pages to delete before continuing
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
// -- replace the search text any pages we deleted
if( [self.deletedPageIdList count] )
{
[self.item setValue:[self.editPDF string] forKeyPath:#"dict.values._searchText"];
}
NSMutableDictionary* originalRotations = [NSMutableDictionary dictionaryWithCapacity:
[self.itemPDFCoordinator pageCount]];
for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
{
NSInteger rotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
[originalRotations setObject:[NSNumber numberWithInteger:rotation] forKey:pageId];
}
// -- now process actions on remaining pages (crop, rotate, and convert to b&w)
BOOL didCropAnyPages = NO;
NSMutableArray* convertToBwJobs = [NSMutableArray array];
for( NSString* pageId in [self.pageActionDict allKeys] )
{
NSArray* actions = [self.pageActionDict objectForKey:pageId];
for( NSDictionary* action in actions )
{
NSNumber* rotationNumber = [action objectForKey:#"rotation"];
NSValue* cropRectVal = [action objectForKey:#"cropRect"];
NSNumber* convertToBlackAndWhite = [action objectForKey:#"convertToBlackAndWhite"];
if( rotationNumber )
{
[self.itemPDFCoordinator rotateByDegrees:[rotationNumber integerValue]
forPageWithID:pageId];
}
else if( cropRectVal )
{
[self.itemPDFCoordinator setNormalizedBounds:[cropRectVal rectValue]
forBox:kPDFDisplayBoxCropBox
forPageWithID:pageId];
// -- set a flag so we know to recrop the entire document
didCropAnyPages = YES;
}
else if( [convertToBlackAndWhite boolValue] )
{
NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
NRMJob* job = [NRMAppJobFactory convertToBlackAndWhiteJobForItem:self.item
pageIndex:pageIndex];
[convertToBwJobs addObject:job];
}
}
}
// -- reapply crop box to any cropped pages
if( didCropAnyPages )
{
[self.itemPDFCoordinator applyCropBoxToAllPages];
}
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
for( NRMJob* job in convertToBwJobs )
{
if( ![[self.masterDocument docjob] addJob:job forItem:self.item error:&error] )
[NSApp presentError:error];
else
[job waitUntilDone];
}
// -- make sure document attributes are updated
NSDictionary *docDict = [self.itemPDFCoordinator documentAttributes];
NSDictionary *newDict = [(NRMItem *)item updateDocumentAttributes:docDict];
if (![newDict isEqualToDictionary:docDict])
[self.itemPDFCoordinator setDocumentAttributes:newDict];
[self.itemPDFCoordinator waitUntilAllOperationsAreFinished];
// -- check if we need to reprocess any pages
for( NSString* pageId in self.itemPDFCoordinator.pageIdList )
{
NSInteger oldRotation = [[originalRotations objectForKey:pageId] integerValue];
NSInteger newRotation = [[self.itemPDFCoordinator pageForId:pageId] rotation];
if( oldRotation != newRotation )
{
// -- if it's an image page and we already have OCR data for it, we should reanalyze
NSUInteger pageIndex = [self.itemPDFCoordinator pageIndexForId:pageId];
BOOL isPageImage = [self.itemPDFCoordinator isImagePageAtIndex:pageIndex DPI:NULL];
if( isPageImage && [item OCRDataForPageIndex:pageIndex] )
{
NRMJob* job = [NRMAppJobFactory reprocessPageJobForItem:self.item
pageIndex:pageIndex];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reanalyzeJobFinished:)
name:kNRMJobFinishedNotification
object:job];
success = [[self.masterDocument docjob] addJob:job forItem:self.item error:&error];
if( !success )
{
if( error )
[self presentError:error];
}
//goto bail;
}
}
}
//Force Save of PDF to Disk
[self.itemPDFCoordinator setManagedObjectContext:[item managedObjectContext]];
[self.itemPDFCoordinator saveChanges];
}
if( success )
{
[self updateChangeCount:NSChangeCleared];
if( self.isPDFEdited )
{
// !CLEARSPLIT! please do not remove this comment
[self.item setValue:nil forKeyPath:#"dict.values._splitId"];
if( ![self loadPDFForItem:item error:&error] )
goto bail;
}
}
bail:
if( error )
[self presentError:error];
if( delegate )
{
/* signature:
- (void)document:(NSDocument *)document didSave:(BOOL)didSaveSuccessfully contextInfo:(void *)contextInfo;
*/
objc_msgSend( delegate, didSaveSelector, self, (error ? NO : YES), contextInfo );
}
}
can anyone suggest me how can I get out of this issue.
Rather than calling waitUntilAllOperationsAreFinished (which blocks a thread), I'd suggest specifying an additional completion operation, which you can make dependent upon the other operations finishing:
-(void)save {
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
// whatever you want to do when it's done
}];
for (NSString* deletedPageId in self.deletedPageIdList) {
NSOperation *operation = [self.pdfCoordinator removePageWithId:deletedPageId];
[completionOperation addDependency:operation];
}
// add this completionOperation to a queue, but it won't actually start until the other operations finish
[queue addOperation:completionOperation];
}
Clearly this assumes that you change removePageWithId to return the operation it added to the queue:
- (NSOperation *)removePageWithId:(NSString*)pageId {
NRMPDFOperation *operation = [[NRMPDFOperation alloc] initWithNRMPDF:self
pageId:pageId
selector:#selector(removePageOpImpl:)
args:nil];
[self addOperation:operation];
[operation release];
return operation;
}
This way, you're not blocking any thread waiting for the operations to finish, but simply specify what to do when they do finish.

CKQueryOperation working with big batch

I have problem with creating CKQuery operation with big batch of data. My query works with 100 results but after more results query fail, because one thread is bad dispatched or something (libdispatch.dylib`dispatch_group_leave:
) i am lost... any idea?
+ (void) fetchAnswersWithRecordId:(CKRecordID *)recordId completionHandler:(CloudKitCompletionHandler)handler {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANSrecordID == %#", recordId];
CKQuery *query = [[CKQuery alloc] initWithRecordType:ckAnswers predicate:predicate];
CKQueryOperation *operation = [[CKQueryOperation alloc] initWithQuery:query];
CKQueryOperation * __weak weakSelf = operation;
operation.resultsLimit = 300;
NSMutableArray *tmp = [[NSMutableArray alloc] init];
operation.recordFetchedBlock = ^(CKRecord *record) {
[tmp addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (!handler)
return;
NSArray *array = [NSArray arrayWithArray:tmp];
if(cursor != nil) {
CKQueryOperation *newOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
newOperation.recordFetchedBlock = weakSelf.recordFetchedBlock;
newOperation.completionBlock = weakSelf.completionBlock;
[[self publicCloudDatabase] addOperation:newOperation];
} else {
NSLog(#"Results: %lu", [array count]);
dispatch_async(dispatch_get_main_queue(), ^{
handler(array, error);
});
}
};
[[self publicCloudDatabase] addOperation:operation];}
I think your issue lies with the __weak operation and the way you create an operation inside another operation. Here is an example (in swift) of how I do something similar i.e. get additional results, but in a fetch and not a query. Note the use of a instance variable to initialize first time and the use of semi-recursion through GCD dispatch_aync:
private func _fetchRecordChangesFromCloud() {
if !_fetching {
// this is the first and only time this code is called in GCD recusion
// we clean the caches we use to collect the results of the fetch
// so we can then save the record in the correct order so references can be created
_fetchedModifiedRecords = []
_fetchedDeletedRecordIDs = []
// mark fetching has started
_fetching = true
}
let operation = CKFetchRecordChangesOperation(recordZoneID: _customRecordZoneID, previousServerChangeToken: _serverChangeToken)
operation.recordChangedBlock = { (record: CKRecord?) in
if let record = record {
println("Received record to save: \(record)")
self._fetchedModifiedRecords.append(record)
}
}
operation.recordWithIDWasDeletedBlock = { (recordID: CKRecordID?) in
if let recordID = recordID {
println("Received recordID to delete: \(recordID)")
self._fetchedDeletedRecordIDs.append(recordID)
}
}
operation.fetchRecordChangesCompletionBlock = {
(serverChangeToken: CKServerChangeToken?, clientChangeToken: NSData?, error: NSError?) -> Void in
if let error = error {
println("Error in fetching record changes: \(error)")
// try again next sync
self._fetchAfterNextSuccessfullSync = true
self._fetching = false
return
}
// fetched records successfuly
println("fetched records successfuly")
if let serverChangeToken = serverChangeToken {
self._serverChangeToken = serverChangeToken
}
if operation.moreComing {
// we need to create another operation object and do it again
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
self._fetchRecordChangesFromCloud()
}
} else {
// we are finally done
// process the fetched records
self._processFetchedRecords()
// save all changes back to persistent store
self._saveBackgroundContext()
// we are done
self._fetching = false
}
}
self._privateDatabase.addOperation(operation)
}
FYI, dispatch_async() is not necessary, this is a memory management issue. The following works properly for a multi-batch fetch:
//
__block CKQueryOperation* enumerateOperationActive = nil;
//
NSPredicate* predicate = [NSPredicate predicateWithValue:TRUE];
CKQuery* query = [[[CKQuery alloc] initWithRecordType:#"myType" predicate:predicate] autorelease];
CKQueryOperation* enumerateOperation = [[[CKQueryOperation alloc] initWithQuery:query] autorelease];
// DEBUG: fetch only 1 record in order to "force" a nested CKQueryOperation cycle
enumerateOperation.resultsLimit = 1;
enumerateOperation.recordFetchedBlock = ^(CKRecord* recordFetched)
{
// ...
};
enumerateOperation.queryCompletionBlock = ^(CKQueryCursor* cursor, NSError* error)
{
if (error)
{
// ...
}
else
{
if (cursor)
{
CKQueryOperation* enumerateOperationNested = [[[CKQueryOperation alloc] initWithCursor:cursor] autorelease];
// DEBUG: fetch only 1 record in order to "force" a doubly-nested CKQueryOperation cycle
enumerateOperationNested.resultsLimit = 1;
enumerateOperationNested.recordFetchedBlock = /* re-used */ enumerateOperationActive.recordFetchedBlock;
enumerateOperationNested.queryCompletionBlock = /* re-used */ enumerateOperationActive.queryCompletionBlock;
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperationNested;
[database addOperation:enumerateOperationNested];
}
}
};
//
// CRITICAL: keep track of the very last (active) operation
enumerateOperationActive = enumerateOperation;
[database addOperation:enumerateOperation];
NOTE: if you attempt to access (the original) enumerateOperation.queryCompletionBlock instead of (the very last) enumerateOperationActive.queryCompletionBlock, the operation will never complete.
I liked the recursive solution on the main thread. Below is my solution in Objective C. I'm using a class level var: _recordArray, allocated ahead of time.
- (void) readRecords_Resurs: (CKDatabase *) database query: (CKQuery *) query cursor: (CKQueryCursor *) cursor {
// Send either query or cursor
CKQueryOperation *operation;
if (query != nil) operation = [[CKQueryOperation alloc] initWithQuery: query];
else operation = [[CKQueryOperation alloc] initWithCursor: cursor];
operation.recordFetchedBlock = ^(CKRecord *record) {
[_recordArray addObject:record];
};
operation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) {
if (cursor == nil || error != nil) {
// Done
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecordsDone: error == nil ? nil : [error localizedDescription]]; });
}
else {
// Get next batch using cursor
dispatch_async(dispatch_get_main_queue(), ^{ [self readRecords_Resurs: database query: nil cursor: cursor]; });
}
};
[database addOperation: operation]; // start
}
- (void) readRecordsDone: (NSString *) errorMsg {
}
My solution is a category that uses two operations but both use the same blocks, and you can supply how many results per request you'd like.
#interface CKDatabase (MH)
/* Convenience method for performing a query receiving the results in batches using multiple network calls. Best use max 400 for cursorResultsLimit otherwise server sometimes exceptions telling you to use max 400. Even using CKQueryOperationMaximumResults can cause this exception. */
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler;
#end
#implementation CKDatabase(MH)
- (void)mh_performCursoredQuery:(CKQuery *)query cursorResultsLimit:(int)cursorResultsLimit inZoneWithID:(CKRecordZoneID *)zoneID completionHandler:(void (^)(NSArray /* CKRecord */ *results, NSError *error))completionHandler{
//holds all the records received.
NSMutableArray* records = [NSMutableArray array];
//this block adds records to the result array
void (^recordFetchedBlock)(CKRecord *record) = ^void(CKRecord *record) {
[records addObject:record];
};
//this is used to allow the block to call itself recurively without causing a retain cycle.
void (^queryCompletionBlock)(CKQueryCursor *cursor, NSError *error)
__block __weak typeof(queryCompletionBlock) weakQueryCompletionBlock;
weakQueryCompletionBlock = queryCompletionBlock = ^void(CKQueryCursor *cursor, NSError *error) {
//if any error at all then end with no results. Note its possible that we got some results,
// and even might have got a cursor. However if there is an error then the cursor doesn't work properly so will just return with no results.
if(error){
completionHandler(nil,error);
}
else if(cursor){
CKQueryOperation* cursorOperation = [[CKQueryOperation alloc] initWithCursor:cursor];
cursorOperation.zoneID = zoneID;
cursorOperation.resultsLimit = cursorResultsLimit;
cursorOperation.recordFetchedBlock = recordFetchedBlock;
cursorOperation.queryCompletionBlock = weakQueryCompletionBlock; // use the weak pointer to prevent retain cycle
//start the recursive operation and return.
[self addOperation:cursorOperation];
}
else{
completionHandler(records,nil);
}
};
//start the initial operation.
CKQueryOperation* queryOperation = [[CKQueryOperation alloc] initWithQuery:query];
queryOperation.zoneID = zoneID;
queryOperation.resultsLimit = cursorResultsLimit;
queryOperation.recordFetchedBlock = recordFetchedBlock;
queryOperation.queryCompletionBlock = queryCompletionBlock;
[self addOperation:queryOperation];
}
#end

Stop pinwheel of death in infinite loop Objective C

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.

Using NSTimer with two intervals

I'm creating an app that converts text to Morse code, and then flash it out using the iPhone's flashlight. I have used string replacement, to convert the content of a NSString to Morse code.
// some of the code :
str = [str stringByReplacingOccurrencesOfString:#"5" withString:n5];
str = [str stringByReplacingOccurrencesOfString:#"6" withString:n6];
str = [str stringByReplacingOccurrencesOfString:#"7" withString:n7];
str = [str stringByReplacingOccurrencesOfString:#"8" withString:n8];
str = [str stringByReplacingOccurrencesOfString:#"9" withString:n9];
str = [str stringByReplacingOccurrencesOfString:#"0" withString:n0];
NSString *morseCode = [[NSString alloc] initWithFormat:str];
self.label.text = morseCode;
I have found a script that turns the iPhone's flashlight on and off, with adjustable intervals using NSTimer. But I can't figure out how to add two different intervals, one for the dot and one for the Morse dash.
- (void)viewDidLoad
{
[super viewDidLoad];
int spaceTime;
spaceTime = 1;
int dashTime;
dashTime = 2;
int dotTime;
dotTime = 0.8;
strobeIsOn = NO;
strobeActivated = NO;
strobeFlashOn = NO;
flashController = [[FlashController alloc] init];
self.strobeTimer = [
NSTimer
scheduledTimerWithTimeInterval:spaceTime
target:self
selector:#selector(strobeTimerCallback:)
userInfo:nil
repeats:YES
];
self.strobeFlashTimer = [
NSTimer scheduledTimerWithTimeInterval:dotTime
target:self
selector:#selector(strobeFlashTimerCallback:)
userInfo:nil
repeats:YES
];
}
- (void)strobeTimerCallback:(id)sender {
if (strobeActivated) {
strobeIsOn = !strobeIsOn;
// ensure that it returns a callback. If no, returns only one flash
strobeFlashOn = YES;
} else {
strobeFlashOn = NO;
}
}
- (void)strobeFlashTimerCallback:(id)sender {
if (strobeFlashOn) {
strobeFlashOn = !strobeFlashOn;
[self startStopStrobe:strobeIsOn];
} else {
[self startStopStrobe:NO];
}
}
Should I use two timers or can I have one with different intervals? Should I put the content of the string in an array?
I'm new in Obj-C ..
I would try to make a recursive function:
parseAndFlash
{
NSString *codeString = #"-.-. --- -.. .";
int currentLetterIndex = 0;
//codeString and currentLetterIndex should be declared outside this function as members or something
double t_space = 2, t_point = 0.5, t_line = 1, t_separator = 0.1;
double symbolDuration = 0;
if(currentLetterIndex >= [codeString length])
return;
char currentLetter = [codeString characterAtIndex:currentLetterIndex];
switch (currentLetter) {
case '-':
symbolDuration = t_line;
[self flashOnFor:t_line];
break;
case '.':
symbolDuration = t_point;
[self flashOnFor:t_point];
break;
case ' ':
symbolDuration = t_space;
[self flashOff];
break;
default:
break;
}
currentLetterIndex ++;
symbolDuration += t_separator;
[self performSelector:#selector(parseAndFlash) withObject:nil afterDelay:symbolDuration];
}
you can try to run code in sequence on background treed and sleep it for as long as you need. It would be much easier code to write and maintain than to use bunch of timers.
// execute in background
[self performSelectorInBackground:#selector(doTheMagic) withObject:nil];
- (void)doTheMagic {
NSLog(#"Turn ON");
[NSThread sleepForTimeInterval:1];
NSLog(#"Turn OFF");
[NSThread sleepForTimeInterval:0.1f];
NSLog(#"Turn ON");
[NSThread sleepForTimeInterval:1.0f];
// ...
}