Foundation on Linux `CFBooleanGetTypeID`/`CFGetTypeID`


(Ryan Lovelett) #1

I am trying to run some Swift code that relies on Foundation on Linux
(it is thoughtbot's Argo library if anyone wants to know the exact
code).

There is a fairly simple extension to NSNumber that looks like:

import Foundation

extension NSNumber {
  var isBool: Bool {
    return CFBooleanGetTypeID() == CFGetTypeID(self)
  }
}

However it seems that Foundation on Linux has neither
`CFBooleanGetTypeID` or `CFGetTypeID`.

Is this expected?


(Jens Alfke) #2

Those are part of CoreFoundation, the C library on which the Mac/iOS Foundation framework is built. The two have a complicated relationship; Foundation exposes most but not all of the CoreFoundation APIs as Objective-C, but there are still some features you have to drop down to C to use. This is one.

But I think you’re using the in-development Swift Foundation? That’s a different implementation entirely. Presumably it will have some API of its own to determine the type of a number.

—Jens

···

On May 23, 2016, at 12:25 PM, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:

However it seems that Foundation on Linux has neither
`CFBooleanGetTypeID` or `CFGetTypeID`.


(Ryan Lovelett) #3

However it seems that Foundation on Linux has neither
`CFBooleanGetTypeID` or `CFGetTypeID`.

Those are part of CoreFoundation, the C library on which the Mac/iOS
Foundation framework is built. The two have a complicated
relationship; Foundation exposes most but not all of the
CoreFoundation APIs as Objective-C, but there are still some features
you have to drop down to C to use. This is one.

But I think you’re using the in-development Swift Foundation? That’s a
different implementation entirely. Presumably it will have some API of
its own to determine the type of a number.

Yes I'm using the in-development Swift Foundation. From what I can tell
this version is the one that will be used on non-Darwin platforms.

Assuming that such an API does not currently exist on NSNumber, I'm
reasonably confident that it does not, does this mean that a Swift
Evolution thread has to be started to add it?

···

On Mon, May 23, 2016, at 04:33 PM, Jens Alfke wrote:

On May 23, 2016, at 12:25 PM, Ryan Lovelett via swift-users <swift- >> users@swift.org> wrote:

—Jens


(Tony Parker) #4

Hi Ryan,

NSNumber basically exists to hide the underlying number type.

Why not use `func boolValue` instead, if you need a true/false answer?

- Tony

···

On May 23, 2016, at 2:01 PM, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:

On Mon, May 23, 2016, at 04:33 PM, Jens Alfke wrote:

On May 23, 2016, at 12:25 PM, Ryan Lovelett via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

However it seems that Foundation on Linux has neither
`CFBooleanGetTypeID` or `CFGetTypeID`.

Those are part of CoreFoundation, the C library on which the Mac/iOS Foundation framework is built. The two have a complicated relationship; Foundation exposes most but not all of the CoreFoundation APIs as Objective-C, but there are still some features you have to drop down to C to use. This is one.

But I think you’re using the in-development Swift Foundation? That’s a different implementation entirely. Presumably it will have some API of its own to determine the type of a number.

Yes I'm using the in-development Swift Foundation. From what I can tell this version is the one that will be used on non-Darwin platforms.

Assuming that such an API does not currently exist on NSNumber, I'm reasonably confident that it does not, does this mean that a Swift Evolution thread has to be started to add it?

—Jens

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


(Jens Alfke) #5

I don’t remember the original code snippet, but there are cases where you need to know what type of number is stored in an NSNumber. An example is encoding to JSON, where a boolean value should be written as “true” or “false”, not “1” or “0”.

—Jens

···

On May 24, 2016, at 11:03 AM, Tony Parker <anthony.parker@apple.com> wrote:

Why not use `func boolValue` instead, if you need a true/false answer?


(Ryan Lovelett) #6

Hi Ryan,

NSNumber basically exists to hide the underlying number type.

Why not use `func boolValue` instead, if you need a true/false answer?

It's not so much that I need a bool _answer_. Its more that I need to
know what _type_ the NSNumber represents.

I started playing around with this as a stand alone quandary. I tried to
come up with portable code that ran on _both_ Darwin and Glibc (i.e.,
OSX and Linux). And think I'm even more perplexed than I was before.

Ok so the goal. To turn JSON into Swift base types (e.g., Int, Bool,
String, etc...). For example, given the JSON below I'd like to end up
with two variables `bool` and `number` that are both correctly typed
(e.g., `bool: Bool` and `number: Int`).

{
"number": 1234567890,
"bool": false
}

I came up with the following code.

import Foundation

let jsonStr = "{\"number\": 1234567890, \"bool\": false}"
let jsonData = jsonStr.data(using: NSUTF8StringEncoding)
let obj = jsonData.flatMap({ try? NSJSONSerialization.jsonObject(with:
$0) })!
let v = obj as! [String : Any]
let bool = v["bool"] as? Bool
let number = v["number"] as? Bool

This is the closest to a "portable" solution as I could get in an hour
of trying. And to top it off: it isn't portable. The biggest portability
issue is that on Glibc `obj` is `[String : Any]` where on Darwin it is
`[String : AnyObject]`. But the real head scratcher is:

let number = v["number"] as? Bool

On Glibc `number` is `Bool? = nil` :tada:
On Darwin `number` is `Bool? = true` :triumph:

The rub is that the way Glibc worked in that example is what I was
trying to achieve `CFBooleanGetTypeID` and `CFGetTypeID` originally. I
was _expecting_ the Darwin behavior since that has always been the
behavior. I was _not expecting_ the Glibc behavior as this is different
than Darwin. All that having been said: I prefer the Glibc behavior.

I'm not sure if this is expected behavior a bug. Or something else
entirely.

···

On Tue, May 24, 2016, at 02:03 PM, Tony Parker wrote:

- Tony

On May 23, 2016, at 2:01 PM, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:

On Mon, May 23, 2016, at 04:33 PM, Jens Alfke wrote:

On May 23, 2016, at 12:25 PM, Ryan Lovelett via swift-users <swift-users@swift.org> wrote:

However it seems that Foundation on Linux has neither
`CFBooleanGetTypeID` or `CFGetTypeID`.

Those are part of CoreFoundation, the C library on which the Mac/iOS Foundation framework is built. The two have a complicated relationship; Foundation exposes most but not all of the CoreFoundation APIs as Objective-C, but there are still some features you have to drop down to C to use. This is one.

But I think you’re using the in-development Swift Foundation? That’s a different implementation entirely. Presumably it will have some API of its own to determine the type of a number.

Yes I'm using the in-development Swift Foundation. From what I can tell this version is the one that will be used on non-Darwin platforms.

Assuming that such an API does not currently exist on NSNumber, I'm reasonably confident that it does not, does this mean that a Swift Evolution thread has to be started to add it?

—Jens

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


(Tony Parker) #7

Fair enough, and we do use this trick in NSJSONSerialization(.m).

One other possibility is using the objCType property on NSNumber’s superclass NSValue to check. I haven’t seen how much of this is implemented in corelibs-foundation yet. We did redefine Boolean to _Bool in Swift CF, so we’ll have to see if that accidentally resulted in a runtime difference between platforms here that we didn’t consider.

- Tony

···

On May 24, 2016, at 12:49 PM, Jens Alfke <jens@mooseyard.com> wrote:

On May 24, 2016, at 11:03 AM, Tony Parker <anthony.parker@apple.com <mailto:anthony.parker@apple.com>> wrote:

Why not use `func boolValue` instead, if you need a true/false answer?

I don’t remember the original code snippet, but there are cases where you need to know what type of number is stored in an NSNumber. An example is encoding to JSON, where a boolean value should be written as “true” or “false”, not “1” or “0”.

—Jens


(Jens Alfke) #8

One other possibility is using the objCType property on NSNumber’s superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s Foundation. NSNumbers initialized with booleans have objcType “c” because `BOOL` is just a typedef for `char`. So the only way to tell a boolean apart from an 8-bit int is to compare the object pointer against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

    char ctype = self.objCType[0];
    switch (ctype) {
        case 'c': {
            // The only way to tell whether an NSNumber with 'char' type is a boolean is to
            // compare it against the singleton kCFBoolean objects:
            if (self == (id)kCFBooleanTrue)
                return yajl_gen_bool(gen, true);
            else if (self == (id)kCFBooleanFalse)
                return yajl_gen_bool(gen, false);
            else
                return yajl_gen_integer(gen, self.longLongValue);
        }

I haven’t seen how much of this is implemented in corelibs-foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on Github, and the objcType stuff doesn’t seem to be functional. It looks like objcType will only have a value if the object was initialized as an NSValue with the type code passed in, not if the typical NSNumber initializers were used.

—Jens

···

On May 24, 2016, at 12:52 PM, Tony Parker <anthony.parker@apple.com> wrote:


(Tony Parker) #9

Let’s get a bug into JIRA, then we’ll figure out what we should do here.

- Tony

···

On May 24, 2016, at 1:03 PM, Jens Alfke <jens@mooseyard.com> wrote:

On May 24, 2016, at 12:52 PM, Tony Parker <anthony.parker@apple.com <mailto:anthony.parker@apple.com>> wrote:

One other possibility is using the objCType property on NSNumber’s superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s Foundation. NSNumbers initialized with booleans have objcType “c” because `BOOL` is just a typedef for `char`. So the only way to tell a boolean apart from an 8-bit int is to compare the object pointer against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

    char ctype = self.objCType[0];
    switch (ctype) {
        case 'c': {
            // The only way to tell whether an NSNumber with 'char' type is a boolean is to
            // compare it against the singleton kCFBoolean objects:
            if (self == (id)kCFBooleanTrue)
                return yajl_gen_bool(gen, true);
            else if (self == (id)kCFBooleanFalse)
                return yajl_gen_bool(gen, false);
            else
                return yajl_gen_integer(gen, self.longLongValue);
        }

I haven’t seen how much of this is implemented in corelibs-foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on Github, and the objcType stuff doesn’t seem to be functional. It looks like objcType will only have a value if the object was initialized as an NSValue with the type code passed in, not if the typical NSNumber initializers were used.

—Jens


(Jens Alfke) #10

For completeness I should add that there are other cases where you need to know the exact type:

* When formatting a floating-point number, you should use 6 decimal places if it's a float, 16 if it's a double. (If you use all 15 for a float, you end up with results like “0.999999999999999” instead of “0.1”.)
* There’s an edge case in converting an NSNumber to an integer. Most of the time you can use longLongValue. But if the number is an unsigned 64-bit int (objcType == “Q”) you have to use unsignedLongLongValue, otherwise it may overflow and appear negative.

—Jens


(Ryan Lovelett) #11

Let’s get a bug into JIRA, then we’ll figure out what we should
do here.

That's the problem for me. What is the bug? Based on the code example I
provided in this thread. I'm somewhat convinced that the bug is that
Foundation on Linux/Glibc is "broken". In that it does not match the
behavior of Foundation on Darwin, yet it has the desired behavior.

···

On Tue, May 24, 2016, at 04:07 PM, Tony Parker wrote:

- Tony

On May 24, 2016, at 1:03 PM, Jens Alfke <jens@mooseyard.com> wrote:

On May 24, 2016, at 12:52 PM, Tony Parker <anthony.parker@apple.com> >>> wrote:

One other possibility is using the objCType property on NSNumber’s
superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s
Foundation. NSNumbers initialized with booleans have objcType “c”
because `BOOL` is just a typedef for `char`. So the only way to tell
a boolean apart from an 8-bit int is to compare the object pointer
against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

*char* ctype = *self*.*objCType*[];
*switch* (ctype) {
*case* 'c': {
*// The only way to tell whether an NSNumber with 'char' type is a
boolean is to*
*// compare it against the singleton kCFBoolean objects:*
*if* (*self* == (*id*)*kCFBooleanTrue*)
*return* yajl_gen_bool(gen, *true*);
*else* *if* (*self* == (*id*)*kCFBooleanFalse*)
*return* yajl_gen_bool(gen, *false*);
*else*
*return* yajl_gen_integer(gen, *self*.*longLongValue*);
}

I haven’t seen how much of this is implemented in corelibs-
foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on
Github, and the objcType stuff doesn’t seem to be functional. It
looks like objcType will only have a value if the object was
initialized as an NSValue with the type code passed in, not if the
typical NSNumber initializers were used.

—Jens


(Ryan Lovelett) #12

For completeness I should add that there are other cases where you need
to know the exact type:

* When formatting a floating-point number, you should use 6 decimal
places if it's a float, 16 if it's a double. (If you use all 15 for a
float, you end up with results like “0.999999999999999” instead of
“0.1”.)
* There’s an edge case in converting an NSNumber to an integer. Most of
the time you can use longLongValue. But if the number is an unsigned
64-bit int (objcType == “Q”) you have to use unsignedLongLongValue,
otherwise it may overflow and appear negative.

I don't know you think these should be in their own bug or as a part of
https://bugs.swift.org/browse/SR-1610. I will defer to you.

···

On Tue, May 24, 2016, at 04:18 PM, Jens Alfke via swift-users wrote:

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


(Tony Parker) #13

Let’s get a bug into JIRA, then we’ll figure out what we should do here.

That's the problem for me. What is the bug? Based on the code example I provided in this thread. I'm somewhat convinced that the bug is that Foundation on Linux/Glibc is "broken". In that it does not match the behavior of Foundation on Darwin, yet it has the desired behavior.

What I mean is that I want to track the issue in JIRA so we don’t lose it, and so we can find it later when looking for issues that prevent fully cross-platform behavior. We can look into fixing these in several ways, including adding new API in both frameworks, changing implementations, etc.

- Tony

···

On May 24, 2016, at 1:11 PM, Ryan Lovelett <swift-dev@ryan.lovelett.me> wrote:
On Tue, May 24, 2016, at 04:07 PM, Tony Parker wrote:

- Tony

On May 24, 2016, at 1:03 PM, Jens Alfke <jens@mooseyard.com <mailto:jens@mooseyard.com>> wrote:

On May 24, 2016, at 12:52 PM, Tony Parker <anthony.parker@apple.com <mailto:anthony.parker@apple.com>> wrote:

One other possibility is using the objCType property on NSNumber’s superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s Foundation. NSNumbers initialized with booleans have objcType “c” because `BOOL` is just a typedef for `char`. So the only way to tell a boolean apart from an 8-bit int is to compare the object pointer against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

    char ctype = self.objCType[0];
switch (ctype) {
case 'c': {
// The only way to tell whether an NSNumber with 'char' type is a boolean is to
// compare it against the singleton kCFBoolean objects:
if (self == (id)kCFBooleanTrue)
return yajl_gen_bool(gen, true);
else if (self == (id)kCFBooleanFalse)
return yajl_gen_bool(gen, false);
else
return yajl_gen_integer(gen, self.longLongValue);
        }

I haven’t seen how much of this is implemented in corelibs-foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on Github, and the objcType stuff doesn’t seem to be functional. It looks like objcType will only have a value if the object was initialized as an NSValue with the type code passed in, not if the typical NSNumber initializers were used.

—Jens


(Ryan Lovelett) #14

Let’s get a bug into JIRA, then we’ll figure out what we should
do here.

That's the problem for me. What is the bug? Based on the code example
I provided in this thread. I'm somewhat convinced that the bug is
that Foundation on Linux/Glibc is "broken". In that it does not match
the behavior of Foundation on Darwin, yet it has the desired
behavior.

What I mean is that I want to track the issue in JIRA so we don’t lose
it, and so we can find it later when looking for issues that prevent
fully cross-platform behavior. We can look into fixing these in
several ways, including adding new API in both frameworks, changing
implementations, etc.

I have filed a bug https://bugs.swift.org/browse/SR-1610

Obviously please reformat/update accordingly. This was the best I could
come up with.

···

On Tue, May 24, 2016, at 04:19 PM, Tony Parker wrote:

On May 24, 2016, at 1:11 PM, Ryan Lovelett <swift- >> dev@ryan.lovelett.me> wrote:
On Tue, May 24, 2016, at 04:07 PM, Tony Parker wrote:

- Tony

- Tony

On May 24, 2016, at 1:03 PM, Jens Alfke <jens@mooseyard.com> wrote:

On May 24, 2016, at 12:52 PM, Tony Parker >>>>> <anthony.parker@apple.com> wrote:

One other possibility is using the objCType property on NSNumber’s
superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s
Foundation. NSNumbers initialized with booleans have objcType “c”
because `BOOL` is just a typedef for `char`. So the only way to
tell a boolean apart from an 8-bit int is to compare the object
pointer against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

*char* ctype = *self*.*objCType*[];
*switch* (ctype) {
*case* 'c': {
*// The only way to tell whether an NSNumber with 'char' type is a
boolean is to*
*// compare it against the singleton kCFBoolean objects:*
*if* (*self* == (*id*)*kCFBooleanTrue*)
*return* yajl_gen_bool(gen, *true*);
*else* *if* (*self* == (*id*)*kCFBooleanFalse*)
*return* yajl_gen_bool(gen, *false*);
*else*
*return* yajl_gen_integer(gen, *self*.*longLongValue*);
}

I haven’t seen how much of this is implemented in corelibs-
foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on
Github, and the objcType stuff doesn’t seem to be functional. It
looks like objcType will only have a value if the object was
initialized as an NSValue with the type code passed in, not if the
typical NSNumber initializers were used.

—Jens


(Tony Parker) #15

That’s perfect, thanks Ryan.

- Tony

···

On May 24, 2016, at 1:39 PM, Ryan Lovelett <swift-dev@ryan.lovelett.me> wrote:

On Tue, May 24, 2016, at 04:19 PM, Tony Parker wrote:

On May 24, 2016, at 1:11 PM, Ryan Lovelett <swift-dev@ryan.lovelett.me <mailto:swift-dev@ryan.lovelett.me>> wrote:

On Tue, May 24, 2016, at 04:07 PM, Tony Parker wrote:

Let’s get a bug into JIRA, then we’ll figure out what we should do here.

That's the problem for me. What is the bug? Based on the code example I provided in this thread. I'm somewhat convinced that the bug is that Foundation on Linux/Glibc is "broken". In that it does not match the behavior of Foundation on Darwin, yet it has the desired behavior.

What I mean is that I want to track the issue in JIRA so we don’t lose it, and so we can find it later when looking for issues that prevent fully cross-platform behavior. We can look into fixing these in several ways, including adding new API in both frameworks, changing implementations, etc.

I have filed a bug https://bugs.swift.org/browse/SR-1610

Obviously please reformat/update accordingly. This was the best I could come up with.

- Tony

- Tony

On May 24, 2016, at 1:03 PM, Jens Alfke <jens@mooseyard.com <mailto:jens@mooseyard.com>> wrote:

On May 24, 2016, at 12:52 PM, Tony Parker <anthony.parker@apple.com <mailto:anthony.parker@apple.com>> wrote:

One other possibility is using the objCType property on NSNumber’s superclass NSValue to check.

That doesn’t work, unfortunately, at least not with Apple’s Foundation. NSNumbers initialized with booleans have objcType “c” because `BOOL` is just a typedef for `char`. So the only way to tell a boolean apart from an 8-bit int is to compare the object pointer against the singleton true and false objects.

Here’s a snippet of Obj-C code I use for this in my JSON encoder:

    char ctype = self.objCType[0];
switch (ctype) {
case 'c': {
// The only way to tell whether an NSNumber with 'char' type is a boolean is to
// compare it against the singleton kCFBoolean objects:
if (self == (id)kCFBooleanTrue)
return yajl_gen_bool(gen, true);
else if (self == (id)kCFBooleanFalse)
return yajl_gen_bool(gen, false);
else
return yajl_gen_integer(gen, self.longLongValue);
        }

I haven’t seen how much of this is implemented in corelibs-foundation yet.

I took a peek at the Swift NSNumber and NSValue implementations on Github, and the objcType stuff doesn’t seem to be functional. It looks like objcType will only have a value if the object was initialized as an NSValue with the type code passed in, not if the typical NSNumber initializers were used.

—Jens


(Jens Alfke) #16

Your issue seems to be with the implicit conversion of NSNumber to a numeric type like Bool. This only happens on Apple platforms, and I think it’s going away in Swift 3. You should be able to work around it by casting to NSNumber, not Bool.

The problems I described with determining the type of the internal number, seem to be covered already as SR-72 <https://bugs.swift.org/browse/SR-72>.

—Jens

···

On May 24, 2016, at 1:39 PM, Ryan Lovelett <swift-dev@ryan.lovelett.me> wrote:

I have filed a bug https://bugs.swift.org/browse/SR-1610

Obviously please reformat/update accordingly. This was the best I could come up with.