Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Graniteman
Nov 16, 2002

I’m new to mac development, and working on an iOS app. Is xcode … unstable? Like, should I have to relaunch to get reproducible results?

I’m having a situation at least once a day where my unit tests will start failing and I’ll get unhandled nils, or access an array out of bounds, within unit tests unrelated to recent changes. If I run the test again, I get exactly the same error. If I restart Xcode everything works again.

And if I step through the code it will be something totally bonkers like
var myIndex = stuff
DoSomeUnrelatedFunction() // NOTE: myIndex is valid here
myArray[myIndex] // wtf myIndex is now -19283272975

If I reboot, that myArray access line will be fine.

Is this an Xcode bug, or am I doing something really hosed up in my code somewhere that’s corrupting memory in my app? As far as I know I’m not doing any funny memory access or unsafe stuff in my code at all. My fear is that I’m blaming Xcode for something that will end up being a problem on users’ devices.

A while ago I did try to nuke my Xcode install due to an unrelated problem and reinstall fresh, but it didn’t end up looking exactly like my original install. I didn’t have this memory instability problem before then, but I don’t know that it started right after, either. Should I try harder to completely Xcode from my mac and reinstall?

Adbot
ADBOT LOVES YOU

Graniteman
Nov 16, 2002

haveblue posted:

It's 90% likely you're doing something hosed up, the Xcode test harness generally does not eat itself for no reason. Have you tried catching the exact moment of corruption with a watchpoint?

I didn't know watchpoints were a thing. I'll definitely try that the next time it happens. The thing that confuses me is that even if I clean the build folder and rebuild, it will still error in the same spot. But if I restart Xcode it will rebuild and run just fine. I'm not an expert developer at all, so I definitely don't know what I don't know about what could go wrong, but that doesn't seem possible to me.

Also, it's never the same problem twice. It's not like it's the same variable getting overwritten. Still, a watchpoint is a great idea, since once it starts happening it's reproducibly in the same spot.

Plorkyeran posted:

Do you have SIP disabled and/or are you downloading Xcode from a sketchy third-party?

SIP is enabled / default. I used the App Store to download Xcode, then had some second thoughts and tried to delete it and all library folders, and then downloaded from apple (linked from https://xcodereleases.com)

Graniteman
Nov 16, 2002

former glory posted:

One thing, though: it does seem to throw a lot of the nice principles of Clean Code out the window.

I’m a coding hobbyist, and working on building my first iOS app, and using SwiftUI for it. The thing I’m frustrated by is that the language feels like it wants you to use protocol driven solutions for creating clean code. However, I feel like protocols just aren’t up to the job the way superclasses and inheritance are. I did a ton of work going down a path of using protocols to get my dependency inversion, only to discover that you can’t use existential types in the same ways that you can use classes.

There’s an accepted swift evolution to try to at least emphasize the risk of using existentials
https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md
And another accepted proposal to reduce some of those limitations
https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md

But I’m not a good enough programmer to see how to work around the constraints of protocols to still implement the Clean Code goals I have in my mind. I keep thinking about it as I work, and not seeing a path forward. If I were using classes I feel like I have enough of a personal experience to find solutions, but with structure and protocols I’m just not seeing it.

Graniteman
Nov 16, 2002

take boat posted:

do you have a sample protocol or use-case you could post?

Two main problems for me were persistence and not knowing about type erased wrappers.

I’m setting up a simulation where the initial conditions were captured in a struct that has dependencies that get composed at runtime based on user decisions.

struct InitialConditions: Codable {
var firstConfig: Protocol1
var secondConfig: Protocol2
var strategyPerformer: Protocol3
etc
}

At run time the user selects an option for each config from the available options (e.g. firstConfig is set to one of the Protocol1 conformant concrete types, and then the concrete type is configured with it’s own concrete-type-specific UI). The final set of chosen and configured options are composed into an InitialConditions that are passed to a simulator.

I was attempting to use Codable to persist InitialConditions, but found out that you just can’t do it. Codable won’t load firstConfig above from a JSON because it can’t tell which concrete type it should instantiate.

And yeah, I was hitting a bunch of issues with self associated types. I have since learned the type-erased wrapper approach, but at the time I was making my own concrete-typed wrappers to solve it with an enum to determine which concrete type was being stored, and optional vars to store each specific concrete type. Since it’s all concrete types, it’s Codable conformant. I didn’t like it, but I couldn’t (and still can’t) see another way to use composition and strategy pattern that wouldn’t throw a fit about self-associated types and that would be all concrete types so that Codable would let me save/load.

Then I started thinking about schema changes after launching the app, and all of my goofy concrete-typed wrappers that would each need their own schema migrations and I said gently caress it and decided to switch everything over to Core Data. So I spent a couple weeks (free time coding guy here) learning about Core Data and migrating to it for persistence.

Maybe there was a better way to get Codable to work, but I still don’t know what it is. In my app, the concrete config instances are meaningless outside of the context of an InitialConditions, so if I did create stores+persistence for each, I’d also have to make sure when an InitialConditions was deleted, I’d delete all associated configs from their stores. Core Data does all that for you, at the cost complexity in other places. The object graph features of Core Data are coming in handy and letting me do some nice stuff I hadn’t originally planned on, but it’s definitely got it’s own problems with writing code that Uncle Bob wouldn’t throw in the trash.

Also, I’m still doing a Core Data version of concrete-typed wrappers.
InitialConditions {
strategyPerformer: StrategyHavers
}
In the Core Data data model, StrategyHavers is my wrapper Entity containing
selectedStrategyType: Int16 (with an enum partner)
One-to-one relationships to Entities that define my old concrete types. E.g.
And a non-CD computed var that returns the right strategy entity for the selectedStrategyType.

So finally at run time in the simulation I can get
var strategyPerformer: StrategyHavers
strategyPerform.currentPerformer returns a configured entity conforming to StrategyHavers.

I don’t have any idea if this is reasonable, but it’s working!

Graniteman
Nov 16, 2002

take boat posted:

Swift code:
extension FirstConfig: FirstProtocol {
    func protocolMethod() {
        switch self { ... }
    }
}

Thanks for this idea. I did look at using enum with associated values to store the selection, but it was going to be clunky to have to always access the values using if let or switch statements. Your approach seems like it would have worked. I’m not used to thinking about enums being as sophisticated as they are in Swift so it just didn’t occur to me to have the actual model logic in enum functions. I didn’t occur to me to have the enum itself conform to the protocols. It actually never occurred to me that enums even could conform to protocols.

What I’m doing is a core data version of the below

Swift code:
Struct FirstType1: FirstProtocol {
…
}

Struct selectedFirstConfig {
Enum configTypes {
Case type1
…
}

Var selectedConfig: configTypes
Var type1: FirstType1?
Var type2: FirstType2?

Func selectedConfig() -> FirstProtocol {
Switch selectedConfig {
Case type1: return self.type1
Case type2: return self.type2
}

Graniteman fucked around with this message at 23:44 on Feb 19, 2022

Graniteman
Nov 16, 2002

I'm getting close to launching my first app, and I'm realizing I may not want to use iCloud / CloudKit for cross-device sync. My understanding is that if I use any iCloud entitlements, I will not be able to sell the app to another company without selling my entire LLC with it. My app is using core data extensively, so cloudkit was the obvious choice for device sync. And it's iOS-only, for what that's worth.

What other mechanism should I look at for cross device sync? I don't want users to have to create any accounts to use the app, and I want something that is privacy-oriented.

Graniteman
Nov 16, 2002

take boat posted:

you can transfer an app on the app store to another organization, though I haven't done it personally

Unfortunately you can’t transfer an app to another developer if the app has, or has ever had, an iCloud entitlement. See this thread:

https://developer.apple.com/forums/thread/81028

Since this is going to be a problem for anybody looking to have a sale as an exit option for their app, I’m just wondering if folks can offer any thoughts for their short list of other solutions.

Graniteman
Nov 16, 2002

take boat posted:

oops sorry. well that's frustrating

there's always creating a one-off developer account for the app, which isn't a totally bad idea anyway if you're considering selling it down the road

I had assumed that I could do the same thing, and was going down that path of creating a separate account. However, I've learned that because I've got an LLC, not an individual account, that is not an option. My understanding is that a business account is tied to your DUNS Number for your account, and in order to sell a business account, you must sell the entire business. Which is not an option since my wife and I do non-software-related consulting under the LLC. So people are saying the only feasible way to do it is to have one LLC for each iCloud-enabled app that you might someday sell. Which is obviously ridiculous.

I can't find it again right now but there's a blog post from some semi-popular indie dev about how when he went to sell his app (icloud enabled) he discovered all this, and ended up selling the entire business to the acquiring company for this reason. I'm new to iOS development, and don't know how common knowledge this iCloud poison pill stuff is.

Anyway, it's not totally essential for my business model that I have cross-device sync, but it's nice to have. Ideally I'd like something that can authenticate with an apple ID or sign-in-with-apple. I might fall back on something like letting them AirDrop data between devices, which would also let them share with family members (desirable for my app). There may just not be an account-creation-free way to do cross device sync base on an appleID. I don't know how it would work...

Graniteman
Nov 16, 2002

I'm working on subscriptions for my first app, using StoreKit2. Everything is working as expected in TestFlight, except family sharing. Does Family Sharing work in TestFlight? Products purchased by family members are not showing up as entitled. I'm not sure if I'm doing something wrong in my code or if Family Sharing relationships don't copy over into the TestFlight environment.

FWIW I'm using StoreKit.Transaction.currentEntitlement(for: productIdentifier) and it returns nil for a product that I know was purchased by another Apple Family member. I'm using this same call for subscriptions and non-consumables.

It sure would be nice if you could mock family sharing using sandbox users...

Edit: I got a response from apple that Family Sharing does not work in TestFlight (or Sandbox, but I knew that already). Seems like this should be documented somewhere, but I can't find it anywhere.

Graniteman fucked around with this message at 21:51 on May 19, 2022

Graniteman
Nov 16, 2002

uncle blog posted:

What's the good and smart way to initialize a ViewModel that depends on other variables within a view?


First, just note that you shouldn’t initialize an ObservedObject. That should be @StateObject if you are instantiating the class inside the view.

There are two problems. One, apple has left us with no approved way of doing this, and we are hoping for a change to _newVM = StateObject(wrappedvalue: VM(params)) at WWDC next week. Second, the only half-good unapproved solution doesn’t work because you are using an EnvironmentObject.

People seem to feel it’s best practice to not have views create their own view models, but instead to pass those in using some mechanism. If you can, it’s probably a better design to have myViewModel instantiated somewhere else and either put into the environment for MyView, or passed in. This sidesteps the whole problem. Find a place where these entities all exist and instantiate MyViewModel there.

Sometimes I’ve found that there wasn’t a good way to do all that, and what I’ve done is made appData an optional inside myViewModel. Then, inside MyView, you can assign it using .onAppear (EnvironmentObjects exist within .onAppear closures). It makes MyViewModel ugly because you need to deal with the optional, but, it’s the only way I know to get something from an @EnvironmentObject into a new object inside a view.

Without the EnvironmentObject (like with your coordinator) you can just create your StateObject inside the view init block. See what I’m doing with the coordinator below. Apple docs say not to do this, but they don’t give us anything else.

code:
class MyViewModel: ObservableObject {
	coordinator: Coordinator
	appData: AppData?

	init(coordinator: Coordinator) { }
}

struct MyView: View {
	@EnvironmentObject var appData: AppData
	let coordinator: Coordinator
	@StateObject var myViewModel: MyViewModel

	init(coordinator: Coordinator) {
		let newViewModel = MyViewModel(coordinator: coordinator)
		// can’t assign newViewModel.appData here because self.appData isn’t populated.
		_myViewModel = StateObject(wrappedvalue:  newViewModel)
	}

	var body: some View {
		Text(“Hello”)
		.onAppear {
			//self.appData is populated here, so you can use it.
			myViewModel.appData = self.appData
		}
	}
}

Graniteman
Nov 16, 2002

101 posted:

Could you not safely do
code:
appData: AppData!
in the model there, and avoid having it be optional since it's always going to be initialised in onCreate?

Yeah, I’ve done that, too. If you can be certain that nothing in your view model initializer will use AppData it should be fine I guess, but it feels a little more risky than a normal force unwrap to me since I don’t understand the exact sequence between a view calling it’s initializer (creating the class with a nil appData), any opportunity for code to run in the viewmodel, and when the .onAppear closure finally runs and populates the optional permanently. Since I don’t understand the risk, I would lean toward leaving it optional instead of permanently force-unwrapped.

Graniteman
Nov 16, 2002

101 posted:

I feel like I'm going crazy.

Every time I try SwiftUI, I hit some dumb but huge hurdle so quickly.

code:
List {
	
	ForEach(items, id: \.self) { item in
		
		Text(item)
	}
	.onDelete { indexSet in
		
		print("Delete")
	}
}
.onTapGesture {
	
	print("Tapped")
}
The tap gesture takes precedence over the onDelete so the onDelete almost never gets triggered or if it rarely does, it's at the same time as the tap gesture.
...

You have the .onTapGesture on the entire list, not on each list element.

101 posted:

That seems to work, thanks. I did try something similar, but the Spacer turned out to be the secret sauce I was missing.

Try using .contentShape(Rectangle()) instead of spacer. That viewmodifier is specifically to change the size / shape of the tap target for a view. I use it to solve this problem whenever it comes up.

Graniteman fucked around with this message at 14:55 on Sep 6, 2022

Graniteman
Nov 16, 2002

toiletbrush posted:

How do I programmatically 'pop' a view in SwiftUI? I've got a List wrapped in a NavigationView, with each item having a navigation link to a 'detail' view that has a single editable text view. I want the detail view to pop back to the list view in the onCommit callback of the text field, but I can't figure out how to do it :(

That’s because apple didn’t build swiftui to let you do it. There are a number of ways to work around it, but you aren’t missing something obvious. Apparently in iOS 16 there is a new way to do navigation (NavigationView is deprecated!) which solves these problems and there is easy / good support for programmatic navigation. But prior to iOS 16 you have to roll your own navigation solution.

I don’t have a good solution to recommend to you, so I’ll just say you can google what’s out there.

Graniteman
Nov 16, 2002

toiletbrush posted:

yeah ive found a few solutions but they seem weirdly complicated. am I going against some UI guidelines, or is it just another thing swiftui doesn't support for no obvious reason? Having an edit view dismiss itself when you've finished editing doesn't seem particularly controversial

No, you are understanding correctly. There is no programmatic way to pop the navigation stack in swiftUI prior to iOS 16. After iOS 16 it’s totally reworked, presumably to solve the problems you are seeing.

You can easily have an edit view dismiss itself if you are using modal (sheet, fullScreenCover, etc). Binding to a @State isDisplayed bool in the calling UI is probably the standard swiftui approach, or using the @Environment dismiss() stuff if the modal supports it. What you can’t easily do is pop back multiple levels of a navigation view, or arbitrarily jump around with deep links in your navigation stack, unless you do the complicated stuff (or only support iOS 16).

Graniteman
Nov 16, 2002

dupersaurus posted:

Any suggestions for guides for learning iOS dev when you're already a programmer

Maybe check out the Stanford CS193p course. It may be too basic for you, but it’s the most advanced intro I’ve personally seen. It is the full Stanford course on iOS development. The students are expected to have completed their first year of classes, and know 3-4 languages, and have understanding of basic programming topics.

All of the other online “learn swift” stuff I’ve seen is targeted at people who don’t know how to program, and the material is very hand-holdy. The Stanford course assumes you are smart and are going to do some homework and figure stuff out on your own.

Graniteman
Nov 16, 2002

uncle blog posted:

I'm trying to mutate a struct instance in an array contained within a ViewModel class, but I'm getting the error "Cannot use mutating member on immutable value: function call returns immutable value".

Any tips for what I'm doing wrong?

This code
code:
imagesViewModel.getImage(with: id)?.updateState(.failed)
is the same as this code:
code:
let temp: IdentifiableImage? = imagesViewModel.getImage(with: id)?
temp.updateState(.failed)
The chaining doesn't pass back to the view model. Written out like this it should be obvious that it can't work. You need something like this:

code:
public func updateState(for imageId: String, with state: UploadState) {
       guard let index = images.firstIndex(where: { $0.id == imageId }) else { return } // throw
       var updatedImage = images[index]
       updatedImage.updateState(state)
       images[index] = updatedImage
    }
Now your call site would look like this:

code:
imagedViewModel.updateState(for: id, with: .failed)

Graniteman
Nov 16, 2002

SaTaMaS posted:

In Core Data I have the main ViewContext for everything view related, and all changes are copied to a background context where changes are synced with the server in order to not clog the main thread, and to keep syncing decoupled from view state.

With SwiftData they gave us a type of class called a ModelActor for doing background work. It has its own special ModelContext which you use for that background work. I’ve made a couple in my current project and find it works fine, and when you save that background context you will get changes on the main actor that show up in the UI. I can point you to some example code when I’m at my desk in a day or so if you can’t find a good example. The syntax changed some during the betas fyi.

You should definitely using a model actor to perform your background data access and sync. I don’t offhand know the best way to use that to monitor for changes in the model context to trigger that background work. In Core Data I would subscribe to a notification. Maybe you can still do that since it’s using Core Data internally?

Side note: I really hate that their official recommendation for solving some SwiftData problems is “implement and maintain a compete parallel data storage stack in Core Data and use that to solve your problem.”

Graniteman
Nov 16, 2002

SaTaMaS posted:

Some sample code would be awesome, but monitoring for changes is key, in Core Data I can listen for the ContextDidSaveNotification, copy the changes from the viewContext to the syncContext, then look at the objects associated with the notification, and for each endpoint I have there is an associated NSPredicate that when true means the object needs to be synchronized. For SwiftData I know there is a new query language but I'm not sure how to run queries on the set of changed objects from a save notification like I can in Core Data.

As far as I can tell there is no mechanism like getting ContextDidSaveNotification and being able to filter a list of transactions. It's a really big problem! It means I don't see a way to do deduplication when you sync between devices. With Core Data if I created a duplicate entry on a second device synced by CloudKit I could monitor the incoming transactions and deduplicate. Apple has a whole support article on the topic. With SwiftData if you sync between devices with CloudKit I don't see any way to do it. The hobby project I'm working on now uses SwiftData but I've got CloudKit sync turned off until that problem is solved. It's aggravating because getting free cross device sync without a login is a big part of the value proposition of using Apple's first-party SDKs. If you find a way to handle this please message me or post here because god drat...

Anyway, to use ModelActor classes for background work with SwiftData you need to create a class like this:
code:
final actor MyFancyModelActor: ModelActor {
    // MARK: - ModelActor conformance requirements
    nonisolated let modelContainer: ModelContainer
    nonisolated let modelExecutor: any ModelExecutor

    // You need to pass in the same ModelContainer definition you use throughout the rest of your app. I keep a PersistenceController.shared.container for this purpose.
    init(modelContainer: ModelContainer) {
        self.modelContainer = modelContainer
        let modelContext = ModelContext(modelContainer)
        self.modelExecutor = DefaultSerialModelExecutor(modelContext: modelContext)
    }
    
    // MARK: - Your custom code for the class
    func doAThing(toObjectWithID objectID: PersistentIdentifier) {
        // ModelActors have this subscript defined where you can get an object from its context
        guard let theObjectIWant: MyModelObjectType = self[objectID, as: MyModelObjectType.self] else { return }

        // ModelActors have a protocol-provided self.modelContext which runs isolated to the thread / actor. You can freely use it within ModelActor functions.
        if self.modelContext.hasChanges {
            // do something
        }
    }
}
The call site works like this
code:
let myModelActor = MyFancyModelActor(modelContainer: PersistenceController.shared.container)
Task {
    await myModelActor.doAThing(toObjectWithID: blahblahblah)
}
You can use this setup to freely do work on any thread you want without getting crashes. SwiftData is less sensitive to thread problems than Core Data, but it still won't let you pass ModelObjects across threads without crashes.

Graniteman fucked around with this message at 17:42 on Oct 18, 2023

Graniteman
Nov 16, 2002

It's an optional flag to enable NSPersistentStoreRemoteChangeNotificationPostOptionKey with Core Data, and I haven't checked to see if they set that flag. The problem is that to merge changes or generally handle the contents of that change notification, you have to query the core data context for transaction history (as NSPersistentHistoryResult), which you can parse to get NSManagedObjectID (the Core Data identifier), which you can ultimately use to get a list of all of the Core Data NSManagedObjects that were changed in that notification. If you are using a SwiftData project, you don't have an easy way to convert those NSManagedObjects to SwiftData ModelObjects. Which points back to my whining that they suggest you reimplement your persistence stack in Core Data if you want to do that anything they haven't implemented. If you do implement a Core Data stack, then you can convert your transaction history into Core Data objects, which you can then convert to Swift Data objects. Or just operate directly on the Core Data objects, which of course I would just do that and not use Swift Data at all in the app if it was critical to process transactions.

I may be missing something here, but I don't see a way to parse transaction history like you can with Core Data. With Core Data your context can request a NSPersistentHistoryTransaction.fetchRequest, which doesn't seem to exist for SwiftData. I'm genuinely no expert in Core Data or Swift Data, but I've shipped a couple of indie apps using Core Data with this cloudkit de-dupe stuff, and am working on a Swift Data app where I haven't found a work around. Apple's docs suck, and there hasn't been enough time for StackOverflow to solve all of my problems yet.

And yeah you are right that the actor method doesn't need to be async. I'll edit that post's code.

Graniteman fucked around with this message at 17:45 on Oct 18, 2023

Adbot
ADBOT LOVES YOU

Graniteman
Nov 16, 2002

pokeyman posted:

I think you just saved future-me a few hours of frustration so thank you for writing that out. Wish I had any other ideas :(

Sure I’m happy to help. If you want any more example code for any of that Core Data stuff I’m happy to put up gists or whatever.

For Swift Data I feel like we are just in that early framework period where they just haven’t implemented everything yet. Apple devs continue to say that duplicate Feedbacks count as votes internally when they allocate resources so please submit feedbacks that ask for the missing features you need the most.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply