iterator: support iteration using NSFastEnumeration

Implement the consumer side of the `NSFastEnumeration` protocol,
matching the compilation scheme used by Clang for Objective‐C
`for…in` loops.
This commit is contained in:
Emily 2024-11-03 03:57:19 +00:00 committed by Mitchell Hashimoto
parent 1b736ace69
commit e1c328f647
3 changed files with 121 additions and 0 deletions

115
src/iterator.zig Normal file
View file

@ -0,0 +1,115 @@
const std = @import("std");
const objc = @import("main.zig");
// From <Foundation/NSEnumerator.h>.
const NSFastEnumerationState = extern struct {
state: c_ulong,
itemsPtr: ?[*]objc.c.id,
mutationsPtr: ?*c_ulong,
extra: [5]c_ulong,
};
pub const Iterator = struct {
object: objc.Object,
sel: objc.Sel,
state: NSFastEnumerationState,
initial_mutations_value: ?c_ulong,
// Clang compiles `forin` loops with a size 16 buffer.
buffer: [16]objc.c.id,
slice: []objc.c.id,
pub fn init(object: objc.Object) Iterator {
return .{
.object = object,
.sel = objc.sel("countByEnumeratingWithState:objects:count:"),
.state = .{
.state = 0,
.itemsPtr = null,
.mutationsPtr = null,
.extra = [_]c_ulong{0} ** 5,
},
.initial_mutations_value = null,
.buffer = [_]objc.c.id{null} ** 16,
.slice = &[_]objc.c.id{},
};
}
pub fn next(self: *Iterator) ?objc.Object {
if (self.slice.len == 0) {
// Ask for some more objects.
const count = self.object.msgSend(c_ulong, self.sel, .{
&self.state,
&self.buffer,
self.buffer.len,
});
if (self.initial_mutations_value) |value| {
// Call the mutation handler if the mutations value has
// changed since the start of iteration.
if (value != self.state.mutationsPtr.?.*) {
objc.c.objc_enumerationMutation(self.object.value);
}
} else {
self.initial_mutations_value = self.state.mutationsPtr.?.*;
}
self.slice = self.state.itemsPtr.?[0..count];
}
if (self.slice.len > 0) {
const first = self.slice[0];
self.slice = self.slice[1..];
return objc.Object.fromId(first);
} else {
return null;
}
}
};
test "NSArray iteration" {
const testing = std.testing;
const NSArray = objc.getClass("NSMutableArray").?;
const NSNumber = objc.getClass("NSNumber").?;
const array = NSArray.msgSend(
objc.Object,
"arrayWithCapacity:",
.{@as(c_ulong, 10)},
);
defer array.release();
for (0..@as(c_int, 10)) |i| {
const i_number = NSNumber.msgSend(objc.Object, "numberWithInt:", .{i});
defer i_number.release();
array.msgSend(void, "addObject:", .{i_number});
}
var result: c_int = 0;
var iter = array.iterate();
while (iter.next()) |elem| {
result = (result * 10) + elem.getProperty(c_int, "intValue");
}
try testing.expectEqual(123456789, result);
}
test "NSDictionary iteration" {
const testing = std.testing;
const NSMutableDictionary = objc.getClass("NSMutableDictionary").?;
const NSNumber = objc.getClass("NSNumber").?;
const dict = NSMutableDictionary.msgSend(
objc.Object,
"dictionaryWithCapacity:",
.{@as(c_ulong, 100)},
);
defer dict.release();
for (0..@as(c_int, 100)) |i| {
const i_number = NSNumber.msgSend(objc.Object, "numberWithInt:", .{i});
defer i_number.release();
dict.msgSend(void, "setValue:forKey:", .{
i_number,
i_number.getProperty(objc.Object, "stringValue"),
});
}
var result: c_int = 0;
var iter = dict.iterate();
while (iter.next()) |key| {
const value = dict.msgSend(objc.Object, "valueForKey:", .{key});
result += value.getProperty(c_int, "intValue");
}
try testing.expectEqual(4950, result);
}

View file

@ -5,6 +5,7 @@ pub usingnamespace @import("autorelease.zig");
pub usingnamespace @import("block.zig");
pub usingnamespace @import("class.zig");
pub usingnamespace @import("encoding.zig");
pub usingnamespace @import("iterator.zig");
pub usingnamespace @import("object.zig");
pub usingnamespace @import("property.zig");
pub usingnamespace @import("protocol.zig");

View file

@ -2,6 +2,7 @@ const std = @import("std");
const c = @import("c.zig");
const objc = @import("main.zig");
const MsgSend = @import("msg_send.zig").MsgSend;
const Iterator = @import("iterator.zig").Iterator;
/// Object is an instance of a class.
pub const Object = struct {
@ -104,6 +105,10 @@ pub const Object = struct {
pub fn release(self: Object) void {
objc_release(self.value);
}
pub fn iterate(self: Object) Iterator {
return Iterator.init(self);
}
};
extern "c" fn objc_retain(objc.c.id) objc.c.id;