Handling properties on a block running on a random thread

Apple’s new way of handling returned values in form of blocks instead of delegate methods makes it a bit easier to implement such methods in Xojo, but currently in my attempts it makes it much more unstable too.

I ran into the following: I use an iOSBlock to receive the buffer data and timestamp from an AVAudioEngine. This runs nicely; the block is called frequently and I can see the changes in both values by logging them to the console. But I cannot analyze them. When I try to create an object from their pointer values inside the block (buffer is an AVAudioPCMBuffer, Time an AVAudiotime object), Simulator crashes with a StackOverflow.

I can, anyway, write the ptr values to properties attached to the iOSView. But I could not find a way to trigger their analyzation afterwards. A timer.CallLater in the block gives me another StackOverflow, so I tried attaching a timer to the iOSView with a very short delay, set to modes.off. Inside the block, I activate it with timer.modes.single. Nothing happens, the action method never gets called.

I then tried to make the timer run repeatedly. It fires then, but only if I comment everything in the block. If the block contains code to execute, the whole thing crashes again, this time with a retainCount sent to a forbidden address – this looks like a destructor at work but I cannot convince the debug methods to work in that case. Before I get a hint on what object it tries to deallocate, the crash appears.

For those of you into iOS or OS X declares: How do you handle such cases? I thought maybe creating a custom class with a custom method on its iOS side that is called from the block can help – but that looks like more problems than the good old complicated delegate handling. And how, if a custom iOS method succeeds, would I jump from it into the main thread again to trigger for example a visualization of the analyzed data from a Xojo method?

Best solutions are those you find on your own (with some little help from the LR):
While creating an object in a callback block does not work, you can put declares into it. So something like

[code] declare Function floatChannelData lib AVFoundationLibName selector “floatChannelData” (id as ptr) as ptr
declare Function frameLength lib AVFoundationLibName selector “frameLength” (id as ptr) as UInt32
dim cdata as ptr = floatChannelData (bufferptr)
dim frames as uint32 = frameLength (bufferptr)

system.DebugLog integer (frames).totext +": "+integer(cdata).ToText
dim mblock as new MutableMemoryBlock (cdata, frames)[/code] does work.

Wonderful

Addition:You don’t have to declare the methods in the block. If you want to save time and redundancies, use the new Objective C external declares method and address it from inside the block, wherever they may be located.

And, btw, the external declares are a real time-saver anyway. I love them!

@Antonio: So you ran into this problem too?

What is a block?

@Beatrix: A relatively new Apple way of handling callbacks. Instead of custom delegate classes which were rather complicated in handling, if a method wants to report back you can create a Xojo method and wrap it into a block – check out iOSBlock on the new LR. This block is conserved in memory and called when the appropriate method you assigned it to wants to report back. It can take parameters and even return a result, depending on what the API method expects.

Thanks to Joe, Blocks are available for OS X too. You’ll find a hint to his blocks plugin here on this forum.

And with the help of Jason and hints he received from Joe and Greg, here’s how you can trigger another block on the main queue from inside a block running on a background thread on iOS and OS X.

In theory it should be possible to use a custom method installed on a custom class, but I couldn’t make that run yet. If anyone of you has hints on how to implement dispatch_async_f, please let me know!

Short background: I thought it should be possible to use performSelectorOnMainThread on a custom class method, but that – see above … Instead it is possible to use Grand Central Dispatch with a few tweaks. Here’s the block method that that is called when a LAContextevaluation finishes:

[code]Attributes( hidden ) Sub EvaluationTrigger(success as boolean, errorptr as ptr)
#pragma StackOverflowChecking false
#pragma NilObjectChecking false
// dim sel as ptr = FoundationFrameWork.NSSelectorFromString(“LAContextevaluationresult:”)
// I can get the selector of a custom method, but I have problems with the right definition, it seems. Hints are very welcome

dim block as new AppleBlock(AddressOf EventRaiser) // Eventraiser is another method that takes and returns no parameters.
// AppleBlock is an interface that combines iOSBlock and Objc blocks made by Joes plugin. Use the appropriate one instead!

Declare Function dlopen Lib “/usr/lib/libSystem.dylib” ( path As CString, mode As Int32 ) As Ptr
Declare Function dlsym Lib “/usr/lib/libSystem.dylib” ( handle As Ptr, name As CString ) As ptr
Declare sub dispatch_async lib “/usr/lib/libSystem.dylib” (queue as ptr, block as ptr)

// This is a bit complicated. You can address most of the methods of GCD directly, but not the main thread. It returns a “Selector unknown” exception. Therefore this workaround by the engineers. Can somebody explain me why declaring dispatch_get_main_queue fails while dispatch_queue_create and others do work?

dim sysLib as ptr = dlopen("/usr/lib/libSystem.B.dylib",5)
dim disp as ptr = dlsym(sysLib,"_dispatch_main_q") // However: resolving the mainQueue works this way
lastResult = success // Writing the return values to instance properties because the block can take no parameters.
lastError = errorptr
dispatch_async (disp, block.Handle) // and now call the EventRaiser block on the main queue
End Sub[/code]

EventRaiser in this case is simply

Sub EventRaiser() dim error as AppleError = AppleError.MakefromPtr(lastError) // A Part of iOSLib, factory that returns NIL instead of an empty Object. dim success as Boolean = lastResult RaiseEvent EvaluationResult(success, if (success, "Success", error.localizedDescription)) End Sub