Swift
Some years ago I wrote an OSX app in C using Carbon because my Hackintosh would only run OSX 10.4 (Tiger). The most difficult part was dealing with the Core Audio API.
I recently decided to port it to Swift to avoid having to attempt
to learn Objective C. A derivation of C that sends selectors to
targets using the syntax [target selector arguments...]
I don’t want
to know about.
Core Audio
Having translated the Core Audio part of the app to Swift, I discovered that part of it refused to work. The same code originally written in C works fine.
var id: AudioDeviceID = kAudioObjectUnknown
// Get the default input device
var inputDeviceAOPA: AudioObjectPropertyAddress =
AudioObjectPropertyAddress(mSelector:
kAudioHardwarePropertyDefaultInputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement:
kAudioObjectPropertyElementMaster)
var size: UInt32 = UInt32(MemoryLayout.size(ofValue: id))
// Get device
status =
AudioObjectGetPropertyData(UInt32(kAudioObjectSystemObject),
&inputDeviceAOPA,
0, nil, &size, &id)
if (status != noErr)
{
// AudioObjectGetPropertyData
NSLog("Error in AudioObjectGetPropertyData: " +
"kAudioHardwarePropertyDefaultInputDevice %d", status)
return status
}
NSLog("System input device %d", id)
// Set the audio unit device
status =
AudioUnitSetProperty(output,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0, &id, size)
if (status != noErr)
{
// AudioUnitSetProperty
NSLog("Error in AudioUnitSetProperty: " +
"kAudioOutputUnitProperty_CurrentDevice " +
AudioUnitErrString(status))
return status
}
Swift version. This fails to set the input device with an error.
AudioDeviceID id;
size = sizeof(id);
// Get the default input device
AudioObjectPropertyAddress inputDeviceAOPA =
{kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
// Get device
status = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&inputDeviceAOPA,
0, nil, &size, &id);
if (status != noErr)
{
// AudioObjectGetPropertyData
NSLog(@"Error in AudioObjectGetPropertyData: "
"kAudioHardwarePropertyDefaultInputDevice %s (%d)",
AudioUnitErrString(status), status);
return status;
}
// Set the audio unit device
status = AudioUnitSetProperty(audioData.output,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0, &id, size);
if (status != noErr)
{
// AudioUnitSetProperty
NSLog(@"Error in AudioUnitSetProperty: "
"kAudioOutputUnitProperty_CurrentDevice %s (%d)",
AudioUnitErrString(status), status);
return status;
}
C version, this works with no error. I asked a question about this on
the coreaudio mailing list, but apart from being picked up on my
coarse use of Swift, since fixed, I didn’t get an answer. So I decided
to leave the audio part of the app in C. I changed the file extension
to .m
so that it is compiled as Objective C which allows a certain
amount of interoperability with Swift. In order for this to work, two
special include files need to be used. <AppName>-Bridging-Header.h
allows Swift to access C data. It can just include the C include
file. Use the switch -import-objc-header
to import this header.
#include "Audio.h"
To allow C to access Swift data requires the use of <AppName>-Swift.h
, which is created by the Swift compiler using the switch -emit-objc-header-path
.
HEADER = Tuner-Bridging-Header.h
SWIFTH = Tuner-Swift.h
SOURCES = AppDelegate.swift ScopeView.swift SpectrumView.swift \
StrobeView.swift DisplayView.swift MeterView.swift \
TunerView.swift Audio.o
SFLAGS = -g -target x86_64-apple-macosx10.9 \
-import-objc-header $(HEADER) -emit-objc-header-path $(SWIFTH) \
-Xlinker -rpath -Xlinker @loader_path/../Frameworks -Xlinker -w
CFLAGS = -g -target x86_64-apple-macosx10.9
Extract from Makefile
.
Enums
I make great use of enums in my apps. They are very useful for defining lots of constants for use in case statements. You can use enums in Swift, but the syntax is more complex, and using them in a switch statement in Swift is a pain. I found it was easier to define the enums in the C include file, and then use them in Swift.
// Checkbox tags
enum
{kZoom = 'Zoom',
kFilt = 'Filt',
kMult = 'Mult',
kFund = 'Fund',
kStrobe = 'Strb',
kDown = 'Down',
kLock = 'Lock',
kNote = 'Note'};
// Reference tags
enum
{kRefText = 'RefT',
kRefStep = 'RefS'};
The enums can then be referenced in Swift.
// buttonClicked
@objc func buttonClicked(sender: NSButton)
{
print("Sender", sender, sender.state)
switch sender.tag
{
case kZoom :
spectrumData.zoom = (sender.state == .on) ? true : false
case kFilt :
audioData.filt = (sender.state == .on) ? true : false
case kMult :
displayData.mult = (sender.state == .on) ? true : false
displayView.needsDisplay = true
case kFund :
audioData.fund = (sender.state == .on) ? true : false
case kStrobe :
strobeData.enable = (sender.state == .on) ? true : false
case kDown :
audioData.down = (sender.state == .on) ? true : false
case kLock :
displayData.lock = (sender.state == .on) ? true : false
displayView.needsDisplay = true
case kNote :
audioData.note = (sender.state == .on) ? true : false
default:
break
}
}
Boolean arrays
The data defined in the include file includes two boolean arrays.
// Audio filter
typedef struct
{
bool note[12];
bool octave[9];
} FilterData;
FilterData filterData;
These arrays appear as tuples in Swift, which is not very useful. I found a workaround using reflection on Stack Overflow, but it produces the wrong result.
// This function compiles and appears to work but produces the
// wrong result for a boolean array. Replaced by ObjC access
// functions.
// arrayFromTuple
func arrayFromTuple<T, R>(tuple: T) -> [R]
{
let reflection = Mirror(reflecting: tuple)
var array: [R] = []
for i in reflection.children
{
array.append(i.value as! R)
}
return array
}
So I wrote some access functions in C as a workaround.
// Boolean array access functions to work around Swift limitations
// getNote
bool getNote(int index)
{
return filterData.note[index];
}
// setNote
void setNote(bool value, int index)
{
filterData.note[index] = value;
}
// getOctave
bool getOctave(int index)
{
return filterData.octave[index];
}
// setOctave
void setOctave(bool value, int index)
{
filterData.octave[index] = value;
}
Sliders
I need to use two sliders in another app I am porting. To define
the orientation of the slider, the docs say to use isVertical
,
macOS 10.0+. But the compiler says macOS 10.12+ and I want to support
10.9. The docs for Objective C say vertical
, but the compiler
doesn’t like that either. So I wrote another access function.
// setVertical
void setVertical(NSSlider *slider, bool value)
{
slider.vertical = value;
}
See Also
- Handling Resizing in Windows
- Build Mac OSX apps using command line tools
- Install OSX El Capitan on a PC (MBR)