Skip to content

Commit

Permalink
feat(ios): use RiveFile.init(data:) initializer over init(byteArray:)
Browse files Browse the repository at this point in the history
The (likely) most common use of all `RiveFile` initializers is the `RiveFile.init(name:extension:in:loadCdn:customLoader)` initializer available in `RiveFile+Extensions.swift`. This is the initializer called when a (view) model is initialized with a file name. However, some crash reports are showing that this function is crashing within the Swift runtime. I believe that under certain conditions (I've been unable to reproduce, unfortunately), the Swift runtime is crashing when initializing new types, or bridging between Swift and ObjC - I think that minimizing the usage of different types and bridging may fix some of these crashes.

By switching from `init(byteArray:`) to `init(data:)`, we can remove some (extra) initialization, casting, and bridging between Swift and ObjC.

In the current codepath, there is first new initialization within Swift from `Data` to `[UInt8]` (allowed because `Data` conforms to `Sequence`), which then gets bridged to Objective-C as `NSArray<NSNumber*>*`. Notice the additional bridge from `UInt8` to `NSNumber`, since ObjC can't directly handle Swift value types. So, bytes are getting transformed into a new type - from Swift `Data`, to `UInt8`, to an `NSObject` (since `NSNumber` is a reference type, required for use with `NSArray`). From here, _another_  byte array is created, converting each `NSNumber` back into a byte representation.

`init(data:)` is a simpler solution - since `Data` bridges between Swift and ObjC (as NSData), we can then treat the new `NSData.bytes` as a `UInt8*` to pass to the C++ runtime, without having to allocate a new byte array, or bridging between Swift primitives and ObjC types.

## Current Codepath
- Grab the file data as `Data`
- Initialize a new array - the data as `[UInt8]`
- Pass the bytes to `init(byteArray:`)
  - This bridges `[UInt8]` to `NSArray<NSNumber*>*`
- Create a _new_ byte array from the supplied array.
- Into the C++ runtime…
- The file loads

## New Codepath
- Grab the file data as `Data`
- Pass the data to `init(data:)`
  - This bridges `Data` to `NSData`
- Cast `data.bytes` to UInt8*
- Into the C++ runtime…
- The file loads

## Origin

It seems like some of these changes occurred when out-of-band asset support was added to the iOS runtime. There were some new `RiveFile` initializers added; I'm not sure if there was an intention of switching which was used or not.

## Testing

Nearly the entirety of the Example app uses file name initialization. I ran through the entire app and ensured each animation loaded as intended (both in the Simulator and on a physical device).

Diffs=
11cf3a1ad9 feat(ios): use RiveFile.init(data:) initializer over init(byteArray:) (#9020)

Co-authored-by: David Skuza <[email protected]>
  • Loading branch information
dskuza and dskuza committed Feb 12, 2025
1 parent 50b1c98 commit 4eb9c8d
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
aee913977a8ffb8dd7df5bf5eb2951f02344eb48
11cf3a1ad9ecb14f2e88bf8aa5405c188975f90f
23 changes: 15 additions & 8 deletions Source/Utils/RiveFile+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,34 @@ import Foundation
public extension RiveFile {
convenience init(name fileName: String, extension ext: String = ".riv", in bundle: Bundle = .main, loadCdn: Bool=true, customLoader: LoadAsset? = nil) throws {
RiveLogger.log(loadingFromResource: "\(fileName)\(ext)")
let byteArray = RiveFile.getBytes(fileName: fileName, extension: ext, in: bundle)
if (customLoader == nil){
try self.init(byteArray: byteArray, loadCdn: loadCdn)
}else {
try self.init(byteArray: byteArray, loadCdn: loadCdn, customAssetLoader: customLoader!)
let data = RiveFile.getData(fileName: fileName, extension: ext, in: bundle)
if let customLoader = customLoader {
try self.init(data: data, loadCdn: loadCdn, customAssetLoader: customLoader)
} else {
try self.init(data: data, loadCdn: loadCdn)
}
}

static func getBytes(fileName: String, extension ext: String = ".riv", in bundle: Bundle = .main) -> [UInt8] {
let data = getData(fileName: fileName, extension: ext, in: bundle)

// Import the data into a RiveFile
return [UInt8](data)
}

static func getData(fileName: String, extension ext: String = ".riv", in bundle: Bundle = .main) -> Data {
guard let url = bundle.url(forResource: fileName, withExtension: ext) else {
let errorMessage = "Failed to locate \(fileName) in bundle \(bundle)."
RiveLogger.log(file: nil, event: .fatalError(errorMessage))
fatalError(errorMessage)
}

guard let data = try? Data(contentsOf: url) else {
let errorMessage = "Failed to load \(url) from bundle."
RiveLogger.log(file: nil, event: .fatalError(errorMessage))
fatalError()
}

// Import the data into a RiveFile
return [UInt8](data)

return data
}
}

0 comments on commit 4eb9c8d

Please sign in to comment.