Cuckoo icon indicating copy to clipboard operation
Cuckoo copied to clipboard

Support NSObjectProtocol inheritance for protocol

Open ChaosCoder opened this issue 3 years ago • 3 comments

When having a protocol define inheritance from NSObjectProtocol like this:

protocol TestProtocol: NSObjectProtocol { … }

Cuckoo generates:

class MockTestProtocol: TestProtocol, Cuckoo.ProtocolMock { … }

which produces the error:

cannot declare conformance to 'NSObjectProtocol' in Swift; 'MockTestProtocol' should inherit 'NSObject' instead

So instead, whenever we try to generate a Mock from a protocol that inherits from NSObjectProtocol, a fix would be to let it inherit from NSObject:

class MockTestProtocol: NSObject, TestProtocol, Cuckoo.ProtocolMock { … }

Motivation: When interfacing with Obj-C code there are a lot of NSObjects involved. So this would be super handy to have.

Has someone a hint how this could be implemented? I'm could open a PR but as the generator code base is quite complex, I would need some hints if this is easily doable and potentially where modifications are needed.

ChaosCoder avatar Jun 02 '22 20:06 ChaosCoder

Hey @ChaosCoder, I don't think it's a good idea to make all mock classes inherit from NSObject, but I'd be open to merging a PR that inherits from NSObject if the protocol implements NSObjectProtocol. That way we keep current functionality and extend it to make it easy to use NSObjectProtocol.

As for the PR, you probably need to edit MockTemplate a little to allow for including the NSObject when applicable. To find out when to include NSObject, the tokensWithInheritance in file GenerateMocksCommand contains all the tokens with inheritances, so like a tree you might be able to find out if a given protocol should inherit from NSObject.

I've charted out a not-so-tidy code that I've confirmed to work for the finding the NSObjectProtocol part:

let tokensWithInheritance = options.noInheritance ? tokens : mergeInheritance(tokens)

let protocolDeclarationDictionary: [String: ProtocolDeclaration] = Dictionary(
    uniqueKeysWithValues: tokensWithInheritance.flatMap { file in
        file.declarations.compactMap { token -> (name: String, protocolDeclaration: ProtocolDeclaration)? in
            guard let protocolDeclaration = token as? ProtocolDeclaration else { return nil }
            return (name: protocolDeclaration.name, protocolDeclaration: protocolDeclaration)
        }
    }
)

func containsRecursively(name: String) -> Bool {
    if let protocolDeclaration = protocolDeclarationDictionary[name] {
        if protocolDeclaration.inheritedTypes.contains(where: { $0.name == "NSObjectProtocol" }) {
            return true
        } else {
            return protocolDeclaration.inheritedTypes.contains(where: { inheritanceType in
                containsRecursively(name: inheritanceType.name)
            })
        }
    } else {
        return false
    }
}

let nsObjectProtocols: [ProtocolDeclaration] = protocolDeclarationDictionary.values.reduce(into: []) { accumulator, protocolDeclaration in
    guard containsRecursively(name: protocolDeclaration.name) else { return }
    accumulator.append(protocolDeclaration)
}

MatyasKriz avatar Jun 17 '22 18:06 MatyasKriz

@MatyasKriz I'm willing to work on this issue because I encountered the same issue. @ChaosCoder is also willing to work on this issue, but it's been about 3 months and I will submit a PR.

sk409 avatar Sep 03 '22 10:09 sk409

@MatyasKriz

I'm willing to work on this issue because I encounter the same issue.

@ChaosCoder is also willing to work on this issue, but it's been about 3 months and I will submit a PR.

Thank you, I remember that the code I provided should be sufficient help in implementing this feature. 🙂

MatyasKriz avatar Sep 03 '22 13:09 MatyasKriz