Generic function parameter in Swift points to invalid memory - ios

I'll start with an example setup.
class Parent {
let parentProperty = 1
}
class Child : Parent {
let childProperty = 2
}
class Test {
func testMethod<T : Parent>(data: T) {
// (llbd) print data
}
}
let child = Child()
let test = Test()
// (lldb) print child
test.testMethod(child)
I paused execution on places marked with comment "(lldb) print ..." and executed "print child/data" command in debugger console in Xcode.
Output from said commands are listed below.
// print child
(RingRingTests.Child) $R0 = 0x00007fcb886459a0 {
RingRingTests.Parent = {
parentProperty = 1
}
childProperty = 2
}
// print data
(RingRingTests.Child) $R1 = 0x0000000115a93818 {
RingRingTests.Parent = {
parentProperty = 140512143366480
}
childProperty = 4294967299
}
The child and data variables obviously point to different location in memory. (being that data point to some invalid memory)
This seems to be like the most basic setup for generic function is swift, nevertheless it is failing.
I guess I'm doing something fundamentally wrong. Can somebody point me to the right direction? Thank you.

I've tried to replace your proposed lldb-tests with plain old debug output and all works fine.
class Parent: CustomDebugStringConvertible {
let parentProperty = 1
var debugDescription: String { return "\(parentProperty)" }
}
class Child: Parent {
let childProperty = 2
override var debugDescription: String { return "\(parentProperty), \(childProperty)" }
}
class Test {
func testMethod<T : Parent>(data: T) {
print(data)
}
}
let child = Child()
let test = Test()
print(child) // 1, 2
test.testMethod(child) // 1, 2
Some optimisation can screw lldb-tests, but optimisation should not be done for debug builds.
In other words, my answer is "it's actually all right with your code, but maybe something is wrong with your tests".

Related

How to use a same instance of hiltViewModel among nested composable?

I have a composable function named 'Page' as a basic composable to hold NavHost for my app, please see architecture below:
#Composable
fun Page(viewModel: LdvToolViewModel = hiltViewModel(), scaffoldState: ScaffoldState, navController: NavHostController){
val statusBarMode = viewModel.statusBarUiState
val uiController = rememberSystemUiController()
LaunchedEffect(statusBarMode){
uiController.run {
if(statusBarMode.isDarkContent){
setStatusBarColor(color = Color.White, darkIcons = true)
}else{
setStatusBarColor(color = LdvOrange, darkIcons = false)
}
}
}
val navBuilder: NavGraphBuilder.() -> Unit = {
composable(LdvPages.SEARCHING.name) { SearchUi(viewModel, scaffoldState = scaffoldState) }
composable(LdvPages.ERROR.name) { ErrorUi(viewModel,scaffoldState = scaffoldState) }
composable(LdvPages.PANEL.name) { PanelUi(scaffoldState,viewModel, mBaseViewModel) }
composable(LdvPages.PrivacyPolicy.name){ PrivacyPolicy(scaffoldState)}
composable(LdvPages.TermsOfUse.name){ TermsOfUse(scaffoldState)}
composable(LdvPages.OpenSourceLicense.name){ OpenSourceLicense(scaffoldState)}
composable(LdvPages.DebugPage.name){ DebugPage(viewModel)}
}
val start by derivedStateOf {
if (...){
LdvPages.PANEL.name }else if(...){
LdvPages.ERROR.name
}else{LdvPages.SEARCHING.name}
}
NavHost(navController = navController, startDestination = start, builder = navBuilder)
if(!isNfcEnable){
viewModel.setNfcDisableContent()
ErrorDialog(viewModel = viewModel){
startActivity(Intent(Settings.ACTION_NFC_SETTINGS));
}
}
}
As you can see that 'LdvToolViewModel' has been injected to 'Page' as hiltViewModel. To keep 'LdvToolViewModel' as one instance among lifecycles of nested-composable functions in navBuilder, I have to pass it as parameter to those functions. Is there any better way like I can somehow inject 'LdvToolViewModel' in those functions as hiltViewModel and meanwhile I can still have the injected hiltViewModel as a same instance?
Imagine you have a "HomeGraph", with "Home" as a parent destination, and few destination screens that should share the same ViewModel instance.
First get a NavBackStackEntry, by passing your parent route
val parentEntry: NavBackStackEntry = remember(navBackStackEntry) {
navController.getBackStackEntry(Destination.HomeGraph.route)
}
Then get an instance of a ViewModel by passing the parent NavBackStackEntry
val userViewModel = hiltViewModel<HomeViewModel>(parentEntry)
Also, remember that if you navigate to Destination.HomeGraph.route either from nested navigation or from a different graph a new instance of ViewModel will be created, so if you navigate within a single graph, navigate to startDestination e.g Destination.Home.route - this way you will keep the same ViewModel instance.
I don't thing we have a well-defined ViewModel sharing in compose as we had with a view system e.g by activityViewModels(), but keeping ViewModel state in graphs while user is not accessing them is a bad practice.
You can always pass the ViewModel in one of the graph extension function if necessary.
fun NavGraphBuilder.homeGraph(navController: NavHostController) {
navigation(
startDestination = Destination.Home.route,
route = Destination.HomeGraph.route
) {
composable(Destination.Home.route) { navBackStackEntry ->
val parentEntry = remember(navBackStackEntry) {
navController.getBackStackEntry(Destination.HomeGraph.route)
}
val homeViewModel = hiltViewModel<HomeViewModel>(parentEntry)
HomeRoute(
viewModel = homeViewModel,
onNavigate = { dest ->
navController.navigate(dest.route)
})
}
composable(Destination.Search.route) { navBackStackEntry ->
val parentEntry = remember(navBackStackEntry) {
navController.getBackStackEntry(Destination.HomeGraph.route)
}
val homeViewModel = hiltViewModel<HomeViewModel>(parentEntry)
UserSupportRoute(
viewModel = userViewModel,
onNavigate = { dest ->
navController.navigate(dest.route) {
popUpTo(Destination.Search.route) {
inclusive = true
}
}
})
}
}

How to pass a composable content parameter in data class

I need to pass a compose content parameter in data class. For example a button can render when added into this content.
data class ContentData {
val content: #Composable ()-> Unit
}
This is working but when I get the app background I am getting parcelable exception. How to solve this problem.
One possible explanation I think that will occur related with a parcelable error, happens if you try to pass such object between activities as extras through Intent. Consider not use Composable as parameters in objects. Instead, try to represent the parameters of your Composable with a model which contains the parameters.
// your compose function
#Composable
fun Item(content: String = "Default", padding: Dp){
// ...
}
// Ui Model which contains your data (instead of have a weird composable reference) as a parcelable.
data class ContentData(
val content: String = "Default",
val paddingRaw: Int = 0
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
parcel.readInt()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(content)
parcel.writeInt(paddingRaw)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<ContentData> {
override fun createFromParcel(parcel: Parcel): ContentData {
return ContentData(parcel)
}
override fun newArray(size: Int): Array<ContentData?> {
return arrayOfNulls(size)
}
}
}
// Example if you need the model between activities through the intent as an extra.
val data = ContentData("your content", 11)
val intent = Intent().apply {
putExtra("keyContentData", data)
}
//The way of get and use your model.
val contentData = intent.extras?.get("keyContentData") as ContentData
#Composable
fun ParentComponent(){
// ...
Item(
contentData?.content.orEmpty(),
contentData?.paddingRaw?.dp ?: 0.dp
)
// ...
}

Jetpack Compose: UnsupportedOperationException when adding Entries to MPAndroidCharts dynamically

I try to display live data in an MPAndroidChart hosted in an AndroidView.
I get the graph but an UnsupportedOperationException happens when I call addEntry() to update the graph dynamically. Am I doing something wrong?
You find a demo repo in the comments.
#Composable
fun MyLineChart() {
val mainViewModel = viewModel()
val sensorData = mainViewModel.sensorFlow.collectAsState(SensorModel(0F,0F)).value
AndroidView(modifier = Modifier.fillMaxSize(),
factory = { context ->
val lineChart = LineChart(context)
var entries = listOf(Entry(1f,1f))
val dataSet = LineDataSet(entries, "Label").apply { color = Color.Red.toArgb() }
val lineData = LineData(dataSet)
lineChart.data = lineData
lineChart.invalidate()
lineChart
}){
try {
Log.d("TAG", "MyLineChart: Update --- Current thread id: ${Thread.currentThread()}")
it.data.dataSets[0].addEntry(Entry(sensorData.x, sensorData.y))
it.lineData.notifyDataChanged()
it.notifyDataSetChanged()
it.invalidate()
} catch(ex: Exception) {
Log.d("TAG", "MyLineChart: $ex")
}
}
}
The data is sent to the view via the following ViewModel:
#HiltViewModel
class MainViewModel #Inject constructor(#ApplicationContext var appContext: Context) : ViewModel() {
private var rand: Random = Random(1)
val sensorFlow: Flow<SensorModel> = flow<SensorModel> {
while (true) {
delay(1000L)
Log.d("TAG", "sensorFlow: Current thread id: ${Thread.currentThread()}")
emit(SensorModel(rand.nextFloat(), rand.nextFloat()))
}
}
}
You pass entries to LineDataSet, which is an immutable list.
This library seems to have a pretty bad API, because it doesn't ask for a modifiable list as a parameter, but at the same time it doesn't make it modifiable on its side. This causes you to try to modify the immutable list, which leads to an exception.
Replace
var entries = listOf(Entry(1f,1f))
with
val entries = mutableListOf(Entry(1f,1f))
p.s. I can't advise you another graph library as I haven't worked with any, but I would advise you to look for a library with a better API.
try this code:
// it.data.dataSets[0].addEntry(Entry(sensorData.x, sensorData.y))
val entryList = mutableListOf<Entry>()
entryList.add(Entry(sensorData.x, sensorData.y))
val dataSet = LineDataSet(entryList, "Label").apply {
color = Color.Red.toArgb()
}
it.data = LineData(dataSet)

Error message when defining struct

I am writing a struct in Swift:
struct LevelDictionary {
let kNumberOfSegments: Int = 10
static func loadLevelData() -> NSDictionary {
for var segmentNumber = 0; segmentNumber < kNumberOfSegments; ++segmentNumber {
//My code here
}
return dictionary
}
}
For some reason I get an error on compiling: Instance member 'kNumberOfSegments' cannot be used on type 'LevelDictionary'. What am I missing? I get the same error when I set up LevelDictionary as a Class.
loadLevelData() is a static function which is called on "class" level
LevelDictionary.loadLevelData()
To use kNumberOfSegments in the static function it must be static as well
static let kNumberOfSegments: Int = 10
The direct answer to your question is that you can't use a property in class scope.
A different answer is that you seem to want a static function that returns a dictionary after doing something a certain number of times; which is why you have kNumberOfSegments in the first place. But do you really need to have a variable for something that you aren't going to use again. Another way to do this is to have a default variable in your class method:
struct LevelDictionary {
static func loadLevelData(numberOfSegments: Int = 10) -> NSDictionary {
for segment in 0 ..< numberOfSegments {
// your code here
}
return dictionary
}
}
Now you can call the method without an argument to use the default
let dictionary = LevelDictionary.loadLevelData() // Will use 10 segments
Or you can use a parameter to override the default
let dictianary = LevelDictionary.loadLevelData(20) // Will use 20 segments
You can't use instance member variables/constants inside the static function. (In terms of Objective C you can't use instance member objects inside class function)
Either you should declare the kNumberOfSegments as static or make that function as non-static. I prefer the first option,
struct LevelDictionary
{
static let kNumberOfSegments: Int = 10
static func loadLevelData() -> NSDictionary
{
for var segmentNumber = 0; segmentNumber < kNumberOfSegments; ++segmentNumber
{
//My code here
}
return dictionary
}
}

Terribly Slow migrated Objc to swift code

In an application I've written I have a process that parses a large amount of data from Core-Data and displays it to a graph. While doing this processing I also end up writing the data out to a CSV File. I created a separate class called CSVLine which assists with the creation of the CSV file.
For my test case of 140k recorded my Objective-C code takes aprox 12 seconds to run. After "migrating" the class over to swift It now takes somewhere between 280-360 seconds to run. Obviously I've done something terrible.
Using Instruments I was able to identify the "slow" method and I was wondering if I've done something clear in SWIFT to cause the issue.
Objc
- (void)newLine {
// NSLog(#"Appending %#", self.csvString);
[outData appendData:[self csvData] ];
[self clear];
}
- (void)clear {
// Erase every single value
for (NSUInteger i = 0; i < [values count]; i ++) {
values[i] = #"";
}
}
Swift
func newLine() {
outData.appendData(csvData())
clear()
}
// Clear out the Array
func clear() {
for (var i = 0; i < values.count; i++) {
values[i] = ""
}
}
I'm working with various types of data that are all being written to a single CSV file so there are many blank lines. To accommodate for this I designed this class so that it has an array of keys and an array of values. The keys store the "column" names for the CSV file and values will store either a blank or a value for the index of the key for that data element.
Example:
Keys = [speed,heading,lat,lon]
values might be [200,300,"",""]
or ["","","38.553","25.2256"]
Once I'm done with a line i will write a comma joined list of the values into an internal data structure and clear out the line (erase all the items in the values array). This seems to be where the slowdown is with the swift class. Is there something blatantly "slow" i'm doing when i zero out my array?
Full Swift Class
#objc class CSVLineSwift : NSObject {
// Define Arrays
var keys: [String] = [String]()
var values: [String] = [String]()
var outData : NSMutableData = NSMutableData()
override init() {
}
// Singelton Operator - Thread Safe :: http://code.martinrue.com/posts/the-singleton-pattern-in-swift
class var instance : CSVLineSwift {
// Computed Property
struct Static {
static var instance : CSVLineSwift?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = CSVLineSwift();
}
return Static.instance!
}
// Erase existing Data
func newFile() {
outData = NSMutableData();
outData.appendData(headerData())
}
func csvString() -> String {
return ",".join(values)
}
func csvData() -> NSData {
let string = csvString()
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
return data!
}
func addField(field : String) {
keys.append(field)
values.append("")
}
func setValueForKey(value:String, key:String) {
if let index = find(keys, key) {
values[index] = value
} else {
print( "ERROR -- There was no key: \(key) in the header Array")
}
}
func headerString() -> String {
return ",".join(keys)
}
func headerData() -> NSData {
return headerString().dataUsingEncoding(NSUTF8StringEncoding)!
}
func newLine() {
outData.appendData(csvData())
clear()
}
// Clear out the Array
func clear() {
for (var i = 0; i < values.count; i++) {
values[i] = ""
}
}
func writeToFile(fileName : String) {
outData.writeToFile(fileName, atomically: true)
}
}
Be sure that Swift's optimization level in Build Settings is not -Onone. In my experience, it is orders of magnitude slower than -O. (-O is also the default for 'release', so alternatively you could simply build for release, as already suggested.) As for 'zeroing out' the array, it might be faster (although I do not know) to simply re-initialize the array with a repeated value:
values = [String](count: values.count, repeatedValue: "")
Or if you know you will be appending the new values as you go along, and are not bound to using the indices, you could call:
values.removeAll(keepCapacity: true)
And add the new values with values.append() rather than at indices.
It appears my issues were due to a debug build. In debug build there is no optimization. Once you do a run build the optimization kicks in and things run MUCH MUCH faster....
Similar to the answer posted here: Is Swift really slow at dealing with numbers?

Resources