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:
parent
1b736ace69
commit
e1c328f647
3 changed files with 121 additions and 0 deletions
115
src/iterator.zig
Normal file
115
src/iterator.zig
Normal 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 `for…in` 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);
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue