Skip to content

[SR-7576] NSNumber to Float bridging results in crash with Swift 4.1 on iOS9+CABasicAnimation #3710

Open
@swift-ci

Description

@swift-ci
Previous ID SR-7576
Radar None
Original Reporter stonehouse (JIRA User)
Type Bug
Environment

Developer machine:
Xcode 9.3 9E145
Swift 4.1 'swiftlang-902.0.48'
Test device:
iPod Touch
iOS 9.3.5 (13G36)

Additional Detail from JIRA
Votes 0
Component/s Foundation
Labels Bug, 4.1Regression
Assignee None
Priority Medium

md5: 3fb56679807fff4408f2adb44e525772

Issue Description:

Casting Float to NSNumber results in nil in cases where it cannot be cleanly represented.

This seems to be related to SR-5179, and it seems from looking at SR-5228 that this may be an intentional change to avoid lossy conversion. However, I wanted to point out that this behaviour leads to crashes on iOS 9 when using CABasicAnimation and animating CGFloat properties.

I recently discovered that our app started crashing on iOS 9 after the upgrade to Xcode 9.3/Swift 4.1. I've determined the cause is this issue happening somewhere in CABasicAnimation. If I don't specify `fromValue` then it works fine. This crash does not happen in iOS 10 or later.

The problem with this crash is that it results inside CoreAnimation code, so the developer needs to either disable the animation on iOS 9 or sanitise Float values before passing them in.

If I should file a radar let me know, but it seems like the problem is with Swift, because the issue doesn't exist in iOS 10 or later.

The following exception is thrown on an iOS 9 device when attempting to run the animation

Fatal error: Unable to bridge NSNumber to CGFloat: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-902.0.48/src/swift/stdlib/public/SDK/Foundation/NSNumber.swift, line 606

Here is a simplified example of the kind of animation that might crash on iOS 9

import UIKit

class SampleLayer: CALayer {
    var brightness: CGFloat = 0
    
    @objc dynamic var locations: [CGFloat] = [0.0, 1.0] {
    didSet {
        setNeedsDisplay()
    }
    }
    
    func setBrightnessAnimated(_ brightness: CGFloat, duration: CFTimeInterval, timingFunction: CAMediaTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)) {
        let oldBrightness = self.brightness
        let newBrightness = brightness
        
        // On iOS 9 with Swift 4.1 this is crashing due to a bug where CGFloat cannot be cast to NSNumber in some conditions
        if duration > 0 {
            let oldLocations = presentation()?.locations ?? [0, oldBrightness]
            let animation = CABasicAnimation(keyPath: "locations")
            animation.duration = duration
            animation.fromValue = oldLocations
            animation.timingFunction = timingFunction
            removeAnimation(forKey: "brightness")
            add(animation, forKey: "brightness")
        }
        
        self.brightness = newBrightness
    }
}

let testLayer = SampleLayer()
testLayer.setBrightnessAnimated(1, duration: 0.1)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions