I have a method from a class under tests which takes two delegates: the second delegate will call a method after the first delegate will be called with a callback function as input.
#implemenation ClassUnderTest
...
- (void) methodWithMultipleCallbacks: (id<MyDelegate>) delegate
withSecondDelegate: (id<MyDelegateWithCallback>) delegateWithCallback {
for (int i = 0; i < 2; i++){
Callback callback = ^(int input) {
NSLog(#"%d-th callback", i);
NSLog(#"input = %d", input);
};
[delegateWithCallback fetchInt:callback];
}
[delegate delegateDoStuff:32];
}
But the strange thing happens: I tried to test it using OCMock, mocking both delegates, but the crashes, and I got a EXEC_BAD_ACCESS.
I am utterly confused and would really appreciate any help here! Here's the test function
- (void) testWithMultipleCallbacks {
id <MyDelegateWithCallback> mockDelegateWithCallback = OCMProtocolMock(#protocol(MyDelegateWithCallback));
OCMStub([mockDelegateWithCallback fetchInt:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
void (^block)(int) = NULL;
[invocation getArgument:&block atIndex:2];
NSLog(#"got here");
block(33);
});
[_classUnderTest methodWithMultipleCallbacks: _mockedDelegate withSecondDelegate: mockDelegateWithCallback];
OCMVerify(OCMTimes(1), [_mockedDelegate delegateDoStuff:[OCMArg any]]);
}
I have a curious problem where my boolean variable is changing before I can assign it to a variable to be read in my react native code for Spotify SDK.
I have pinpointed the EXACT location as to where it changes and I am currently trying to figure out why:
I have a boolean called isLoggedIn that is set equal to 1 if a successful login occurs. This variable then sets loggedIn to a boolean value which is sent to my React Native code:
else
{
// login successful
NSLog(#"What is the value of isLoggedIn %#", [self isLoggedIn]);
////THIS shows that isLoggedIn still equal to 1.
[self initializePlayerIfNeeded:[RNSpotifyCompletion onComplete:^(id unused, RNSpotifyError* unusedError) {
NSLog(#"What is the value of isLoggedIn right after %#", [self isLoggedIn]);
/////NOW the isLoggedIn variable is now equal to 0.
// do UI logic on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[authController.presentingViewController dismissViewControllerAnimated:YES completion:^{
_loggingIn = NO;
NSLog(#"What is the value of isLoggedIn %#", [self isLoggedIn]);
NSNumber* loggedIn = [self isLoggedIn]; /////this is the line we need to be == 1
resolve(loggedIn);
if(loggedIn.boolValue)
{
[self sendEvent:#"login" args:#[]];
}
}];
});
}]];
}
So obviously I assume that it is due to this line right?:
[self initializePlayerIfNeeded:[RNSpotifyCompletion onComplete:^(id unused, RNSpotifyError* unusedError)
Which is running this method: initializePlayerIfNeeded but when I read the value of the isLoggedIn variable at the end of this method it still shows a boolean value of 1 (look at last line):
-(void)initializePlayerIfNeeded:(RNSpotifyCompletion*)completion
{
if(![self hasPlayerScope])
{
[completion resolve:nil];
return;
}
// ensure only one thread is invoking the initialization at a time
BOOL initializedPlayer = NO;
NSError* error = nil;
BOOL allowCaching = (_cacheSize.unsignedIntegerValue > 0);
#synchronized(_player)
{
// check if player is already initialized
if(_player.initialized)
{
initializedPlayer = YES;
}
else
{
initializedPlayer = [_player startWithClientId:_auth.clientID audioController:nil allowCaching:allowCaching error:&error];
}
}
// handle initialization failure
if(!initializedPlayer)
{
[completion reject:[RNSpotifyError errorWithNSError:error]];
return;
}
// setup player
_player.delegate = self;
_player.playbackDelegate = self;
if(allowCaching)
{
_player.diskCache = [[SPTDiskCache alloc] initWithCapacity:_cacheSize.unsignedIntegerValue];
}
// attempt to log in the player
[self loginPlayer:completion];
NSLog(#"What is the value of isLoggedIn %#", [self isLoggedIn]);
////THIS shows that isLoggedIn still equal to 1.
}
So that method finishes up with the value still equal to 1, but why after that method returns does the next line show that isLoggedIn equal to 0? I am extremely new to objC so every bit of knowledge is helpful for me. I am using this React Native Spotify library from github: https://github.com/lufinkey/react-native-spotify
You can find the exact file I'm working with here: https://github.com/lufinkey/react-native-spotify/blob/master/ios/RNSpotify.m
What else would you need to help solve this?
EDIT: here's the method that changes the isLoggedIn:
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isLoggedIn)
{
if(!_initialized)
{
return #NO;
}
else if(_auth.session == nil)
{
return #NO;
}
return #YES;
}
I create UIAlertView in my function, the problem is it shows a lot of time when the function runs, how can I create an if statement to show only one time like if UIAlertView shows not show any more.
- (void)showAlert {
_myAlertView = nil;
_myAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Call_On_Hold",nil)
message:NSLocalizedString(#"Please_Wait",nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"Close_call",nil)
otherButtonTitles:nil, nil];
_myAlertView.tag = myAlertViewsTag;
[_myAlertView show];
}
Here is the function that my UIAlertView appear continuously instead of one time.
- (void) trafficTimerRun:(NSTimer*)theTimer
{
++ trafficTimerTicks;
pjmedia_rtcp_stat stat;
if (!get_stream_info([call_id intValue], &stat)) {
return;
}
LogDebug(TAG_SIP, #"Got %d bytes on stream 0 (previous: %d)", stat.rx.bytes, prev_bytes);
if (stat.rx.bytes == prev_bytes) {
if (trafficTimerTicks >= 10) {
// Steve-note: Here we need to show a pop-up message when the call in on hold when the trafficTimerTicks run.
[self showAlert];
LogError(TAG_SIP, #"No traffic received, hanging up");
// [theTimer invalidate];
// broken = YES; Steve note: The call shouldnt broke.
// [self hangup]; Steve note: The call shouldnt hangup.
}
}
}
Use a boolean:
bool alertIsShowing = false;
and in your updating method put something like this:
if (trafficTicks > 10){
if (!alertIsShowing){
alertIsShowing = true;
[self showAlert];
}
}
Then when your alert is dismissed, reset your boolean:
alertIsShowing = false;
I'm having a hard time finding good examples on how to use these functions.
static void * kQueue1Key = "key1";
static void * kQueue2Key = "key2";
dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL);
dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL);
dispatch_sync(queue1, ^{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (A)");
dispatch_sync(queue2, ^{
NSLog(#"I'm expecting this line to run (B)");
if(dispatch_get_specific(kQueue2Key))
{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (C)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (B)"];
}
});
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (A)"];
}
});
Result
I'm expecting this line to run (A)
I'm expecting this line to run (B)
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Should not end up here (C)'
Is it expected behavior? If I were to dispatch_sync to queue1 since I'm not on the queue I would deadlock. What am I missing?
Oh here, it popped into my head why you're getting what you're getting. Notes in line:
dispatch_sync(queue1, ^{
When you get to this point, the "current queue" is queue1
if(dispatch_get_specific(kQueue1Key))
You're asking the current queue for the value it has for kQueue1Key, you set that earlier, so it gives it back to you.
{
NSLog(#"I'm expecting this line to run (A)");
dispatch_sync(queue2, ^{
When you get to this point, the "current queue" is now queue2
NSLog(#"I'm expecting this line to run (B)");
if(dispatch_get_specific(kQueue2Key))
You're asking the current queue for the value it has for kQueue2Key, you set that earlier, so it gives it back to you.
{
if(dispatch_get_specific(kQueue1Key))
You're now asking the current queue for the value it has for kQueue1Key. Since the current queue is queue2 and you never set a value with kQueue1Key on queue2 you get back NULL.
{
NSLog(#"I'm expecting this line to run (C)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
The misunderstanding here is that dispatch_get_specific doesn't traverse the stack of nested queues, it traverses the queue targeting lineage. For instance, if you did this instead,
static void * kQueue1Key = (void*)"key1";
static void * kQueue2Key = (void*)"key2";
dispatch_queue_t queue1 = dispatch_queue_create("com.company.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("com.company.queue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, kQueue1Key, (void *)kQueue1Key, NULL);
dispatch_queue_set_specific(queue2, kQueue2Key, (void *)kQueue2Key, NULL);
// Set Queue2 to target Queue1
dispatch_set_target_queue(queue2, queue1);
dispatch_sync(queue2, ^{
if(dispatch_get_specific(kQueue1Key))
{
NSLog(#"I'm expecting this line to run (A)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
if(dispatch_get_specific(kQueue2Key))
{
NSLog(#"I'm expecting this line to run (B)");
}
else
{
[NSException raise:NSInternalInconsistencyException format:#"Should not end up here (C)"];
}
});
...the targeting relationship is the one that gets traversed, not the stack relationship. It would be nice if there were something that traversed the stack relationship, but I'm not aware of anything (that you wouldn't have to implement yourself).
As mentioned in my comment, recursive locking using dispatch_sync is, in the general case, not possible due to the possibility of non-default queue targeting. For what it's worth, given/assuming default queue targeting, here is one possible approach:
#import <unordered_set>
#import <pthread.h>
static dispatch_once_t recursiveLockWithDispatchQueueTLSKeyOnceToken;
static pthread_key_t recursiveLockWithDispatchQueueTLSKey;
typedef std::unordered_multiset<const void*> RecursiveLockQueueBag;
static void freeRecursiveLockWithDispatchQueueTLSValue(void* tlsValue)
{
RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(tlsValue);
if (ms) delete ms;
}
static inline BOOL queueStackCheck(dispatch_queue_t q, BOOL checkAndPushNotPop) // If yes, check and push if not on. If no, pop.
{
dispatch_once(&recursiveLockWithDispatchQueueTLSKeyOnceToken, ^{
pthread_key_create(&recursiveLockWithDispatchQueueTLSKey, freeRecursiveLockWithDispatchQueueTLSValue);
});
RecursiveLockQueueBag* ms = reinterpret_cast<RecursiveLockQueueBag*>(pthread_getspecific(recursiveLockWithDispatchQueueTLSKey));
if (!ms)
{
ms = new RecursiveLockQueueBag();
pthread_setspecific(recursiveLockWithDispatchQueueTLSKey, reinterpret_cast<const void*>(ms));
}
const void* const vpq = reinterpret_cast<const void*>((__bridge const void*)q);
BOOL alreadyOn = NO;
if (checkAndPushNotPop)
{
alreadyOn = (ms->count(vpq) > 0);
if (!alreadyOn)
{
ms->insert(vpq);
}
}
else
{
ms->erase(vpq);
}
return alreadyOn;
}
void dispatch_recursive_sync(dispatch_queue_t queue, dispatch_block_t block)
{
if (queueStackCheck(queue, YES))
{
block();
}
else
{
#try
{
dispatch_sync(queue, block);
}
#finally
{
queueStackCheck(queue, NO);
}
}
}
#implementation MyAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
dispatch_queue_t a = dispatch_queue_create("a", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t b = dispatch_queue_create("b", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t c = dispatch_queue_create("c", DISPATCH_QUEUE_SERIAL);
//dispatch_set_target_queue(a, c);
dispatch_recursive_sync(a, ^{
dispatch_recursive_sync(b, ^{
dispatch_recursive_sync(c, ^{
dispatch_recursive_sync(a, ^{
dispatch_recursive_sync(b, ^{
dispatch_recursive_sync(c, ^{
dispatch_recursive_sync(a, ^{
NSLog(#"got there");
});
});
});
});
});
});
});
}
#end
This is the lowest-overhead implementation I could think of in a few minutes. I used C++ to avoid message sending overhead. It requires that all uses of the queue use this function. This can be useful when there's a private queue protecting internal state of an object (i.e. where the queue is private and therefore guaranteed not to be retargeted, and where you can easily ensure that all consumers of the queue use dispatch_recursive_sync.
Hello every time i try to record the date with CFAbsoluteTimeGetCurrent(); my app ignores the rest of the buttons, it's as if it takes over all memory and blocks all user input. I was wondering what i am doing wrong? I thought of making a function showtime() but i dont know how to pass values between functions from toggleRecording to showtime so that my method would work if this would even solve the problem. Below is my code:
- (IBAction)toggleRecording:(id)sender
{
// Start recording if there isn't a recording running. Stop recording if there is.
[[self recordButton] setEnabled:NO];
if (![[[self captureManager] recorder] isRecording]){
[[self captureManager] startRecording];
/* figure out a day to record for every half a second
while([[[self captureManager]recorder] isRecording]){
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
NSLog(#" time is %i", startTime);
}
*/
}
else
[[self captureManager] stopRecording];
}
-(void)showtime:(id)sender{
while([[[self captureManager]recorder] isRecording]){
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
NSLog(#" time is %f", startTime);
}
}
An application has to run the event loop to receive the events. While your own code is doing something else (here it's doing a "while" loop), events are queued and not delivered until your code returns.
Schematically, an application is doing something like:
while(1) {
event = _UIReceiveNextQueuedEvent();
_UIProcessEvent(event); // <- this calls -showTime:
}
If you want to record time while not blocking the loop, you will have to schedule an NSTimer every 0.5s and invalidate it as soon as recording is turned off.
Something like:
- (void)showTime:(id)sender
{
if ([[[self captureManager]recorder] isRecording]) {
[NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
}
}
- (void)timerFired:(NSTimer *)timer
{
if ([[[self captureManager]recorder] isRecording]) {
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
NSLog(#" time is %f", startTime);
} else {
[timer invalidate];
}
}