Imagine you have the following Objective-C code:
#import <Foundation/Foundation.h>
@interface Animal : NSObject {
NSString* name;
}
- (void)makeSound;
- (Animal*)initWithName:(NSString*) name;
+ (void)petAnAnimal:(Animal*) animal;
@end
@implementation Animal
- (void)makeSound {
}
+ (void)petAnAnimal:(Animal*) animal {
[animal makeSound];
}
- (Animal*)initWithName:(NSString*) n {
name = n;
return self;
}
@end
@interface Dog : Animal {
NSString* breed;
}
- (Dog*)initWithName:(NSString*) n andBreed:(NSString*) b;
@end
@implementation Dog
- (Dog*)initWithName:(NSString*) n andBreed:(NSString*) b {
self = [self initWithName: n];
breed = b;
return self;
}
- (void)makeSound {
NSLog(@"The Dog %@ (%@) has barked\n", name, breed);
}
@end
Can you interoperate with this from Dart? Yep, you easily can. You take ffigen
and you generate bindings for it, which allow you to write code like this:
final dog = Dog.alloc()
.initWithName_andBreed_(
objc.NSString('Boxer'),
objc.NSString('pug'),
);
dog.makeSound();
Animal.petAnAnimal_(dog);
This prints
2024-05-21 14:09:23.663 hello_world[26434:23013753] The Dog Boxer (pug) has barked
2024-05-21 14:09:23.663 hello_world[26434:23013753] The Dog Boxer (pug) has barked
Now the question is: can you extend Animal
and make things still work?
Yes, you can - though right now you would have to do it manually. Consider for example that you want to write the following:
class Squirrel extends Animal {
final String color;
Squirrel({required String color, required String name});
void makeSound() {
print('Squirrel $name of color $color is making some squirrel sounds!');
}
}
How would you go about it? Well, you have to do something like this:
class Squirrel extends Animal {
static ffi.Pointer<objc.ObjCObject> _registerClass() {
final cls = objc.allocateClassPair(class_Animal, "Squirrel",
extraBytes: ffi.sizeOf<ffi.Pointer>());
objc.addInstanceVariable(
cls,
"color",
ffi.sizeOf<ffi.Pointer>(),
ffi.sizeOf<ffi.Pointer>() == 4 ? 2 : 3,
"^v",
);
_nameVar = objc.class_getInstanceVariable(cls, "name");
_colorVar = objc.class_getInstanceVariable(cls, "color");
final makeSoundMethod =
objc.class_getInstanceMethod(class_Animal, sel_makeSound);
final types = objc.method_getTypeEncoding(makeSoundMethod);
objc.class_addMethod(
cls,
sel_makeSound,
ffi.NativeCallable<
ffi.Void Function(
ffi.Pointer<objc.ObjCObject>,
ffi.Pointer<objc.ObjCSelector>,
)>.isolateLocal(_makeSoundTrampoline)
.nativeFunction
.cast(),
types);
return cls;
}
// Objective-C will invoke this method which will then invoke
// [makeSound] below.
static void _makeSoundTrampoline(
ffi.Pointer<objc.ObjCObject> self,
ffi.Pointer<objc.ObjCSelector> selector,
) {
Squirrel._(self).makeSound();
}
void makeSound() {
print(
'Squirrel $name of color $color is making some squirrel sounds!');
}
factory Squirrel({required String name, required String color}) {
final _ret = objc_msgSend(_class_Squirrel, sel_alloc);
final result = Squirrel._(_ret, retain: false, release: true);
result.initWithName_(objc.NSString(name));
result._color = color;
return result;
}
ffi.Pointer<ffi.Pointer<ffi.Void>> get _colorSlot =>
ffi.Pointer<ffi.Pointer<ffi.Void>>.fromAddress(
pointer.address + _dartDataOffset);
String get _color => objc.unwrapPersistentHandle(_colorSlot.value);
set _color(_SquirrelData data) {
_colorSlot.value = objc.createPersistentHandle(data);
}
objc.NSString get name =>
objc.NSString.castFromPointer(objc.object_getIvar(pointer, _nameVar));
String get color => _color;
}
You can then use this code like so:
final squirrel = Squirrel(name: 'Xyz', color: 'red');
print('Asking animal to make sound (from Dart)');
squirrel.makeSound();
print('Indirectly asking animal to make sound (via Objective-C)');
Animal.petAnAnimal_(squirrel);
And this will work as expected and print:
flutter: Asking animal to make sound (from Dart)
flutter: Squirrel Xyz of color red is making some squirrel sounds!
flutter: Indirectly asking animal to make sound (via Objective-C)
flutter: Squirrel Xyz of color red is making some squirrel sounds!
Is it too manual? Yep. But most of this is boilerplate that can be just generated e.g. via macros.