@synthesize-ing a C array of structs in Objective-C 2.0
Solution 1
@synthesize buffers
fails to compile as follows: "error: synthesized property 'buffers' must either be named the same as a compatible ivar or must explicitly name an ivar"
Try:
@synthesize buffers = _buffers;
Solution 2
I'm just now doing a similar thing for an OpenGL app, and since this is a useful question (and so long without an answer) I want to translate my solution. Note that it's probably less effort to use use NSArray… but if you need to hone performance and/or use basic C (or GL or similar non-object) types this should help. Also, forgive me if I do something that doesn't work in OSX, as my code is actually for iPhone... I'm sure that at least the concepts are the same though. Here is an outline of steps:
- Declare your variable as a pointer
- Protect it from direct outside access with @private
- Declare the property as also a pointer, which will be set to point at any array we pass to the setter
- Write or synthesize a setter and getter method. My use of @synthesize seemed to confuse things and led to a memory leak, so I'll follow the write-your-own path here.
- (The missing link from some previous posts, I think) Since you are returning a C array, allocate memory using malloc. The init method is probably a good place for this, and you can use dealloc or elsewhere to call free(myBuffers) since your variable is of suitably broad scope.
I don't know that @synthesize it will handle malloc'd variables for you, but someone else may have experience with that that I lack. Therefore, I am just writing the setter and getter. But note we still get dot syntax and the vanilla look of @property in the interface.
Here's code:
In PlayerState.h:
@interface PlayerState : NSObject {
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
@private
AudioQueueBufferRef *myBuffers;
// [...]
@property(assign) AudioStreamBasicDescription dataFormat;
@property(assign) AudioQueueRef queue;
@property(assign) AudioQueueBufferRef *myBuffers;
That gives you a pointer and a property, the latter of which is equivalent to the accessor methods to the memory indicated by the pointer. We'll write our own accessors, and we will allocate memory for the pointer and give it something to point at.
Now in PlayerState.m:
-(id)init
{
if (self = [super init]) {
myBuffers = (AudioQueueBufferRef*)malloc(sizeof(AudioQueueBufferRef) * 3);
/* memory is allocated. Free it elsewhere. Note that "Leaks" will
probably falsely flag this, but you can test it in Object Allocations
and see that it is clearly released. */
}
return self;
}
// return an array of structs
-(AudioQueueBufferRef*)myBuffers
{
// you can check for values, etc here, but basically it all comes down to:
return myBuffers;
}
// the setter; there are several ways to write it, depending on what your objective
-(void)setMyBuffers:(AudioQueueBufferRef*)abc
{
int i = 3;
while (i > 0) {
i--;
myBuffers[i] = abc[i];
}
//another possible implementation, not tested
//free(myBuffers);
//myBuffers = NULL;
//myBuffers = xyz;
}
// if you want to set individual attributes, you can have another method like:
-(void)setMyBuffersA:(AudioQueueBufferRef)a B:(AudioQueueBufferRef)b C:(AudioQueueBufferRef)c
{
myBuffers[0] = a;
myBuffers[1] = b;
myBuffers[2] = c;
}
Now you can call these from another class like this:
-(void)methodOfOtherClass
{
PlayerState * playerState = [[PlayerState alloc] init];
AudioQueueBufferRef abc[3] = //something
[playerState setMyBuffers:(AudioQueueBufferRef*)abc];
DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])
abc[1] = 6.22; // reassigning one of the values in our local variable
playerState.myBuffers = (GLfloat*)abc; //call the setter again, with dot syntax this time if you like
DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])
[playerState setMyBuffersA:yourStructA B:yourStructB C:yourStructC];
DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])
}
This tests OK for me using iPhone OS and my (admittedly slightly different) openGL types. However, I think it will work for you too. It's possible you'll have to adjust something, or that I've introduced a minor typo while copying and pasting over my original code. Also, if you like to log things while you're coding like I do, it might be more useful to log some member of the buffer structs to see that what's there is what's supposed to be there.
Also if you're used to printing objects while you code with your NSLog wrapper, ie: DLog(@"array is %@", myNSarray)
you'll get an EXC_BAD_ACCESS
trying to do the same with a C array. The C array is not an Obj-C object, so keep that in mind and hold its hand a bit more than you would an Objective-C object ;)
Solution 3
Edit: Peter Hosey has pointed out that an array in C is not the same thing as a pointer. (see this document for details). That would explain the error you are seeing, and would make the code that I posted wrong.
The other SO question that gs links to in his answer suggests a work-around, which I have copied, in the context of this question:
// PlayerState.h:
@interface PlayerState : NSObject
{
AudioQueueBufferRef _buffers[3];
}
@property(readonly) AudioQueueBufferRef * buffers;
// PlayerState.m:
@implementation PlayerState
@dynamic buffers;
- (AudioQueueBufferRef *)buffers { return _buffers; }
@end
This would allow you to access buffers
as if it were a pointer to an array of AuidoQueueBufferRef
objects.
Solution 4
Looks like this question here:
Create an array of integers property in objective-c
Solution 5
Why are you translating the structure into an Objective-C object? Objective-C is a strict superset of C, so you can just use the given struct as-is with Objective-C.
[EDIT] In response to your comments, the compiler is complaining about the mBuffers
declaration because of the rules about what's allowable as the size of a static array. The rules in C are a little stricter than the rules in C++. As an easy fix, just change the line
static const int kNumberBuffers = 3;
into
#define kNumberBuffers 3
and then the struct should compile correctly (provided, of course, that you've included the necessary headers that define all of the proper data types such as AudioStreamBasicDescription
, etc.).
Comments
-
ELLIOTTCABLE almost 2 years
I'm trying to follow a tutorial for a C++ interface in the Mac OS X API (Audio Queue Services), but in a Cocoa (well, actually just Foundation) application (well, actually just a 'tool'). It has a struct that looks like this:
static const int kNumberBuffers = 3; // 1 struct AQPlayerState { AudioStreamBasicDescription mDataFormat; // 2 AudioQueueRef mQueue; // 3 AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4 AudioFileID mAudioFile; // 5 UInt32 bufferByteSize; // 6 SInt64 mCurrentPacket; // 7 UInt32 mNumPacketsToRead; // 8 AudioStreamPacketDescription *mPacketDescs; // 9 bool mIsRunning; // 10 };
I'm having a lot of trouble with translating item 4 into Objective-C, because I can't figure out how to
@synthesize
a C array. Specifically, this is what I have so far:PlayerState.h
#import <Foundation/Foundation.h> #import <AudioToolbox/AudioQueue.h> @interface PlayerState : NSObject { AudioStreamBasicDescription dataFormat; AudioQueueRef queue; AudioQueueBufferRef _buffers[3]; int audioFile; // make this an actual type? UInt32 bufferByteSize; SInt64 currentPacket; UInt32 numPacketsToRead; AudioStreamPacketDescription* packetDescs; bool isRunning; } @property(assign) AudioStreamBasicDescription dataFormat; @property(assign) AudioQueueRef queue; @property(assign) AudioQueueBufferRef buffers; @property(assign) int audioFile; @property(assign) UInt32 bufferByteSize; @property(assign) SInt64 currentPacket; @property(assign) UInt32 numPacketsToRead; @property(assign) AudioStreamPacketDescription* packetDescs; @property(assign) bool isRunning; @end
PlayerState.m
#import "PlayerState.h" @implementation PlayerState @synthesize dataFormat; @synthesize queue; @synthesize buffers; @synthesize audioFile; @synthesize bufferByteSize; @synthesize currentPacket; @synthesize numPacketsToRead; @synthesize packetDescs; @synthesize isRunning; @end
@synthesize buffers
fails to compile as follows: "error: synthesized property 'buffers' must either be named the same as a compatible ivar or must explicitly name an ivar"This is obviously because the corresponding ivar is named
_buffers
and notbuffers
- but this is necessary, because I can't define a property as an array (can I?@property(assign) *AudioQueueBufferRef buffers
is a syntax error)What can I do to either define the ivar as an array of
AudioQueueBufferRef
structs, or synthesize the property such that it refers to the_buffers
array? -
Mathias about 15 yearsAn array is automatically a pointer type. You don't need that extra * after AudioQueueBufferRef in the interface.
-
ELLIOTTCABLE about 15 yearsUnfortunately, that doesn't work. The struct must use some C++ magic that is unknownst to me, because copy-pasting it verbatim into a Foundation tool results in a compile error (compiler doesn't like the
AudioQueueBufferRef mBuffers[kNumberBuffers]
line, for whatever reason). -
ELLIOTTCABLE about 15 yearsSo… what should I change it to? I tried juggling brackets, brackets with a number in them, and asterisks all over the place. I just can't get it to compile with any combination of the three.
-
Mathias about 15 yearsDid you rename your source file to .mm so that xcode knows to expect objective-c++ (a mix of the two)?
-
Mathias about 15 yearsExactly as I show it in my code above. Don't use the * after AudioQueueBufferRef when declaring the array, but do use the * when declaring the property.
-
Mathias about 15 yearsAudioQueueBufferRef buffers[3] is actually a pointer. When you declare an array of type X, you are effectively creating a variable of type X* that points to a block of memory big enough to hold the array.
-
ELLIOTTCABLE about 15 yearsNope, I haven't. I want to avoid getting into Objective-C++, or anything involving C++. The goal here is to write an Objective-C class to wrap the tools described in the AQS documents. I'll try that though, just for fun!
-
ELLIOTTCABLE about 15 yearsUnfortunately, that leaves me with the same error: "error: type of property 'buffers' does not match type of ivar 'buffers'".
-
ELLIOTTCABLE about 15 yearsThis makes the posted code preform the same as eJames' code does - I'd give you an upmod, except I'm out of votes for the day. Unfortunately, I end up at the same place as I'm stuck at with eJames' code (see comments above)
-
Mathias about 15 yearsWell, that's just bizarre. In that case, I'd take a look at the answer from gs. I thought for sure this would work.
-
Nico about 15 yearseJames: Not quite. Arrays are different from pointers. (This is one of two known errors in my pointer tutorial, which I haven't had time to fix.) I used to have a link to a page that explained all the differences, but can't find it. Here's a substitute: lysator.liu.se/c/c-faq/c-2.html
-
Mathias about 15 years@Peter Hosey: Wow. I learn something new every day. If I could upvote your comment, I would! Thank you for the link. I'll update my answer.
-
ELLIOTTCABLE over 13 yearsI’ll have to take a look at this when/if I return to the project that sparked this… I don’t even have the original code, so I can’t even test if this works for the original problem :x