Generics - check the type

Hi there!


I have a problem while checking the type of a function parameter.

In Playground the following two lines result in "true":


var test = [1.0,2.3,4.0]
print(test is Array<AnyHashable>)


On the other hand, the initializer of my class, defined as:

class Examine<Element: Hashable> {
     public init(withObject object: Any) {
        super.init()
        print(object is Array<AnyHashable>)
        ...
     }
}


called as:

var test:Examine<Double> = Examine<Double>(withObject: [1.0,2.3,4.0])

produces "false"


I don't understand that.

Is it XCode 8 or 9 ?


I do not have the same results as you:


If I copy the class definition in XCode8, I get the following compiler error:

'super' members cannot be referenced in a root class


So I deleted super.init() and it compiles.


Then I test :

var test:Examine<Double> = Examine<Double>(withObject: [1.0,2.3,4.0])

I get true

if I test with

       var test:Examine<Double> = Examine<Double>(withObject: ["test"])

I also get true, which is logic


And if I test in playGround:

var test = [1.0,2.3,4.0]
print(test is Array<AnyHashable>)

I get true


But if var test is not an array, then I get false

Strange:


When I set a breakpoint at line #03 (within the initializer)


public init(withObject object: Any, characterSet: NSCharacterSet?) throws {
     print(object is Array<AnyHashable>)
     guard ((object is NSString) || (object is Array<AnyHashable>)) else {
          os_log("Error creating VTExamine instance", log: log_stat, type: .error)
          throw VTSwiftyStatsError(type: .missingData, file: #file, line: #line, function: #function)
     }
...
}


the debugger command po result is "true":


false <-- result of print(object is Array<AnyHashable>)

(lldb) po object is Array<AnyHashable>
true


I'm calling the initializer with:


test = try Examine<Double>(withObject:  [1.0,2.3,4.0], characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)



???

What is characterSet used for ?


Note : AFAIK, alphanumerics does not contain dot

One use case of the class is to analyze arbitrary strings. The class exists already as part of an matured statistical Objective C-Framework I'v written. As part of that framework the class evaluates statistics (mean, mode, normality, frequencies ... depending on the type of input data) for all objects supported by ObjC (as id). I thought it would be easier to handle different data types by using generics in Swift. Therefore I need to know what type of data are actually used during initialization. But that's seems to be impossible. Unfortunately.

And the fact that dot (.) is not alphanumeric is not a problem for this ?


test = try Examine<Double>(withObject: [1.0,2.3,4.0], characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)

No. Actually the parameter is not evaluated yet. I'm just at the beginning of the translation from ObjC to Swift. Strictly speaking I'm in an "experimental" state...


But I don't understand the difference between the output of the debugger (po) and the print statemant... Basically it seems to be a "framework-issue" because all works well when all class definitions and the "test application" reside in one swift-file...

Every time you post, your code is slightly different, which make it impossible to track what's going on. In an earlier thread about this framework, you posted a different declaration of class "Examine", so we no longer really know:


— What are the access levels?


— Is it a pure Swift type, or an Obj-C class?


— What protocols does the class conform to, or what does it inherit from?


If you have one version of your code that weirdly doesn't work, the issue ought to be solvable from that one version. Posting other/incomplete code fragments does not help.


You also didn't answer Claude's question about which version Xcode (and hence which version of the Swift compiler) you're using.

Hi!


Sorry for the incosinstency of my posts. I'll try to be more specific now:


I'l try to explain what the problem is. I'll attach the full source at the end of this post.


1. I use Xcode Version 8.3.3 (8E3004b), so I'm using Swift 3

2. I've created a Swift-Framework (teststats) project targeting macOS with two classes so far: Examine and StatsError

3. Both classes defined as "public"

4. To test the functionality of the framework I've created a second target within the same Xcode project (command line tool written in swift).

5. The command line tool is linked against the framework.


The class examine is defined as a "generic" class. It is necessary to check the actual type of the used objects within the class Examine at runtime.

The if-statement in "init" evaluates always to "false" when running the command line tool. When the same expression is evaluated via "po object is Array<Element>" in the debugger (after setting a breakpoint), the result is "true".


Sourcecode:

1. main.swift (command line tool target)

//
//  main.swift
//  TestExamine
//

import Foundation
import teststats

var test: Examine<Double>

do {
    test = try Examine<Double>(withObject: [3.14,2.23, 0.575], characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)
    print(test.descriptionString!)
}
catch {
    let e = error as! StatsError
    print(e.type)
}


2. Framework target

2.1. Examine.swift

//
//  Examine.swift
//  teststats
//
import Foundation
import os.log

public class Examine<Element: Hashable>: NSCopying, NSCoding {
    /// A user defined tag to identify the instance
    public var tag: Any?
    /// A human readable description
    public var descriptionString: String?

    public init() {
        initialize()
    }
    /// Initializes a Examine instance using a string or an array<Element>
    /// Desginated Initializer
    public init(withObject object: Any, characterSet: NSCharacterSet?) throws {
        // allow only arrays as 'object'
        if (object is Array<Element>) {
            self.initialize(withArray: object as! NSArray)
        }
        else {
            os_log("Error creating Examine instance", log: log_stat, type: .error)
            throw StatsError(type: .invalidArgument, file: #file, line: #line, function: #function)
        }
    }

    private func initialize(withString string: String, characterSet: NSCharacterSet) {
        initialize()
        return
    }

    private func initialize(withArray array: NSArray) {
        initialize()
        return
    }

    public func encode(with aCoder: NSCoder) {
        aCoder.encode(42, forKey: "magic")
    }

    required public init?(coder aDecoder: NSCoder) {
    }

    public func copy(with zone: NSZone? = nil) -> Any {
        let res: Examine = Examine()
        return res
    }

    fileprivate func initialize() {
        descriptionString = "Description"
        tag = "";
    }

    fileprivate func elementIsType<T>(object: Any!, type: T.Type) -> Bool {
        return object is T
    }
}


2.2. StatsError.swift

//
//  StatsError.swift
//
import Foundation
import os.log

let log_stat = OSLog(subsystem: "local.examine.stats", category: "Examine")

/// Custom error class
public class StatsError: NSError, LocalizedError {
    public var type: ErrorType = .none
    public var line: Int = 0
    public var function: String = ""
    public var file: String = ""
    /// Error codes
    public enum ErrorType: Int {
        /// No error
        case none
        /// Invalid argments
        case invalidArgument
    }

    /// A string describing the error
    override public var localizedDescription: String {
        return "Error description"
    }
    /// A string describing the reason for failure
    override public var localizedFailureReason: String? {
        return "A Reason"
    }

    /// Init
    public init(type: ErrorType, file: String, line: Int, function: String) {
        super.init(domain: "local.examine.stats", code: type.rawValue, userInfo: nil)
        self.type = type
        self.line = line
        self.function = function
        self.file = file
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

I think there's nothing wrong with your code, but the symptoms you see are a side-effect of something you've done to your Xcode project. When I try your code in a regular Cocoa app (i.e. one with a bundle), it works just fine, and the "if" statement isn't false when running the app.


If I start a new project with a command-line tool target instead, then I get errors trying to link or run the tool because there is no Swift library. Browsing the web, I see that using a Swift-based framework with a non-bundled Swift-based executable is a bit of a problem. Apparently you changed settings in the project to both find and link to a Swift library and Foundation module at run time.


I don't know what you did, but I have to assume that the result isn't correct, and is leading to incorrect run-time behavior. Is there a reason you must test this from a command-line tool, and does it still fail if you try from a Cocoa test app?


Unless you can post a copy of the entire project somewhere, it doesn't look like there's anywhere to go with this discussion of the source code.

Thank you for you kind reply!


No, there is no need to test the framework using a command line tool. I thought it would be the most simple way to test the functionality of the framework. The only thing I've changed was "Always Embed Swift Standard Library" (setting to "true") for the framework target.


I agree with you that some project settings are the cause of the strange runtime behaviour. I'll will do some research. It would be very kind, if this post could be followed by you and Claude31 or anyone who's interested.


Thank you for the time you spent.


(Sorry about my bad english.)


Best regards!

Accepted Answer

Ah, OK, I see. I had looked for a build setting for that, but I was looking in the wrong place. When I set that build setting to "Yes", it fails, but there are also error messages in the log saying that some Swift library functions are duplicated.


So, it seems that your targets were linked against different instances of the Swift library. My guess is that the instance being used by the framework isn't initialized properly, which means that some internal Swift library functions don't work.


If you search for this problem on the web, you'll find some suggested solutions (though for earlier Swift versions, so they may not still work). But it would be easier to use a Cocoa app for testing, and avoid all these headaches.

Ok. I'll try that. Thanks so far. It's late here in Germany and I need some sleep.


I'll post an update soon.


Thanks!!!

Generics - check the type
 
 
Q