[swift-users] Referencing Swift Functions from the Objective-C Runtime

Jeff Kelley slaunchaman at gmail.com
Wed Nov 23 21:33:43 CST 2016


Hi Brian!

Thanks for the replies. I haven’t been able to hack on this for a few days
but hopefully I can over the holiday break this weekend.

I’ve been experimenting with building XCTest for watchOS and constructing a
test harness. I’ve gotten it to the point where I can execute the same test
class in iOS and watchOS, with the caveat that I need to enumerate the test
methods like so:

import XCTest


class ClaspTests: XCTestCase {



    static var allTests = {

        return [

            ("testPassingTest", testPassingTest),

            ]

    }()



    func testPassingTest() {

        print("This is my test method.")

        XCTAssertTrue(true)

    }



}

This works fine for Swift tests, but the Swift implementation of XCTestCase
doesn't inherit from NSObject, so I can’t run Objective-C tests in it. I
was hoping to make a new Swift class, exposing it to Objective-C as a false
XCTestCase using @objc(XCTestCase), and then making a subclass of
XCTestCase in Swift to relay its tests back to XCTest.

My goal here is to get as much drop-in support for running existing test
suites in watchOS as possible. I can get there reasonably well with what I
have so far—just by adding the allTests property—but it would be awesome to
enumerate Objective-C classes as well.

Being able to enumerate the test methods of the Swift classes would be a
feather in the cap of this experiment, and would probably be beneficial to
XCTest as a whole, but I’m not sure how I would go about that in a
non-Objective-C-runtime world.


Jeff Kelley

SlaunchaMan at gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Mon, Nov 21, 2016 at 11:06 AM, Brian Gesiak <modocache at gmail.com> wrote:

> I'm curious: what are you hoping to accomplish with corelibs-xctest? If we
> can help you by modifying corelibs-xctest itself, that could be another
> option here. :)
>
> - Brian Gesiak
>
>
> On Mon, Nov 21, 2016 at 10:46 AM, Jeff Kelley via swift-users <
> swift-users at swift.org> wrote:
>
>> The type comes from XCTest. I’m trying to enumerate Objective-C methods
>> in order to use them with XCTest in the open-source Swift version of
>> XCTest, which needs the tests to be supplied as this type:
>>
>> /// This is a compound type used by `XCTMain` to represent tests to run. It combines an
>> /// `XCTestCase` subclass type with the list of test case methods to invoke on the class.
>> /// This type is intended to be produced by the `testCase` helper function.
>> /// - seealso: `testCase`
>> /// - seealso: `XCTMain`
>> public typealias XCTestCaseEntry = (testCaseClass: XCTestCase.Type, allTests: [(String, (XCTestCase) throws -> Void)])
>>
>>
>>
>>
>> Jeff Kelley
>>
>> SlaunchaMan at gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
>> jeffkelley.org
>>
>> On Nov 21, 2016, at 1:13 AM, Jacob Bandes-Storch <jtbandes at gmail.com>
>> wrote:
>>
>> "throws" is the part that's not representable in Obj-C. Why are you using
>> it? If you're interacting with method_getImplementation, you need to think
>> like the Obj-C runtime.
>>
>> https://developer.apple.com/library/content/documentation/Co
>> coa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWor
>> ks.html#//apple_ref/doc/uid/TP40008048-CH104-SW1
>>
>> This works:
>>
>>     typealias DescriptionMethod = @convention(c) (NSObject, Selector) ->
>> NSString
>>
>>     let fn = unsafeBitCast(method_getImplementation(class_getInstanceMethod(NSObject.self,
>> "description")), DescriptionMethod.self)
>>
>>     fn(NSObject(), "description") as String
>>
>>
>> Jacob
>>
>> On Sun, Nov 20, 2016 at 9:41 PM, Jeff Kelley <slaunchaman at gmail.com>
>> wrote:
>>
>>> Still trying on this (copied the code directly, Foo is actually
>>> XCTestCase):
>>>
>>> typealias TestMethod = @convention(c) (XCTestCase) throws -> Void
>>>
>>> This seagulls the compiler with “error: '(XCTestCase) throws -> Void'
>>> is not representable in Objective-C, so it cannot be used with
>>> '@convention(c)’”. I’m trying to use it here:
>>>
>>> let testMethod: IMP = method_getImplementation(method)
>>>
>>> let test: TestMethod = unsafeBitCast(testMethod,
>>>                                      to: TestMethod.self)
>>>
>>> testMethods.append((methodName as String, test))
>>>
>>> If I try to put the type directly in the call to unsafeBitCast(), the
>>> compiler gives me an error:
>>>
>>> Attribute can only be applied to types, not declarations
>>>
>>> Thanks for your suggestions! I hadn’t seen @convention() before.
>>>
>>>
>>> Jeff Kelley
>>>
>>> SlaunchaMan at gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
>>>  | jeffkelley.org
>>>
>>> On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes at gmail.com>
>>> wrote:
>>>
>>> For a function such as bar() above, the type you want to cast the IMP to
>>> would probably be "@convention(c) (Foo, Selector) -> ()".
>>>
>>> On Sun, Nov 20, 2016 at 9:05 PM, Jeff Kelley <slaunchaman at gmail.com>
>>> wrote:
>>>
>>>> Thanks Jacob! I tried using unsafeBitCast, but it fails with the
>>>> following: “fatal error: can't unsafeBitCast between types of different
>>>> sizes”. I considered wrapping every call in a closure that calls
>>>> objc_msgSend(), but alas, that’s not exposed to Swift. I have another
>>>> approach in mind, so I’ll try that next.
>>>>
>>>>
>>>> Jeff Kelley
>>>>
>>>> SlaunchaMan at gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
>>>>  | jeffkelley.org
>>>>
>>>> On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes at gmail.com>
>>>> wrote:
>>>>
>>>> I imagine unsafeBitCast would be the way to go here. But are you
>>>> assuming that all of the instance methods have type "(Foo) throws -> Void"
>>>> ? Or do you somehow want to dynamically use the type information?
>>>>
>>>> Jacob
>>>>
>>>> On Fri, Nov 18, 2016 at 10:37 PM, Jeff Kelley via swift-users <
>>>> swift-users at swift.org> wrote:
>>>>
>>>>> Hello,
>>>>>
>>>>> I’m trying to enumerate the methods of a class in Swift using the
>>>>> Objective-C runtime. Everything is working fine so far, except for the very
>>>>> last step. Suppose I have a Swift class like this:
>>>>>
>>>>> class Foo: SomeSuperclass {
>>>>>
>>>>>     @objc func bar() {
>>>>>         print("Hello, World!")
>>>>>     }
>>>>>
>>>>> }
>>>>>
>>>>> Using the Objective-C runtime methods, I can get the method with
>>>>> class_copyMethodList and then get to the method’s implementation
>>>>> using method_getImplementation. However, what I need to do next is to
>>>>> stick this into a tuple that looks like this:
>>>>>
>>>>> typealias FooEntry = (fooClass: SomeSuperclass.Type, methods: [(String,
>>>>> (Foo) throws -> Void)])
>>>>>
>>>>> For now, the workaround is to make a static variable that returns all
>>>>> of the entries:
>>>>>
>>>>>     static var allEntries = {
>>>>>         return [
>>>>>             ("bar", bar),
>>>>>         ]
>>>>>     }
>>>>>
>>>>> Is there any way to go from the raw IMP that I get back from the
>>>>> runtime to the Swift type so I can construct this list dynamically?
>>>>>
>>>>>
>>>>> Jeff Kelley
>>>>>
>>>>> SlaunchaMan at gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
>>>>>  | jeffkelley.org
>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> swift-users mailing list
>>>>> swift-users at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-users
>>>>>
>>>>>
>>>>
>>>>
>>>
>>>
>>
>>
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20161123/9d2b590a/attachment.html>


More information about the swift-users mailing list