I'm having trouble with some objective-c in Xcode. I'm trying to adapt an example from the DiskArbitration framework, and I'm getting stuck. This example sets a callback whenever the framework sees a disk. From the callback, I want to update a label.
#interface AVRecorderDocument ()
#property (assign) IBOutlet NSTextField *labelSpaceLeft;
#end
#implementation AVRecorderDocument
....
-id(init){
...
DASessionRef sessionDA = DASessionCreate(kCFAllocatorDefault);
DARegisterDiskAppearedCallback(sessionDA, kDADiskDescriptionMatchVolumeMountable, diskAppearedCallback, 0);
DASessionScheduleWithRunLoop(sessionDA, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
...
}
static void diskAppearedCallback(DADiskRef disk, void* context)
{
CFDictionaryRef description = DADiskCopyDescription(disk);
NSLog(#"Disk appeared: %#", description);
NSLog(#"Disk Name: %#", CFDictionaryGetValue(description, CFSTR("DADeviceModel")));
// [_labelSpaceLeft setStringValue:[NSString CFDictionaryGetValue(description, CFSTR("DADeviceModel"))]];
CFRelease(description);
}
...
#end
The commented line inside the static function is what i've tried, but I get a "Use of undeclared identifier" error.
I've also tried converting the function to a method, which the compiler is ok with, but then I don't know how to properly call this from the DARegisterDiskAppearedCallback line at the top.
-(void)diskAppearedCallback:(DADiskRef *)disk context:(void *)context{
CFDictionaryRef description = DADiskCopyDescription(*disk);
NSLog(#"Disk appeared: %#", description);
NSString *spaceLeft = CFDictionaryGetValue(description, CFSTR("DADeviceModel"));
NSLog(#"Disk Name: %#", CFDictionaryGetValue(description, CFSTR("DADeviceModel")));
[labelSpaceLeft setStringValue:[NSString stringWithFormat: #"%#", spaceLeft]];
CFRelease(description);
}
**UPDATE: Here is a gist of the project: https://gist.github.com/anonymous/cb1c5795536ca15ef4e3
OK so the issue is that you are basically mixing C and Objective-C. The callback function diskAppearedCallback is a C function and is not part of that class, even though it is defined between #implementation and #end. It therefore doesn't have access to _labelSpaceLeft.
To fix the issue you need to get the callback function to call a method on that class instance, so when registering the callback, pass the instance in the last parameter. This is precisely what context pointers are for in callback functions.
DARegisterDiskAppearedCallback(sessionDA,
kDADiskDescriptionMatchVolumeMountable,
diskAppearedCallback,
self); // HERE
and then context in the callback function will be a pointer to the class instance:
static void diskAppearedCallback(DADiskRef disk, void* context) {
CFDictionaryRef description = DADiskCopyDescription(*disk);
NSLog(#"Disk appeared: %#", description);
NSString *spaceLeft = CFDictionaryGetValue(description, CFSTR("DADeviceModel"));
NSLog(#"Disk Name: %#", CFDictionaryGetValue(description, CFSTR("DADeviceModel")));
// HERE
AVRecorderDocument *instance = (AVRecorderDocument *)context;
[instance.labelSpaceLeft setStringValue:spaceLeft];
CFRelease(description);
}
Related
I have a singleton WarningManager class in my project looks as follows
WarningManager.h file
+ (WarningManager *)getInstance;
- (void) createAndPushWarning:(id<UIAlertViewDelegate>)actionDelegate isLocalisedStrings:(BOOL)localized text:(NSString *)text cancel:(NSString *)cancel buttons:(NSString*)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
WarningManager.m file
static WarningManager *instance = nil;
+ (WarningManager *) getInstance {
if (!instance) {
instance = [[self alloc] init];
}
return instance;
}
- (void) createAndPushWarning:(id<UIAlertViewDelegate>)actionDelegate isLocalisedStrings:(BOOL)localized text:(NSString *)text cancel:(NSString *)cancel buttons:(NSString*)firstObj, ... {
va_list argumentList;
va_start(argumentList, firstObj);
NSMutableArray *buttonArray = [NSMutableArray array];
if(firstObj){
if(!localized){
[buttonArray addObject:NSLocalizedString(firstObj, #"")];
} else {
[buttonArray addObject:firstObj];
}
id eachObject;
while ((eachObject = va_arg(argumentList, id))){
if(!localized){
[buttonArray addObject: NSLocalizedString(eachObject, #"")];
}else{
[buttonArray addObject: eachObject];
}
}
}
if (!localized) {
if(text){
text = NSLocalizedString(text, #"");
}
if(cancel){
cancel = NSLocalizedString(cancel, #"");
}
}
va_end(argumentList);
LogW(#"Warning : %#",text);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"My App Name", #"") message:text delegate:actionDelegate cancelButtonTitle:cancel otherButtonTitles:nil];
for (NSString *button in buttonArray) {
[alertView addButtonWithTitle:button];
}
[alertView show];
}
the above code works well when an objective - C class file calls as follows
[[WarningManager getInstance] createAndPushWarning:self isLocalisedStrings:NO text:#"Text want to display" cancel:nil buttons:#"Button 1", #"Button 2", nil];
Now I am creating swift classes and I want to use this same warning manager in it but variadic function syntax changed in swift so I have added another method by replacing variadic arguments as follows in WarningManager class.
WarningManager.h file
- (void) createAndPushWarning:(id<UIAlertViewDelegate>)actionDelegate isLocalisedStrings:(BOOL)localized text:(NSString *)text cancel:(NSString *)cancel agruments:(va_list)buttons
WarningManager.M file
- (void) createAndPushWarning:(id<UIAlertViewDelegate>)actionDelegate isLocalisedStrings:(BOOL)localized text:(NSString *)text cancel:(NSString *)cancel agruments:(va_list)buttons {
[self createAndPushWarning:actionDelegate isLocalisedStrings:localized text:text cancel:cancel buttons:(__bridge NSString *)(buttons), nil];
}
To call this from a swift class I have created an extension to WarningManager class as follows in WarningManager+ArgumentList.swift file
extension WarningManager {
class func WarningWrapper(actionDelegate: UIAlertViewDelegate, isLocalizedString: Bool, text:String, cancel: String, _ args: CVarArg...) {
withVaList(args) { _ in WarningManager.getInstance().createAndPushWarning(actionDelegate, isLocalisedStrings: isLocalizedString, text: text, cancel: cancel, agruments: getVaList(args)) }
}
}
Calling my extension method from swift class
WarningManager.WarningWrapper(actionDelegate: self, isLocalizedString: false, text: "Message to Display", cancel: "OK", "")
So when I call this extension from my swift class I am getting bad access error
but I change my WarningManager.m file like this it displays the alert.
- (void) createAndPushWarning:(id<UIAlertViewDelegate>)actionDelegate isLocalisedStrings:(BOOL)localized text:(NSString *)text cancel:(NSString *)cancel agruments:(va_list)buttons {
[self createAndPushWarning:actionDelegate isLocalisedStrings:localized text:text cancel:cancel buttons:nil];
}
Somewhere I am making mistake in sending the arguments I am not sure how to fix this.
any suggestions and ideas are appreciated, Thanks In Advance for people who read this question this far.
You can't pass a va_list where an actual list of arguments is required, even if they are variadic. Think of a va_list as an array. You can't pass an array where a parameter list is required and expect each array item to be treated as an individual parameter. The entire array is passed as a single first parameter.
In an ideal world you would get an error about this (and in pure Swift you would), but C's variadic parameter lists do not include enough type information to be able to detect this error.
You need to reverse your code: put most of the code into the va_list-taking version (with the "arguments:" parameter) and then call that from the variadic version, which simply calls va_start, calls the va_list version, and then calls va_end.
I'm saving a users zip code to the Firebase database and want to query that database on app launch to see if the user has input their zip code already or if they're a brand new user.
I've posted my code before. I pulled the sample code from the Firebase docs, but it seems that my app is never even running the following code to get the value
[[[_ref child:#"user"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {...
What am I missing out on?
#import "FollowingVC.h"
#import <FirebaseDatabase/FirebaseDatabase.h>
#import Firebase;
#interface FollowingVC ()
#property NSString *uid;
#property FIRDatabaseReference *ref;
#property NSString *zipcode;
#end
#implementation FollowingVC
- (void)viewDidLoad {
[super viewDidLoad];
[self createAuthorizedUser];
[self checkForZipCode];
}
-(void)createAuthorizedUser
{
[[FIRAuth auth]
signInAnonymouslyWithCompletion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
if (!error) {
self.uid = user.uid;
self.ref=[[FIRDatabase database]reference];
}
}];
}
-(void)checkForZipCode
{
NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:#"user"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
// Get user value
self.zipcode = snapshot.value[#"zip code"];
NSLog(#"It worked: %#", self.zipcode);
// ...
} withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
}];
}
#end
Firebase is asynronous and you need to allow time for events to complete before moving on in the app.
In this case, you should call [self checkForZipCode] inside the sign-in block after self.uid is populated.
Otherwise you run the risk of the checkForZipCode function running before the self.uid is populated.
Let Firebase control the flow of the app - and don't try to use Firebase synchronously as it will get you into trouble due to internet lag etc.
Hello I am currently having issues bridging a variable in my completion block. I want to set isPreviewPlaying to NO in the completion block but I can't.
static void completionCallback (SystemSoundID mySSID, void *myself) {
AudioServicesRemoveSystemSoundCompletion (mySSID);
AudioServicesDisposeSystemSoundID(mySSID);
CFRelease(myself);
UIButton *previewButton = (__bridge UIButton*)myself;
[previewButton setTitle:#"Preview" forState:UIControlStateNormal];
_isPreviewPlaying = NO // I want to do this, but I can't.
}
- (void)previewButtonPressed:(id)sender {
if (_isPreviewPlaying) {
_isPreviewPlaying = NO;
NSLog(#"STOP");
AudioServicesDisposeSystemSoundID(soundID);
} else {
NSString * selectedPreviewSound = [self.soundFile objectAtIndex: _soundFileIndex];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath: selectedPreviewSound], &soundID);
AudioServicesPlaySystemSound (soundID);
_isPreviewPlaying = YES;
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, completionCallback, (__bridge_retained void *)sender);
[sender setTitle:#"Stop" forState:UIControlStateNormal];
}
}
You will need to pass the class instance to the completion block function, as it's a C function, and that way you can access properties and methods on the instance.
That is the intent of myself parameter I believe, however you are currently releasing it using CFRelease() for some reason I cannot fathom (I am assuming that sender is a button as previewButtonPressed: looks like a button event callback, and releasing it won't do it any favours; read crash).
Therefore I would suggest:
Remove the CFRelease() call.
Cast myself to whatever the class is: MyClass *myclass = (MyClass *)myself;.
Call myclass.previewPlaying = NO; (assuming it's a property). Call [myclass previewNotPlaying] (see below).
Pass self instead of sender to AudioServicesAddSystemSoundCompletion().
EDIT Having said that, I now see you are using the button instance to display information. Instead of calling the property, above, call a method on the instance instead and make that method do the work:
- (void)previewNotPlaying
{
// _previewButton is an IBOutlet to the button
[_previewButton setTitle:#"Preview" forState:UIControlStateNormal];
_isPreviewPlaying = NO;
}
I need some help.
I'm using this singleton pattern within an iOS application I'm developing:
.h file
#import <Foundation/Foundation.h>
#class Item;
#interface ItemManager : NSObject
- (id)init;
+ (ItemManager *)sharedInstance;
- (int)ratingFromObjectName:(NSString *)objectName;
#property(nonatomic,strong) NSArray *itemArray;
#end
.m file
static ItemManager *sharedInstance = nil;
+ (ItemManager *)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ItemManager alloc] init];
});
return sharedInstance;
}
- (int)ratingFromObjectName:(NSString *)objectName {
for (int i = 0; i < itemArray.count; i++) {
if ([[[itemArray objectAtIndex:i] itemName] isEqualToString:objectName]) { //This is the line that throws bad access code 1
NSLog(#"Found: %# Returned: %d", [[itemArray objectAtIndex:i] ratingAverage],
[[[itemArray objectAtIndex:i] ratingAverage] intValue]);
return [[[itemArray objectAtIndex:i] ratingAverage] intValue];
}
}
return 0;
}
I get bad access when I use this in another class:
int rating = [[ItemManager sharedInstance] ratingFromObjectName:bla];
The bla object being sent is a NSString that is definitely working, it is 100% not the issue, as I have tested this. Removing the sharedInstance method and creating an array every time seems to work, however my attempt for this singleton is to avoid that, any suggestions would be greatly appreciated.
Please note that I have commented on the line returning the error.
Regards, WA
You need to work out which line of code is throwing the bad access. Is it the sharedInstance method or ratingFromObjectName:. I would first change the calling code to
ItemManager *manager = [ItemManager sharedInstance];
int rating = [manager ratingFromObjectName:bla];
As that will help with isolating the problem.
Secondly, I would also consider not using the Singleton pattern unless really necessary is iOS apps. It's been my experience that it is often overused (and in the Java world), and whilst convenient, can make writing unit tests more complicated.
Long time lurker, first time poster. I'm relatively new to objective-C so my apologies if I'm asking something fairly simple. My google & stack overflow-fu has let me down here, so I figured somebody could maybe help.
I have a synchronous process executing, say, three functions in a row - call it A -> B-> C , where task A executes, followed by B, followed by C.
Now, B involves an asynchronous process with a delegate callback for completion. But B must complete before C is executed, so I need some mechanism such that C is not triggered before B has finished. I imagine there must be a common design pattern for this problem?
Initially naive solution would be -
execute A
execute B
while (!B finished) {}
execute C
...but this seems really lame.
I suspect I can do this with some kind of block, but for the life of me I just can't figure it out. Could anyone help?
appreciate any assistance!
Guillaume
Thanks for all the feeback - apologies for not responding sooner. I've now resolved this in a slightly different way to the suggestions:
Firstly, I extended NSObject to have the following method -
#import "NSObject+LTExtensions.h"
#implementation NSObject (Testing)
- (void) performSelectorWithBlock: (SEL) selector withSemaphore:(dispatch_semaphore_t)semaphore
{
[self performSelector:selector]; // This selector should complete the semaphore
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
#end
This allows me to execute a block via a selector. When the block executes, the thread on which it is executed will wait until signaled to proceed by a specific dispatch semaphore.
What we can then do is as follows:
Call A
Create a dispatch semaphore and define a selector which executes B
Call the method defined above to execute B and wait for the selector to complete
When B is completed (via a delegate callback), it signals the dispatch semaphore to suspend the wait
I then execute C
So we have
A
B -> Asynchronous with delegate callback
C
Here's a simple example of how the above is implemented
-(void) methodA {
// ... do something
// Assign your semaphore (this is a dispatch_semaphore_t)
self.semaphore = dispatch_semaphore_create(0);
[self performSelectorWithBlock:#selector(methodB) withSemaphore:semaphore];
[self methodC];
}
-(void) methodB {
// ... do whatever needs to be done asynchronously
CFRunLoopRun();
}
-(void) methodBDelegateCallBack {
// This is called when B completes
// Signal completion
dispatch_semaphore_signal(self.semaphore);
CFRunLoopStop(CFRunLoopGetCurrent());
}
-(void) methodC {
...
}
Works very well without any issues (but I am new to Obj C, so there may be glaring issues with my approach).
Another approach to this problem might be the following: create an helper object for the async task and copy a completion block when the task is called. Call the completion block using the delegate methods once the async task is finished. As a result we might execute the tasks in order like the following:
FSTask *taskA = [FSTask taskWithName:#"Task A"];
FSAsyncTask *taskB = [FSAsyncTask asyncTaskWithName:#"Task B"];
FSTask *taskC = [FSTask taskWithName:#"Task C"];
[taskA performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskB performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
[taskC performTaskWithCompletionBlock:^ (NSString *result) {
NSLog(#"%#", result);
}];
}];
}];
So how is this achieved? Well, look at the task objects below ...
FSTask.m - synchronous work on main thread ...
#interface FSTask ()
#property (nonatomic, copy) NSString *name;
#end
#implementation FSTask
#synthesize name = _name;
+ (FSTask *)taskWithName:(NSString *)name
{
FSTask *task = [[FSTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
NSString *message = [NSString stringWithFormat:#"%#: doing work on main thread ...", _name];
NSLog(#"%#", message);
if (block)
{
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
block(result);
}
}
#end
FSAsyncTask.m - asynchronous work on background thread ...
#interface FSAsyncTask ()
#property (nonatomic, copy) void (^block)(NSString *taskResult);
#property (nonatomic, copy) NSString *name;
- (void)performAsyncTask;
#end
#implementation FSAsyncTask
#synthesize block = _block;
#synthesize name = _name;
+ (FSAsyncTask *)asyncTaskWithName:(NSString *)name
{
FSAsyncTask *task = [[FSAsyncTask alloc] init];
if (task)
{
task.name = name;
}
return task;
}
- (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block
{
self.block = block;
// the call below could be e.g. a NSURLConnection that's being opened,
// in this case a NSURLConnectionDelegate method will return the result
// in this delegate method the completion block could be called ...
dispatch_queue_t queue = dispatch_queue_create("com.example.asynctask", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^ {
[self performAsyncTask];
});
}
#pragma mark - Private
- (void)performAsyncTask
{
for (int i = 0; i < 5; i++)
{
NSString *message = [NSString stringWithFormat:#"%d - %#: doing work on background thread ...", i, _name];
NSLog(#"%#", message);
[NSThread sleepForTimeInterval:1];
}
// this completion block might be called from your delegate methods ...
if (_block)
{
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *result = [NSString stringWithFormat:#"%#: result", _name];
_block(result);
});
}
}
#end
You can assign a block property to B where it would be used to execute a block of code before calling the delegate method. something like:
#property (nonatomic, copy)void(^yourBlock)(id blockParameter);
So, after calling B's delegate, you could call upon this block and execute it. Inside this block, you can call C's method.
the way I handled this is.
I created a NSMutableDictionary before the async call.
Then i make the async call. and do a check for the value I am waiting for
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[AsyncCallClass asyncCall:^{
#synchronized(dictionary) {
[dictionary setValue:myValue forKey:#"result"];
}
}];
while (true){
#synchronized(dictionary){
if ([dictionary valueForKey:#"resultValue"] != nil){
break;
}
}
[NSThread sleepForTimeInterval:.25];
}
MyResultClass *result = [dictionary valueForKey:#"resultValue"];
you can add time out for this too to stop it from being an infinite loop. but this is my solution. and it seems to work pretty well.
Here is the typical code I use to do such things (adapt the completionBlock signature and method names to your needs of course)
typedef void (^BCompletionBlock)(void);
#interface B : NSObject <BDelegate>
#property(nonatomic, copy) BCompletionBlock completionBlock;
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock;
#end
#implementation B
-(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock
{
// Store the completion block for later use
self.completionBlock = aCompletionBlock;
// Then execute your asynchronous action, that will call some delegate method when done
[self doYourAsynchronousActionWithDelegate:self];
}
-(void)yourBDelegateMethodCalledWhenDone
{
// Upon your async task completion, call your completion block then
if (self.completionBlock) self.completionBlock();
}
#end
Then here is an example usage:
-(void)doActions
{
[a doSynchronousAction];
[b doAsynchronousActionWithCompletion:^{
[c doSynchronousAction];
// A,B,C are now done
}];
}
I do this quite all the time to "convert" actions that uses delegate methods (to tell me when they are done) to actions that uses completionBlocks (have some classes to do this for UIAlertViews, UIActionsSheets, and many more cases for example) and it works like a charm.
I find it much more easier to use completionBlocks than the delegate mechanism in such cases.
You can also pass C in a block like so...
define a custom block
typedef void(^myCompletion)(BOOL complete);
Create your B method
-(void)performBWithCompletionBlock:(myCompletion)complete;
{
// do your things
[self.delegate delegateCallback];
complete(YES);
}
then create BG / async ABC
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // now we're on a BG queue to perform our async tasks
[self performA];
[self performBWithCompletionBlock:^(BOOL complete) {
if (complete == YES)
[self performC];
}];
});
If you want C to be on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self performC];
});