NSClassFromString from dependencies - swift

I'm having this problem getting the reflection to work from external frameworks as dependencies.
So let say I had two projects, one is a sample app project
SampleApp.xcodeproj
#obj protocol Extension : NSObjectProtocol {
//..Some definitions
}
And another with the framework project that the sample app will be depended on and is a submodule of the sample app project.
Framework.xcodeproj
class SampleClass : NSObject, Extension {
//some definitions
}
In this framework, Extension.swift file, where the Extension protocol is defined, is included in the build as a reference
Now here's the problem on this code
if let customExtension = NSClassFromString("Framework.SampleClass") as? NSObject.Type {
let extensionInstance = customExtension.init()
if extensionInstance is Extension { //The problem here, it returned false at this point
print("It is an Extension")
} else {
print("It is not an Extension")
}
}
I noticed that the Extension protocol defined in the Framework project was defined as Framework.Extension instead of SampleApp.Extension. How would I make it so the SampleClass class is defined as SampleApp.Extension instead of Framework.Extension?

Related

Using Swift protocol default implementation in Kotlin Multiplatform

I try to use in Kotlin Multiplatform XCFramework with Swift code.
I have a protocol with extension for default implementation of this protocol
#objc protocol Greeting {
var something: String { get }
}
extension Greeting {
var something: String {
return "Hello from Swift"
}
}
And in Platform.kt I'm writing
class GreetingImpl: NSObject(), GreetingProtocol {
override fun something(): String {
return (this as GreetingProtocol).something()
}
}
actual class Platform actual constructor() {
val object = GreetingImpl()
val value = object.something() //Application builds but falls here
}
How can I use Swift protocol default implementation in Kotlin Multiplatform?
As far as I can see, there are two main problems:
The extension is missing an #objc annotation. While this is a Swift-side limitation, this prevents Kotlin from providing full interoperability(Kotlin/Native supports no direct interoperability with Swift, only through Objective-C [docs]).
Objective-C does not support protocol default implementation(see this related StackOverflow question).
So, I would say there is no option to use Swift protocol default implementation in Kotlin Multiplatform.

Unable to cast to protocol from framework during test cases

So I have a class that comes from one of our internal frameworks. It is defined as follows:
// This lives within a framework
class ExternalClass: ExternalClassProtocol {
// implementation here
}
// This lives within my test target
class MockExternalClass: ExternalClassProtocol {
// Mock implementation here
}
// This lives within the same external frame work as ExternalClass
protocol ExternalClassProtocol: AnyObject {
// Protocol methods here
}
During my test cases, if I try to cast MockExternalClass as? ExternalClassProtocol, the test case crashes.
However, during live app runtime, there is no problem casting ExternalClass as? ExternalClassProtocol.
Is this an issue with trying to implement a protocol from an external module? Is there a way around this?
The class is being accessed through dependency injection (see below dependency injection implementation). The crash occurs on the resolve function.
If you actually debug to this point, you can see that the mock dependency IS in my dependency root (the services array below).
So I know its not failing to cast due to the dependency being missing.
#propertyWrapper
struct Injected<Value> {
var key: String
var wrappedValue: Value {
get { return Dependency.root.resolve(key: self.key) }
set { Dependency.root.add(key: self.key, newValue) }
}
init(key: String) {
self.key = key
}
}
class Dependency {
static let root = Dependency()
var services: [String : Any] = [:]
func add<T>(key: String, _ service: T) {
services[key] = service
}
func resolve<T>(key: String) -> T {
guard let component: T = services[key] as? T else {
// The test crashes here. It works fine on other mocks that are internal to the project
fatalError("Dependency '\(T.self)' not resolved!")
}
return component
}
func clearDependencies() {
self.services.removeAll()
}
private init() {}
}
In my test case:
#testable import MyProject
import ExternalDependency
class TestCase: XCTestCase {
private var subject: ClassWithService!
private var mockInternalClass: MockInternalClassProtocol!
private var mockExternalClass: MockInternallClassProtocol!
func setUp() {
mockExternalClass = MockExternalClass() // This one crashes when trying to cast to its parent protocol
mockInternalClass = MockInternalClass() // This one does not crash when casting to parent protocol.
Dependency.root.add(key: "internal_class", mockInternalClass)
Dependency.root.add(key: "external_class", mockExternalClass)
}
}
Some things I've tried:
Adding AnyObject to the protocol (this fixed a similar issue for internally defined classes that I mock).
changing mockExternalClass type to be the protocol
changing mockExternalClass type to be the implementation
Aside from one protocol being defined in one of our pods, there is no difference between the external protocol and the one we have defined in our own project.
One thing I have noticed is that the cast does not fail if you set a break point inside one of my test case functions. But if you try the same cast within the Dependency.resolve function it crashes. Which leads me to believe there is an issue with the generics.
Any ideas?

How to get bundle for a struct?

In Swift, you can call
let bundle = NSBundle(forClass: self.dynamicType)
in any class and get the current bundle. If you NSBundle.mainBundle() this will fail getting correct bundle when for example running unit tests.
So how can you get the current bundle for a Swift struct?
The best solution here depends on what you need a bundle for.
Is it to look up resources that exist only in a specific app, framework, or extension bundle that's known to be loaded when the code you're writing runs? In that case you might want to use init(identifier:) instead of dynamically looking up the bundle that defines a certain type.
Beware of "follows the type" bundle lookups. For example, if a framework class Foo uses NSBundle(forClass: self.dynamicType) to load a resource, a subclass of Foo defined by the app loading that framework will end up looking in the app bundle instead of the framework bundle.
If you do need a "follows the type" bundle lookup for a struct (or enum), one workaround that might prove helpful is to define a class as a subtype:
struct Foo {
class Bar {}
static var fooBundle: NSBundle { return NSBundle(forClass: Foo.Bar.self) }
}
Note there's nothing dynamic here, because nothing needs to be — every Foo comes from the same type definition (because structs can't inherit), so its static type matches its dynamic type.
(Admittedly, an NSBundle(forType:) that could handle structs, enums, and protocols might make a nice feature request. Though I imagine it could be tricky to make it handle extensions and everything...)
extension Bundle {
static var current: Bundle {
class __ { }
return Bundle(for: __.self)
}
}
Updated for Swift 3.0+:
struct Foo {
class Bar {}
static var fooBundle: Bundle { return Bundle(for: Foo.Bar.self) }
}
Swift 5
struct Foo {
class Bar {}
static var fooBundle: Bundle { return Bundle(for: Foo.Bar.self) }
}
For Swift Packages, we get the Bundle where the Swift struct is declared anywhere in the same module (target) via:
Bundle.module
However, we need to import a resource in the Package to get this autogenerated.
Bundle.main also works for other projects if you don't have extension targets.
Swift 4+
You can do let bundle = InternalConstants.bundle if you add this struct to your project. A very elegant solution in my opinion.
internal struct InternalConstants {
private class EmptyClass {}
static let bundle = Bundle(for: InternalConstants.EmptyClass.self)
}
Another potential solution (less elegant):
internal struct InternalConstants {
internal static let bundle = Bundle(identifier: "com.hello.world")!
}

Instantiate class from protocol type

I am writing method which takes a type which conforms to a protocol and instantiates an instance of this class. When I build it, the compiler crashes with a segfault. I appreciate that this points to a compiler bug 99% of the time, but I am interested to see if what I'm trying to do is logically correct or am I just throwing absolute nonsense at the compiler and I shouldn't be surprised to see it crash.
Here is my code
protocol CreatableClass {
init()
}
class ExampleClass : CreatableClass {
required init() {
}
}
class ClassCreator {
class func createClass(classType: CreatableClass.Type) -> CreatableClass {
return classType()
}
}
ClassCreator.createClass(ExampleClass.self)
I also tried to rule out passing a Type as a method parameter as being the root of the problem and the following code also crashes the compiler:
protocol CreatableClass {
init()
}
class ExampleClass : CreatableClass {
required init() {
}
}
let classType: CreatableClass.Type = CreatableClass.self
let instance = classType()
So - is this just a straightforward compiler bug and does what I am trying to do seem reasonable, or is there something in my implementation that is wrong?
Edit:
This can be achieved using generics as shown #Antonio below but unfortunately i believe that isn't useful for my application.
The actual non-dumbed down use-case for doing this is something like
protocol CreatableClass {}
protocol AnotherProtocol: class {}
class ClassCreator {
let dictionary: [String : CreatableClass]
func addHandlerForType(type: AnotherProtocol.Type, handler: CreatableClass.Type) {
let className: String = aMethodThatGetsClassNameAsAString(type)
dictionary[className] = handler()
}
required init() {}
}
I usually do that by defining a generic method. Try this:
class func createClass<T: CreatableClass>(classType: T.Type) -> CreatableClass {
return classType()
}
Update
A possible workaround is to pass a closure creating a class instance, rather than passing its type:
class ClassCreator {
class func createClass(instantiator: () -> CreatableClass) -> (CreatableClass, CreatableClass.Type) {
let instance = instantiator()
let classType = instance.dynamicType
return (instance, classType)
}
}
let ret = ClassCreator.createClass { ExampleClass() }
The advantage in this case is that you can store the closure in a dictionary for example, and create more instances on demand by just knowing the key (which is something in 1:1 relationship with the class name).
I used that method in a tiny dependency injection framework I developed months ago, which I realized it works only for #objc-compatible classes only though, making it not usable for my needs...

Array extension called from other module

Array extension methods are unavailable from other modules (for example the XCTest project)
For the sake of simplicity the code below does nothing but it can be used to reproduce the error
import Foundation
extension Array {
mutating func myMethod(toIndex: Int) -> Int! {
// no real code, it's here only to show the problem
return 0
}
}
Calling it from the same module works as expected but from a test class don't
class MyProjectTests: XCTestCase {
func testMoveObjectsFromIndexes1() {
var arr = ["000", "001", "002", "003"]
arr.myMethod(0)
}
}
I think this is correct because the method visibility is restricted to its own module, indeed I obtain the error '[String]' does not have a member named 'myMethod'
I've tried to define the extended method as public as shown below
extension Array {
public mutating func myMethod(toIndex: Int) -> Int! {
// no real code, it's here only to show the problem
return 0
}
}
But I get the compile error 'Extension of generic type 'Array<T>' from a different module cannot provide public declarations'
Until Beta 7 using public solved the problem but under XCode 6.1 (6A1046a) I obtain this error
How can I fix it to run under other modules/projects?
Swift does not allow public extensions currently so you will need to include that extension swift file in your project and put it part of the target.
While not entirely solving the original question, I did find that I could test extension methods in Swift 2.0 (Under XCode 7.0) by importing the module with the #testable directive:
#testable import MyGreatModule