Skip to content

Commit 3610863

Browse files
committed
Add proper support for structs and inout params in blocks
Also allows passing strings to Instance.methodForSelector, and fixes an infinite recursion issue when trying to call a nonexistant method (e.g. -description or -debugDescription) on an instance of a class which implements neither -description or -debugDescription.
1 parent 175dcdd commit 3610863

File tree

5 files changed

+121
-3
lines changed

5 files changed

+121
-3
lines changed

examples/range.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const objc = require('../');
2+
const ref = require('ref-napi');
3+
4+
objc.import('AppKit');
5+
6+
const {
7+
NSString,
8+
NSMutableString,
9+
NSAttributedString,
10+
NSMutableAttributedString,
11+
NSLinkAttributeName
12+
} = objc;
13+
14+
const { id, NSRange } = objc.types;
15+
16+
17+
let attrString = NSMutableAttributedString.new();
18+
attrString.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(
19+
'abc', {
20+
[NSLinkAttributeName]: 'https://example.com'
21+
}
22+
));
23+
attrString.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(
24+
'def', {
25+
[NSLinkAttributeName]: 'https://example.de'
26+
}
27+
));
28+
29+
console.log(attrString);
30+
31+
32+
let range = NSRange.new(0, attrString.length());
33+
console.log(range);
34+
35+
36+
let block = new objc.Block((arg0, arg1, arg2) => {
37+
const attrs = objc.wrap(arg0);
38+
const range = arg1;
39+
console.log(`block called w/ args attrs: ${attrs}, range: ${[arg1.location, arg1.length]}, stop: ${arg2.deref()}`);
40+
//ref.set(arg2, 0, 1); // uncomment this to have it stop iterating after the first range.
41+
return;
42+
}, objc.types.void, [id, NSRange, ref.refType(objc.types.char)]);
43+
44+
attrString.enumerateAttributesInRange_options_usingBlock_(range, 0, block);
45+

src/instance.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class Instance {
2828
}
2929

3030
methodForSelector(selector) {
31+
if (typeof selector === 'string') {
32+
selector = new Selector(selector);
33+
}
3134
if (this.type === 'class') {
3235
return runtime.class_getClassMethod(this.__ptr, selector.__ptr);
3336
}
@@ -135,7 +138,24 @@ class Instance {
135138
if (this.__ptr === null || this.__ptr.isNull()) {
136139
return '(null)';
137140
}
138-
return this.call('debugDescription').UTF8String(); // eslint-disable-line new-cap
141+
if (this.respondsToSelector('description')) {
142+
return this.call('description').UTF8String(); // eslint-disable-line new-cap
143+
} else if (this.respondsToSelector('debugDescription')) {
144+
return this.call('debugDescription').UTF8String(); // eslint-disable-line new-cap
145+
} else if (this.respondsToSelector('class')) {
146+
let classname = runtime.class_getName(this.call('class'));
147+
return `<${classname} ${this.__ptr}>`;
148+
} else if (this.type === 'instance') {
149+
let classname;
150+
if (this.type === 'instance') {
151+
classname = runtime.class_getName(runtime.object_getClass(this.__ptr));
152+
} else {
153+
classname = runtime.class_getName(this.__ptr);
154+
}
155+
return `<${classname} ${this.__ptr}>`;
156+
} else {
157+
return `objc.Instance(type: ${this.type}, __ptr: ${this.__ptr})`;
158+
}
139159
}
140160

141161
toString() {
@@ -157,6 +177,7 @@ class Instance {
157177
}
158178

159179
static alloc() {
180+
// TODO is this zero-initialised? (probably will otherwise try to fuck up rhe refcount, right?)
160181
return new InstanceProxy(new Instance(ref.alloc(inoutType)));
161182
}
162183

src/structs.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const ref = require('ref-napi')
22
const struct = require('ref-struct-di')(ref);
33

44
const CompoundInit = Symbol('structs.CompoundInit');
5+
const IsStructSymbol = Symbol();
56
const structs = {};
67

78
const createStructInitializer = (name, StructType) => {
@@ -31,7 +32,7 @@ const createStructInitializer = (name, StructType) => {
3132

3233
return retval;
3334
};
34-
35+
StructType[IsStructSymbol] = true;
3536
return StructType;
3637
};
3738

@@ -49,5 +50,7 @@ module.exports = {
4950
return createStructInitializer(name, type);
5051
},
5152

52-
getStructType: name => structs[name]
53+
getStructType: name => structs[name],
54+
55+
isStructFn: obj => obj[IsStructSymbol] === true
5356
};

src/type-encodings.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ module.exports = {
254254
} else if (typeof type === 'object') {
255255
return type;
256256
}
257+
if (structs.isStructFn(type)) {
258+
return type;
259+
}
257260

258261
throw new TypeError(`Unable to coerce type from ${type}`);
259262
}

test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,52 @@ test('Blocks: objc.Block passes for full block type encoding', t => {
636636
});
637637

638638

639+
test('Blocks: Block w/ struct parameter and inout BOOL*', t => {
640+
const ref = require('ref-napi');
641+
objc.import('AppKit');
642+
const { NSAttributedString, NSMutableAttributedString, NSLinkAttributeName } = objc;
643+
const { id, NSRange } = objc.types;
644+
let attrString = NSMutableAttributedString.new();
645+
attrString.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(
646+
'abc', {
647+
[NSLinkAttributeName]: 'https://example.com'
648+
}
649+
));
650+
attrString.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(
651+
'def', {
652+
[NSLinkAttributeName]: 'https://example.de'
653+
}
654+
));
655+
attrString.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(
656+
'ghi', {
657+
[NSLinkAttributeName]: 'https://example.net'
658+
}
659+
));
660+
t.is(attrString.description().UTF8String(), `abc{
661+
NSLink = "https://example.com";
662+
}def{
663+
NSLink = "https://example.de";
664+
}ghi{
665+
NSLink = "https://example.net";
666+
}`);
667+
668+
let blockArgs = [];
669+
let block = new objc.Block((arg0, arg1, arg2) => {
670+
const attrs = objc.wrap(arg0);
671+
const range = arg1;
672+
blockArgs.push([objc.js(attrs), {location: range.location, length: range.length}]);
673+
if (range.location === 3 && range.length === 3) {
674+
ref.set(arg2, 0, 1); // uncomment this to have it stop iterating after the first range.
675+
}
676+
return;
677+
}, objc.types.void, [id, NSRange, ref.refType(objc.types.char)]);
678+
attrString.enumerateAttributesInRange_options_usingBlock_(NSRange.new(0, attrString.length()), 0, block);
679+
t.deepEqual(blockArgs, [ // We expect the third pair to be missing, which tells us that stopping the iteration early by setting the *stop param worked.
680+
[{NSLink: 'https://example.com'}, { location: 0, length: 3 }],
681+
[{NSLink: 'https://example.de' }, { location: 3, length: 3 }]
682+
]);
683+
})
684+
639685
/*
640686
Iterators
641687
*/

0 commit comments

Comments
 (0)