initial import
This commit is contained in:
commit
e8c63c795e
16 changed files with 632 additions and 0 deletions
5
.envrc
Normal file
5
.envrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
# If we are a computer with nix-shell available, then use that to setup
|
||||
# the build environment with exactly what we need.
|
||||
if has nix; then
|
||||
use nix
|
||||
fi
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
zig-cache
|
||||
zig-out
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "vendor/mach"]
|
||||
path = vendor/mach
|
||||
url = https://github.com/hexops/mach.git
|
28
build.zig
Normal file
28
build.zig
Normal file
|
@ -0,0 +1,28 @@
|
|||
const std = @import("std");
|
||||
const system_sdk = @import("vendor/mach/libs/glfw/system_sdk.zig");
|
||||
|
||||
/// Use this with addPackage in your project.
|
||||
pub const pkg = std.build.Pkg{
|
||||
.name = "objc",
|
||||
.source = .{ .path = thisDir() ++ "/src/main.zig" },
|
||||
};
|
||||
|
||||
pub fn build(b: *std.build.Builder) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const tests = b.addTestExe("objc-test", "src/main.zig");
|
||||
tests.setBuildMode(mode);
|
||||
tests.setTarget(target);
|
||||
tests.linkSystemLibrary("objc");
|
||||
system_sdk.include(b, tests, .{});
|
||||
tests.install();
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
const tests_run = tests.run();
|
||||
test_step.dependOn(&tests_run.step);
|
||||
}
|
||||
|
||||
fn thisDir() []const u8 {
|
||||
return std.fs.path.dirname(@src().file) orelse ".";
|
||||
}
|
111
flake.lock
Normal file
111
flake.lock
Normal file
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1668681692,
|
||||
"narHash": "sha256-Ht91NGdewz8IQLtWZ9LCeNXMSXHUss+9COoqu6JLmXU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "009399224d5e398d03b22badca40a37ac85412a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1659877975,
|
||||
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1672580127,
|
||||
"narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0874168639713f547c05947c76124f78441ea46c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "release-22.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1661151577,
|
||||
"narHash": "sha256-++S0TuJtuz9IpqP8rKktWyHZKpgdyrzDFUXVY07MTRI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "54060e816971276da05970a983487a25810c38a7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"zig": "zig"
|
||||
}
|
||||
},
|
||||
"zig": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1672661306,
|
||||
"narHash": "sha256-PsGj6ynMs4r5BMsPSi9feJe4OxufH0OqJtuHJwT9NBY=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "13d033cc9439685eeb0ea8ef535582ab2302db29",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
46
flake.nix
Normal file
46
flake.nix
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
description = "Objective-C runtime bindings for Zig";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/release-22.05";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
zig.url = "github:mitchellh/zig-overlay";
|
||||
|
||||
# Used for shell.nix
|
||||
flake-compat = {
|
||||
url = github:edolstra/flake-compat;
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
...
|
||||
} @ inputs: let
|
||||
overlays = [
|
||||
# Other overlays
|
||||
(final: prev: {
|
||||
zigpkgs = inputs.zig.packages.${prev.system};
|
||||
})
|
||||
];
|
||||
|
||||
# Our supported systems are the same supported systems as the Zig binaries
|
||||
systems = builtins.attrNames inputs.zig.packages;
|
||||
in
|
||||
flake-utils.lib.eachSystem systems (
|
||||
system: let
|
||||
pkgs = import nixpkgs {inherit overlays system;};
|
||||
in rec {
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
zigpkgs.master
|
||||
];
|
||||
};
|
||||
|
||||
# For compatibility with older versions of the `nix` binary
|
||||
devShell = self.devShells.${system}.default;
|
||||
}
|
||||
);
|
||||
}
|
12
shell.nix
Normal file
12
shell.nix
Normal file
|
@ -0,0 +1,12 @@
|
|||
(import
|
||||
(
|
||||
let
|
||||
flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat;
|
||||
in
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = flake-compat.locked.narHash;
|
||||
}
|
||||
)
|
||||
{src = ./.;})
|
||||
.shellNix
|
16
src/autorelease.zig
Normal file
16
src/autorelease.zig
Normal file
|
@ -0,0 +1,16 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const AutoreleasePool = opaque {
|
||||
pub inline fn init() *AutoreleasePool {
|
||||
return @ptrCast(*AutoreleasePool, objc_autoreleasePoolPush().?);
|
||||
}
|
||||
|
||||
pub inline fn deinit(self: *AutoreleasePool) void {
|
||||
objc_autoreleasePoolPop(self);
|
||||
}
|
||||
};
|
||||
|
||||
// I'm not sure if these are internal or not... they aren't in any headers,
|
||||
// but its how autorelease pools are implemented.
|
||||
extern "c" fn objc_autoreleasePoolPush() ?*anyopaque;
|
||||
extern "c" fn objc_autoreleasePoolPop(?*anyopaque) void;
|
4
src/c.zig
Normal file
4
src/c.zig
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub usingnamespace @cImport({
|
||||
@cInclude("objc/runtime.h");
|
||||
@cInclude("objc/message.h");
|
||||
});
|
74
src/class.zig
Normal file
74
src/class.zig
Normal file
|
@ -0,0 +1,74 @@
|
|||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
const objc = @import("main.zig");
|
||||
const MsgSend = @import("msg_send.zig").MsgSend;
|
||||
|
||||
pub const Class = struct {
|
||||
value: c.Class,
|
||||
|
||||
pub usingnamespace MsgSend(Class);
|
||||
|
||||
/// Returns the class definition of a specified class.
|
||||
pub fn getClass(name: [:0]const u8) ?Class {
|
||||
return Class{
|
||||
.value = c.objc_getClass(name.ptr) orelse return null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns a property with a given name of a given class.
|
||||
pub fn getProperty(self: Class, name: [:0]const u8) ?objc.Property {
|
||||
return objc.Property{
|
||||
.value = c.class_getProperty(self.value, name.ptr) orelse return null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Describes the properties declared by a class. This must be freed.
|
||||
pub fn copyPropertyList(self: Class) []objc.Property {
|
||||
var count: c_uint = undefined;
|
||||
const list = @ptrCast([*c]objc.Property, c.class_copyPropertyList(self.value, &count));
|
||||
if (count == 0) return list[0..0];
|
||||
return list[0..count];
|
||||
}
|
||||
};
|
||||
|
||||
test "getClass" {
|
||||
const testing = std.testing;
|
||||
const NSObject = Class.getClass("NSObject");
|
||||
try testing.expect(NSObject != null);
|
||||
try testing.expect(Class.getClass("NoWay") == null);
|
||||
}
|
||||
|
||||
test "msgSend" {
|
||||
const testing = std.testing;
|
||||
const NSObject = Class.getClass("NSObject").?;
|
||||
|
||||
// Should work with primitives
|
||||
const id = NSObject.msgSend(c.id, objc.Sel.registerName("alloc"), .{});
|
||||
try testing.expect(id != null);
|
||||
{
|
||||
const obj: objc.Object = .{ .value = id };
|
||||
obj.msgSend(void, objc.sel("dealloc"), .{});
|
||||
}
|
||||
|
||||
// Should work with our wrappers
|
||||
const obj = NSObject.msgSend(objc.Object, objc.Sel.registerName("alloc"), .{});
|
||||
try testing.expect(obj.value != null);
|
||||
obj.msgSend(void, objc.sel("dealloc"), .{});
|
||||
}
|
||||
|
||||
test "getProperty" {
|
||||
const testing = std.testing;
|
||||
const NSObject = Class.getClass("NSObject").?;
|
||||
|
||||
try testing.expect(NSObject.getProperty("className") != null);
|
||||
try testing.expect(NSObject.getProperty("nope") == null);
|
||||
}
|
||||
|
||||
test "copyProperyList" {
|
||||
const testing = std.testing;
|
||||
const NSObject = Class.getClass("NSObject").?;
|
||||
|
||||
const list = NSObject.copyPropertyList();
|
||||
defer objc.free(list);
|
||||
try testing.expect(list.len > 20);
|
||||
}
|
18
src/main.zig
Normal file
18
src/main.zig
Normal file
|
@ -0,0 +1,18 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const c = @import("c.zig");
|
||||
pub usingnamespace @import("autorelease.zig");
|
||||
pub usingnamespace @import("class.zig");
|
||||
pub usingnamespace @import("object.zig");
|
||||
pub usingnamespace @import("property.zig");
|
||||
pub usingnamespace @import("sel.zig");
|
||||
|
||||
/// This just calls the C allocator free. Some things need to be freed
|
||||
/// and this is how they can be freed for objc.
|
||||
pub inline fn free(ptr: anytype) void {
|
||||
std.heap.c_allocator.free(ptr);
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
177
src/msg_send.zig
Normal file
177
src/msg_send.zig
Normal file
|
@ -0,0 +1,177 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const c = @import("c.zig");
|
||||
const objc = @import("main.zig");
|
||||
|
||||
/// Returns a struct that implements the msgSend function for type T.
|
||||
/// This is meant to be used with `usingnamespace` to add dispatch
|
||||
/// capability to a type that supports it.
|
||||
pub fn MsgSend(comptime T: type) type {
|
||||
// 1. T should be a struct
|
||||
// 2. T should have a field "value" that can be an "id" (same size)
|
||||
|
||||
return struct {
|
||||
/// Invoke a selector on the target, i.e. an instance method on an
|
||||
/// object or a class method on a class. The args should be a tuple.
|
||||
pub fn msgSend(
|
||||
target: T,
|
||||
comptime Return: type,
|
||||
sel: objc.Sel,
|
||||
args: anytype,
|
||||
) Return {
|
||||
// Our one special-case: If the return type is our own Object
|
||||
// type then we wrap it.
|
||||
const is_object = Return == objc.Object;
|
||||
|
||||
// Our actual return value is an "id" if we are using one of
|
||||
// our built-in types (see above). Otherwise, we trust the caller.
|
||||
const RealReturn = if (is_object) c.id else Return;
|
||||
|
||||
// See objc/message.h. The high-level is that depending on the
|
||||
// target architecture and return type, we must use a different
|
||||
// objc_msgSend function.
|
||||
const msg_send_fn = switch (builtin.target.cpu.arch) {
|
||||
// Aarch64 uses objc_msgSend for everything. Hurray!
|
||||
.aarch64 => &c.objc_msgSend,
|
||||
|
||||
// x86_64 depends on the return type...
|
||||
.x86_64 => switch (@typeInfo(RealReturn)) {
|
||||
// Most types use objc_msgSend
|
||||
inline .Int, .Bool, .Pointer, .Void => &c.objc_msgSend,
|
||||
.Optional => |opt| opt: {
|
||||
assert(@typeInfo(opt.child) == .Pointer);
|
||||
break :opt &c.objc_msgSend;
|
||||
},
|
||||
|
||||
// Structs must use objc_msgSend_stret.
|
||||
// NOTE: This is probably WAY more complicated... we only
|
||||
// call this if the struct is NOT returned as a register.
|
||||
// And that depends on the size of the struct. But I don't
|
||||
// know what the breakpoint actually is for that. This SO
|
||||
// answer says 16 bytes so I'm going to use that but I have
|
||||
// no idea...
|
||||
.Struct => if (@sizeOf(Return) > 16)
|
||||
&c.objc_msgSend_stret
|
||||
else
|
||||
&c.objc_msgSend,
|
||||
|
||||
// Floats use objc_msgSend_fpret for f64 on x86_64,
|
||||
// but normal msgSend for other bit sizes. i386 has
|
||||
// more complex rules but we don't support i386 at the time
|
||||
// of this comment and probably never will since all i386
|
||||
// Apple models are discontinued at this point.
|
||||
.Float => |float| switch (float.bits) {
|
||||
64 => &c.objc_msgSend_fpret,
|
||||
else => &c.objc_msgSend,
|
||||
},
|
||||
|
||||
// Otherwise we log in case we need to add a new case above
|
||||
else => {
|
||||
@compileLog(@typeInfo(RealReturn));
|
||||
@compileError("unsupported return type for objc runtime on x86_64");
|
||||
},
|
||||
},
|
||||
else => @compileError("unsupported objc architecture"),
|
||||
};
|
||||
|
||||
// Build our function type and call it
|
||||
const Fn = MsgSendFn(RealReturn, @TypeOf(target.value), @TypeOf(args));
|
||||
// Due to this stage2 Zig issue[1], this must be var for now.
|
||||
// [1]: https://github.com/ziglang/zig/issues/13598
|
||||
var msg_send_ptr = @ptrCast(*const Fn, msg_send_fn);
|
||||
const result = @call(.auto, msg_send_ptr, .{ target.value, sel.value } ++ args);
|
||||
|
||||
if (!is_object) return result;
|
||||
return .{ .value = result };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// This returns a function body type for `obj_msgSend` that matches
|
||||
/// the given return type, target type, and arguments tuple type.
|
||||
///
|
||||
/// obj_msgSend is a really interesting function, because it doesn't act
|
||||
/// like a typical function. You have to call it with the C ABI as if you're
|
||||
/// calling the true target function, not as a varargs C function. Therefore
|
||||
/// you have to cast obj_msgSend to a function pointer type of the final
|
||||
/// destination function, then call that.
|
||||
///
|
||||
/// Example: you have an ObjC function like this:
|
||||
///
|
||||
/// @implementation Foo
|
||||
/// - (void)log: (float)x { /* stuff */ }
|
||||
///
|
||||
/// If you call it like this, it won't work (you'll get garbage):
|
||||
///
|
||||
/// objc_msgSend(obj, @selector(log:), (float)PI);
|
||||
///
|
||||
/// You have to call it like this:
|
||||
///
|
||||
/// ((void (*)(id, SEL, float))objc_msgSend)(obj, @selector(log:), M_PI);
|
||||
///
|
||||
/// This comptime function returns the function body type that can be used
|
||||
/// to cast and call for the proper C ABI behavior.
|
||||
fn MsgSendFn(
|
||||
comptime Return: type,
|
||||
comptime Target: type,
|
||||
comptime Args: type,
|
||||
) type {
|
||||
const argsInfo = @typeInfo(Args).Struct;
|
||||
assert(argsInfo.is_tuple);
|
||||
|
||||
// Target must always be an "id". Lots of types (Class, Object, etc.)
|
||||
// are an "id" so we just make sure the sizes match for ABI reasons.
|
||||
assert(@sizeOf(Target) == @sizeOf(c.id));
|
||||
|
||||
// Build up our argument types.
|
||||
const Fn = std.builtin.Type.Fn;
|
||||
const params: []Fn.Param = params: {
|
||||
var acc: [argsInfo.fields.len + 2]Fn.Param = undefined;
|
||||
|
||||
// First argument is always the target and selector.
|
||||
acc[0] = .{ .type = Target, .is_generic = false, .is_noalias = false };
|
||||
acc[1] = .{ .type = c.SEL, .is_generic = false, .is_noalias = false };
|
||||
|
||||
// Remaining arguments depend on the args given, in the order given
|
||||
for (argsInfo.fields) |field, i| {
|
||||
acc[i + 2] = .{
|
||||
.type = field.type,
|
||||
.is_generic = false,
|
||||
.is_noalias = false,
|
||||
};
|
||||
}
|
||||
|
||||
break :params &acc;
|
||||
};
|
||||
|
||||
// Copy the alignment of a normal function type so equality works
|
||||
// (mainly for tests, I don't think this has any consequence otherwise)
|
||||
const alignment = @typeInfo(fn () callconv(.C) void).Fn.alignment;
|
||||
|
||||
return @Type(.{
|
||||
.Fn = .{
|
||||
.calling_convention = .C,
|
||||
.alignment = alignment,
|
||||
.is_generic = false,
|
||||
.is_var_args = false,
|
||||
.return_type = Return,
|
||||
.params = params,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test {
|
||||
// https://github.com/ziglang/zig/issues/12360
|
||||
if (true) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(fn (
|
||||
u8,
|
||||
objc.Sel,
|
||||
) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{})));
|
||||
try testing.expectEqual(fn (u8, objc.Sel, u16, u32) callconv(.C) u64, MsgSendFn(u64, u8, @TypeOf(.{
|
||||
@as(u16, 0),
|
||||
@as(u32, 0),
|
||||
})));
|
||||
}
|
70
src/object.zig
Normal file
70
src/object.zig
Normal file
|
@ -0,0 +1,70 @@
|
|||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
const objc = @import("main.zig");
|
||||
const MsgSend = @import("msg_send.zig").MsgSend;
|
||||
|
||||
pub const Object = struct {
|
||||
value: c.id,
|
||||
|
||||
pub usingnamespace MsgSend(Object);
|
||||
|
||||
pub fn fromId(id: anytype) Object {
|
||||
return .{ .value = @ptrCast(c.id, @alignCast(@alignOf(c.id), id)) };
|
||||
}
|
||||
|
||||
/// Returns the class of an object.
|
||||
pub fn getClass(self: Object) ?objc.Class {
|
||||
return objc.Class{
|
||||
.value = c.object_getClass(self.value) orelse return null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the class name of a given object.
|
||||
pub fn getClassName(self: Object) [:0]const u8 {
|
||||
return std.mem.sliceTo(c.object_getClassName(self.value), 0);
|
||||
}
|
||||
|
||||
/// Set a property. This is a helper around getProperty and is
|
||||
/// strictly less performant than doing it manually. Consider doing
|
||||
/// this manually if performance is critical.
|
||||
pub fn setProperty(self: Object, comptime n: [:0]const u8, v: anytype) void {
|
||||
const Class = self.getClass().?;
|
||||
const prop = Class.getProperty(n).?;
|
||||
const setter = if (prop.copyAttributeValue("S")) |val| setter: {
|
||||
defer objc.free(val);
|
||||
break :setter objc.sel(val);
|
||||
} else objc.sel(
|
||||
"set" ++
|
||||
[1]u8{std.ascii.toUpper(n[0])} ++
|
||||
n[1..n.len] ++
|
||||
":",
|
||||
);
|
||||
|
||||
self.msgSend(void, setter, .{v});
|
||||
}
|
||||
|
||||
/// Get a property. This is a helper around Class.getProperty and is
|
||||
/// strictly less performant than doing it manually. Consider doing
|
||||
/// this manually if performance is critical.
|
||||
pub fn getProperty(self: Object, comptime T: type, comptime n: [:0]const u8) T {
|
||||
const Class = self.getClass().?;
|
||||
const prop = Class.getProperty(n).?;
|
||||
const getter = if (prop.copyAttributeValue("G")) |val| getter: {
|
||||
defer objc.free(val);
|
||||
break :getter objc.sel(val);
|
||||
} else objc.sel(n);
|
||||
|
||||
return self.msgSend(T, getter, .{});
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
const NSObject = objc.Class.getClass("NSObject").?;
|
||||
|
||||
// Should work with our wrappers
|
||||
const obj = NSObject.msgSend(objc.Object, objc.Sel.registerName("alloc"), .{});
|
||||
try testing.expect(obj.value != null);
|
||||
try testing.expectEqualStrings("NSObject", obj.getClassName());
|
||||
obj.msgSend(void, objc.sel("dealloc"), .{});
|
||||
}
|
35
src/property.zig
Normal file
35
src/property.zig
Normal file
|
@ -0,0 +1,35 @@
|
|||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
const objc = @import("main.zig");
|
||||
|
||||
pub const Property = extern struct {
|
||||
value: c.objc_property_t,
|
||||
|
||||
/// Returns the name of a property.
|
||||
pub fn getName(self: Property) [:0]const u8 {
|
||||
return std.mem.sliceTo(c.property_getName(self.value), 0);
|
||||
}
|
||||
|
||||
/// Returns the value of a property attribute given the attribute name.
|
||||
pub fn copyAttributeValue(self: Property, attr: [:0]const u8) ?[:0]u8 {
|
||||
return std.mem.sliceTo(
|
||||
c.property_copyAttributeValue(self.value, attr.ptr) orelse return null,
|
||||
0,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
// Critical properties because we ptrCast C pointers to this.
|
||||
const testing = std.testing;
|
||||
try testing.expect(@sizeOf(Property) == @sizeOf(c.objc_property_t));
|
||||
try testing.expect(@alignOf(Property) == @alignOf(c.objc_property_t));
|
||||
}
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
const NSObject = objc.Class.getClass("NSObject").?;
|
||||
|
||||
const prop = NSObject.getProperty("className").?;
|
||||
try testing.expectEqualStrings("className", prop.getName());
|
||||
}
|
30
src/sel.zig
Normal file
30
src/sel.zig
Normal file
|
@ -0,0 +1,30 @@
|
|||
const std = @import("std");
|
||||
const c = @import("c.zig");
|
||||
|
||||
// Shorthand, equivalent to Sel.registerName
|
||||
pub inline fn sel(name: [:0]const u8) Sel {
|
||||
return Sel.registerName(name);
|
||||
}
|
||||
|
||||
pub const Sel = struct {
|
||||
value: c.SEL,
|
||||
|
||||
/// Registers a method with the Objective-C runtime system, maps the
|
||||
/// method name to a selector, and returns the selector value.
|
||||
pub fn registerName(name: [:0]const u8) Sel {
|
||||
return Sel{
|
||||
.value = c.sel_registerName(name.ptr),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the name of the method specified by a given selector.
|
||||
pub fn getName(self: Sel) [:0]const u8 {
|
||||
return std.mem.sliceTo(c.sel_getName(self.value), 0);
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
const testing = std.testing;
|
||||
const s = Sel.registerName("yo");
|
||||
try testing.expectEqualStrings("yo", s.getName());
|
||||
}
|
1
vendor/mach
vendored
Submodule
1
vendor/mach
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 2271c78fd6066fd031ef70ff4900de339dd71629
|
Loading…
Reference in a new issue