Post

Replies

Boosts

Views

Activity

Reply to Business model change confusion: premium -> freemium (originalAppVersion)
Great! Thanks for the swift replies 🙏. I will clarify that the app in question is iOS/iPadOS, so it feels clear that I should be looking at the build number. Our numbering follows a v.v.v structure for version, whereas the build numbers iterate up from 1 for each version number (as listed in the General pane in Xcode). So I'll make sure the build number and subsequent value to condition against are high enough to not cause issues, e.g. 100. For testing, will Testflight also return the default 1.0 for originalAppVersion, even with external testers? Just curiously, has Apple ever stated why macOS gets a different treatment in this case? Thanks for the in-depth response, @endecotp 👏. Our apps are entirely offline and we don't collect analytics (per Apple's instructions for Made for Kids apps). We could, of course, but we don't gain much insight. This particular app is already 12 years old (almost to the date), and we've only done 10 updates to it in total (only one update in the last 11 years 😀). So any fancier receipt validation is a bit beyond our scope (and unnecessary). Since we've only done one update during this entire decade, I can be absolutely sure that all users are currently on the newest version of the app, 1.4.0 (18). So the business model change isn't that risky in this case. However, we will be converting the rest of our apps to freemium during 2025, so I need to be certain how things work and what strategy to use universally. Just FYI, I also did research originalPurchaseDate, but I saw on some thread that it was not advised. Perhaps it's strictly only meant for subscription IAPs.
Mar ’25
Reply to Trouble decoding array objects via NSKeyedArchiver / NSSecureCoding
Thanks 🙏! I would've never guessed the issue was due to Objective-C types. With some trial and error I was able to get everything working, using decodeArrayOfObjects(ofClass:forKey:) for the simple arrays and decodeObject(of:forKey:) for the nested arrays. It was really important to list out NSNumber and NSArray, where appropriate, to avoid warnings. Essentially this is what I ended up with: let myIntegers = decodeArrayOfObjects(ofClass: NSNumber.self, forKey: "myIntegers") as! [Int] let myEmbedIntegers = decoder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "myEmbedIntegers") as! [[[Int]]] let myBooleans = decoder.decodeArrayOfObjects(ofClass: NSNumber.self, forKey: "myBooleans") as! [Bool] let myEmbedBooleans = decoder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "myEmbedBooleans") as! [[Bool]] And here's the whole amended class: import Foundation class MyData: NSObject, NSSecureCoding { public class var supportsSecureCoding: Bool { true } var id: Int? var isModified: Bool? var modifyDate: Date? var myIntegers: [Int]? var myEmbedIntegers: [[[Int]]]? var myBooleans: [Bool]? var myEmbedBooleans: [[Bool]]? init(_id: Int, _isModified: Bool, _modifyDate: Date, _myIntegers: [Int], _myEmbedIntegers: [[[Int]]], _myBooleans: [Bool], _myEmbedBooleans: [[Bool]]) { id = _id isModified = _isModified modifyDate = _modifyDate myIntegers = _myIntegers myEmbedIntegers = _myEmbedIntegers myBooleans = _myBooleans myEmbedBooleans = _myEmbedBooleans } required convenience init(coder decoder: NSCoder) { let id = decoder.decodeInteger(forKey: "id") let isModified = decoder.decodeBool(forKey: "isModified") let modifyDate = decoder.decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date let myIntegers = decodeArrayOfObjects(ofClass: NSNumber.self, forKey: "myIntegers") as! [Int] let myEmbedIntegers = decoder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "myEmbedIntegers") as! [[[Int]]] let myBooleans = decoder.decodeArrayOfObjects(ofClass: NSNumber.self, forKey: "myBooleans") as! [Bool] let myEmbedBooleans = decoder.decodeObject(of: [NSArray.self, NSNumber.self], forKey: "myEmbedBooleans") as! [[Bool]] self.init(_id: id, _isModified: isModified, _modifyDate: modifyDate, _myIntegers: myIntegers, _myEmbedIntegers: myEmbedIntegers, _myBooleans: myBooleans, _myEmbedBooleans: myEmbedBooleans) } func encode(with coder: NSCoder) { coder.encode(id!, forKey: "id") coder.encode(isModified!, forKey: "isModified") coder.encode(modifyDate!, forKey: "modifyDate") coder.encode(myIntegers!, forKey: "myIntegers") coder.encode(myEmbedIntegers!, forKey: "myEmbedIntegers") coder.encode(myBooleans!, forKey: "myBooleans") coder.encode(myEmbedBooleans!, forKey: "myEmbedBooleans") } } Thanks again for the expert help! I can finally move on to more enjoyable challenges 🤣.
Topic: App & System Services SubTopic: General Tags:
Dec ’24
Reply to Trouble decoding array objects via NSKeyedArchiver / NSSecureCoding
Certainly 👌! The full class is below. Note that this is a mock example, but it does reflect and represent my real class pretty identically. import Foundation class MyData: NSObject, NSSecureCoding { public class var supportsSecureCoding: Bool { true } var id: Int? var isModified: Bool? var modifyDate: Date? var myIntegers: [Int]? var myEmbedIntegers: [[[Int]]]? var myBooleans: [Bool]? var myEmbedBooleans: [[Bool]]? init(_id: Int, _isModified: Bool, _modifyDate: Date, _myIntegers: [Int], _myEmbedIntegers: [[[Int]]], _myBooleans: [Bool], _myEmbedBooleans: [[Bool]]) { id = _id isModified = _isModified modifyDate = _modifyDate myIntegers = _myIntegers myEmbedIntegers = _myEmbedIntegers myBooleans = _myBooleans myEmbedBooleans = _myEmbedBooleans } required convenience init(coder decoder: NSCoder) { let id = decoder.decodeInteger(forKey: "id") let isModified = decoder.decodeBool(forKey: "isModified") let modifyDate = decoder.decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date // Here's where the errors happen: The four lines underneath return nil, which in this case cause crashes due to force unwrapping. let myIntegers = decoder.decodeObject(forKey: "myIntegers") as! [Int] let myEmbedIntegers = decoder.decodeObject(forKey: "myEmbedIntegers") as! [[[Int]]] let myBooleans = decoder.decodeObject(forKey: "myBooleans") as! [Bool] let myEmbedBooleans = decoder.decodeObject(forKey: "myEmbedBooleans") as! [[Bool]] self.init(_id: id, _isModified: isModified, _modifyDate: modifyDate, _myIntegers: myIntegers, _myEmbedIntegers: myEmbedIntegers, _myBooleans: myBooleans, _myEmbedBooleans: myEmbedBooleans) } func encode(with coder: NSCoder) { coder.encode(id!, forKey: "id") coder.encode(isModified!, forKey: "isModified") coder.encode(modifyDate!, forKey: "modifyDate") coder.encode(myIntegers!, forKey: "myIntegers") coder.encode(myEmbedIntegers!, forKey: "myEmbedIntegers") coder.encode(myBooleans!, forKey: "myBooleans") coder.encode(myEmbedBooleans!, forKey: "myEmbedBooleans") } } And they are called using these interfaces: public func loadMyData() -> [MyData]? { do { let data = try Data(contentsOf: myDataPath()) let myData = try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: MyData.self, from: data) return myData } catch {} return nil } public func saveMyData(_ mixData: [MyData]) { do { let data = try NSKeyedArchiver.archivedData(withRootObject: myData, requiringSecureCoding: false) try data.write(to: myDataPath()) } catch {} } private func myDataPath() -> URL { return (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! as URL).appendingPathComponent("MyData.plist") }
Topic: App & System Services SubTopic: General Tags:
Dec ’24