Referencing Swift Functions from the Objective-C Runtime


(Jeff Kelley) #1

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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>


(Jacob Bandes-Storch) #2

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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jeff Kelley) #3

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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@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@swift.org <mailto:swift-users@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jacob Bandes-Storch) #4

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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jeff Kelley) #5

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,

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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

to: TestMethod.self)

On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes@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@gmail.com <mailto:slaunchaman@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@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@swift.org <mailto:swift-users@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Jacob Bandes-Storch) #6

"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/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jeff Kelley) #7

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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Nov 21, 2016, at 1:13 AM, Jacob Bandes-Storch <jtbandes@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/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.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@gmail.com <mailto:slaunchaman@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@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@gmail.com <mailto:slaunchaman@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@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@swift.org <mailto:swift-users@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@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Brian Gesiak) #8

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. :slight_smile:

- Brian Gesiak

···

On Mon, Nov 21, 2016 at 10:46 AM, Jeff Kelley via swift-users < swift-users@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 21, 2016, at 1:13 AM, Jacob Bandes-Storch <jtbandes@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/
Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jeff Kelley) #9

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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

···

On Mon, Nov 21, 2016 at 11:06 AM, Brian Gesiak <modocache@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. :slight_smile:

- Brian Gesiak

On Mon, Nov 21, 2016 at 10:46 AM, Jeff Kelley via swift-users < > swift-users@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> |
jeffkelley.org

On Nov 21, 2016, at 1:13 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

On Nov 21, 2016, at 12:08 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

On Nov 19, 2016, at 1:58 AM, Jacob Bandes-Storch <jtbandes@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@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@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan>
> jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users