3

I get an Ambiguous use of 'children' error in XCode 8.0/Swift 3.0 when trying to send a message to the opaque NSTreeController.arrangedObjects object.

Here is a bare playground showing the use case :

import AppKit

extension NSTreeController {

    func whatever () {
        let k = (self.arrangedObjects as AnyObject).children // error here
    }
}

I try to use AnyObject as a bridge to the underlying ObjC object, which is supposed to be able to get through any method call, I guess.

Xcode signals that it found two candidates that could respond to a "children" message: Foundation.XMLNode and AppKit.NSTreeNode.

Of course the obvious solution (casting to NSTreeNode) is not working because arrangedObjects returns an opaque, proxy object not a real NSTreeNode

Any suggestion on how we're supposed to use NSTreeController.arrangedObjects.children in Swift 3 ?

Bogdan Farca
  • 3,856
  • 26
  • 38
  • Cast `arrangedObjects` to the actual type rather than `AnyObject` (`arrangedObjects as! NSTreeNode`) – vadian Sep 23 '16 at 12:42
  • @vadian : As I said in the question, `arrangedObjects` doesn't return a `NSTreeNode` so the cast is failing at runtime. – Bogdan Farca Sep 23 '16 at 12:53
  • Then cast it to what it actually returns. – vadian Sep 23 '16 at 12:54
  • Not so simple, unfortunately. Actually `arrangedObjects` returns a `_NSControllerTreeProxy`, which is a non-public class. If I try to cast to it then it doesn't even compile (`Use of undeclared type '_NSControllerTreeProxy'`). The proxy object is guaranteed to respond to a couple of messages only, one of them being `children`, and it's not supposed to be used otherwise. – Bogdan Farca Sep 23 '16 at 13:02
  • 1
    I have no experience with NSTreeController and cannot test if it works correctly, but `let k = (self.arrangedObjects as AnyObject).children as [NSTreeNode]?` at least compiles. – Martin R Sep 23 '16 at 13:03
  • @MartinR : worked perfectly !! Please make it an answer so I can mark it as the correct answer. – Bogdan Farca Sep 23 '16 at 13:10

2 Answers2

4

The two candidates for the children property differ by their type:

Foundation.XMLNode:137:14: note: found this candidate
    open var children: [XMLNode]? { get }
             ^
AppKit.NSTreeNode:12:14: note: found this candidate
    open var children: [NSTreeNode]? { get }
             ^

You can resolve the ambiguity by casting the value of the property to the expected type. In your case:

let k = (self.arrangedObjects as AnyObject).children as [NSTreeNode]?
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • That's the only valid solution to my knowledge. I've played a lot with `#selector(children())` and similar, but to no avail. – onekiloparsec Oct 29 '16 at 18:24
  • It seems that, as in Xcode 8.2.1, this doesn't work with the "whole module optimization" compiler option. – LShi Feb 01 '17 at 13:38
  • @LShi I'll double-check it later. – Martin R Feb 01 '17 at 13:42
  • I tried to move the code to an Swift extension of `NSTreeController`, a method called `rootNodes`, still had no luck with the whole module optimization. – LShi Feb 05 '17 at 12:57
  • Wow, thanks for this. Could you tell me how you discovered the two candidates containing `children`? – jeff-h Apr 22 '17 at 02:23
  • @jeff-h: By looking at the full compiler messages in the "Report navigator" in Xcode. – Martin R Apr 22 '17 at 05:21
1

Another solution: Adding an Obj-C category to NSTreeController

.h

#import <Cocoa/Cocoa.h>

@interface NSTreeController (RootNodes_m)

- (NSArray *) rootNodes;

@end

.m

#import "NSTreeController+RootNodes_m.h"

@implementation NSTreeController (RootNodes_m)

- (NSArray *) rootNodes {
    NSObject *  arranged = self.arrangedObjects;

    if ([arranged respondsToSelector: @selector(childNodes)]) {
        return [arranged performSelector:@selector(childNodes)];
    }
    return nil;
}

@end

Now in your code you can use it like this:

return treeController.rootNodes() as? [NSTreeNode]

I had problem with the above answer: The compiler refused to compile when "whole module optimization" was turned on. A swift extension didn't help. I'm using Xcode 8.2.1.

Community
  • 1
  • 1
LShi
  • 1,500
  • 16
  • 29