I have encountered a memory "leak" that I don't understand. I have a caching class whose purpose is to limit the number of instances of heavy Data items. Even when it stores NO instances of those items, they are being retained somehow. If they are from a background thread, I believe they are released upon completion of the task. But, of course, as demonstrated by the code, on the main thread they persist.
This occurs on iPadOS 14.8, iPadOS 15 and MacCatalyst.
What am I missing or don't understand?
class DataCache {
var id: String
var data: Data? {
get {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
return try! Data(contentsOf: url)
}
set {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
try! newValue!.write(to: url)
}
}
init(id: String, data: Data) {
self.id = id
self.data = data
}
}
class Item {
static var selection = [Item]()
static var items = [String:Item]()
var id: String = UUID().uuidString
var itemData: DataCache
init() {
itemData = DataCache(id: id,
data: Data(String(repeating: "dummy", count: 4_000_000).utf8)
)
}
required init(_ other: Item) {
self.itemData = DataCache(id: self.id, data: other.itemData.data!)
}
func duplicate(times: Int) {
for index in 0..<times {
print(index)
Item.selection.append(Item(self))
}
}
}
#main struct Main {
static func main() throws {
let item = Item()
perform(item)
performOnSelection() { item in
let _ = Item(item)
}
while (true) {}
}
static func perform(_ item: Item) {
item.duplicate(times: 100)
}
static func performOnSelection(perform action: #escaping (Item)->Void) {
var done = false
DispatchQueue.global().async {
for item in Item.selection {
action(item)
}
done = true
}
while !done { sleep (1) }
}
}
You have autorelease objects being created. Insert an autoreleasepool to drain the pool periodically, e.g.:
func performOnSelection(perform action: #escaping (Item) -> Void) {
...
autoreleasepool {
perform(item)
}
performOnSelection() { item in
autoreleasepool {
let _ = Item(item)
}
}
...
}
and
func duplicate(times: Int) {
for index in 0..<times {
print(index)
autoreleasepool { [self] in
Item.selection.append(Item(self))
}
}
}
E.g. without autoreleasepool:
And with:
It turns off to be much faster when it is not dealing with all of those lingering autorelease objects, too. And peak memory usage has gone from 3.4gb to 47mb.
Related
I have one function which is having some logic which have 2 foreach loop but i want to make code compact so I am trying to use compactmap
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
so instead of 2 foreach i am trying to make in one by using self.item?.connections?.compactMap({ $0.validline }) but I am getting error saying "Type of expression is ambiguous without more context"
I don't see how you can do it without to forEach or compactMap. Here is a possible solution:
func getData() -> [String] {
return item?.connections?.compactMap { connection in
connection.validLine?.compactMap { line in
guard let _ = line.connection?.links[LinkKey.dataGroups], line.dataGroupsCache?.isContinue == true else { return nil }
return checkinGroups.connections?.compactMap(\.id)
}
}
}
Here's a translation of your post into something that is compilable and a direct translation into a version that doesn't use forEach.
I changed connectionIds to ids in your example because otherwise, you might as well just return [].
class Example {
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
func getDataʹ() -> [String] {
guard let connections = item?.connections else { return [] }
let numberOfProperLines = connections.flatMap { $0.validLine ?? [] }
.filter { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
return true
} else {
return false
}
}
.count
return (0..<numberOfProperLines).flatMap { _ in checkinGroups.connections?.compactMap(\.id) ?? [] }
}
var checkinGroups: CheckInGroups!
var item: Item!
}
enum LinkKey: Int {
case dataGroups
}
struct Item {
let connections: [Connection]?
}
struct Connection {
let id: String?
let validLine: [Line]?
let links: [LinkKey: Void]
}
struct Line {
let dataGroupsCache: DataGroups?
let connection: Connection?
}
struct DataGroups {
let isContinue: Bool
}
struct CheckInGroups {
let connections: [Connection]?
}
I'm trying to implement a thread-safe array component in the most efficient and safe way, backed by unit tests.
So far, I would prefer a struct array, to keep a value type and not a reference type.
But when I run the test below, I still have random crashes that I don't explain :
Here's my ThreadSafe array class :
public struct SafeArray<T>: RangeReplaceableCollection {
public typealias Element = T
public typealias Index = Int
public typealias SubSequence = SafeArray<T>
public typealias Indices = Range<Int>
private var array: [T]
private var locker = NSLock()
private func lock() { locker.lock() }
private func unlock() { locker.unlock() }
// MARK: - Public methods
// MARK: - Initializers
public init<S>(_ elements: S) where S: Sequence, SafeArray.Element == S.Element {
array = [S.Element](elements)
}
public init() { self.init([]) }
public init(repeating repeatedValue: SafeArray.Element, count: Int) {
let array = Array(repeating: repeatedValue, count: count)
self.init(array)
}
}
extension SafeArray {
// Single action
public func get() -> [T] {
lock(); defer { unlock() }
return Array(array)
}
public mutating func set(_ array: [T]) {
lock(); defer { unlock() }
self.array = Array(array)
}
}
And here's my XCUnitTest code :
final class ConcurrencyTests: XCTestCase {
private let concurrentQueue1 = DispatchQueue.init(label: "concurrentQueue1",
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
private let concurrentQueue2 = DispatchQueue.init(label: "concurrentQueue2",
qos: .background,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
private var safeArray = SafeArray(["test"])
func wait(for expectations: XCTestExpectation, timeout seconds: TimeInterval) {
wait(for: [expectations], timeout: seconds)
}
func waitForMainRunLoop() {
let mainRunLoopExpectation = expectation(description: "mainRunLoopExpectation")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { mainRunLoopExpectation.fulfill() }
wait(for: mainRunLoopExpectation, timeout: 0.5)
}
func waitFor(_ timeout: TimeInterval) {
let mainRunLoopExpectation = expectation(description: "timeoutExpectation")
DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { mainRunLoopExpectation.fulfill() }
wait(for: mainRunLoopExpectation, timeout: timeout + 0.5)
}
override func setUpWithError() throws {
try super.setUpWithError()
safeArray = SafeArray(["test"])
}
func testSafeArrayGet() {
var thread1: Thread!
var thread2: Thread!
concurrentQueue1.async {
thread1 = Thread.current
let startTime = Date()
for i in 0...1_000_000 {
self.safeArray.set(["modification"])
print("modification \(i)")
}
print("time modification: \(Date().timeIntervalSince(startTime))")
}
concurrentQueue2.async {
thread2 = Thread.current
let startTime = Date()
for i in 0...1_000_000 {
let _ = self.safeArray.get()
print("read \(i)")
}
print("time read: \(Date().timeIntervalSince(startTime))")
}
waitFor(10)
XCTAssert(!thread1.isMainThread && !thread2.isMainThread)
XCTAssert(thread1 != thread2)
}
}
Edit: Event with a class and a simple approach to make it thread safe, I get a crash. Here's a very simple test that crashes :
class TestClass {
var test = ["test"]
let nsLock = NSLock()
func safeSet(_ string: String) {
nsLock.lock()
test[0] = string // crash
nsLock.unlock()
}
}
func testStructThreadSafety() {
let testClass = TestClass()
DispatchQueue.concurrentPerform(iterations: 1_000_000) { i in
testClass.safeSet("modification \(i)")
let _ = testClass.test[0]
}
XCTAssert(true)
}
Why is it crashing? What am I doing wrong?
Note that if I make it a class I don't get crashes, but I would prefer to keep it a struct.
Creating an example for a struct is very easy and straightforward. For example,
import Foundation
struct User: Identifiable, Codable {
let id: UUID
let isActive: Bool
let name: String
let age: Int
let company: String
static let example = User(id: UUID(), isActive: true, name: "Rick Owens", age: 35, company: "Rick Owens Inc.")
}
Now, how can I create an example if I made this an entity in core data? I can't just put let example = CachedUser(id: UUID(), ...) like I did with the struct. I want this example to be part of my core data automatically without having to manually create it by using forms, buttons, etc... Thanks in advance!
You can simply check if your default user exists in database. If it does not then you need to create one and save it. Something like the following would work if you have synchronous operations:
class CachedUser {
static var example: CachedUser = {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
if let existingUser = Database.fetchUser(id: exampleUUID) {
return existingUser
} else {
let newUser = CachedUser()
// TODO: apply example values to user
Database.saveUser(newUser)
return newUser
}
}()
}
This will lazily return existing or generate a new user for you. This user will then be persistent in your database.
The code will only be executed once per session, first time you call CachedUser.example.
If you have your database setup asynchronous then with closures it should look something like this:
class User {
static private(set) var example: User!
static func prepareExampleUser(_ completion: () -> Void) {
let exampleUUID = UUID(uuidString: "33041937-05b2-464a-98ad-3910cbe0d09e")!
Database.fetchUser(id: exampleUUID) { user in
if let user = user {
example = user
completion()
} else {
let newUser = User()
newUser.id = exampleUUID
// TODO: apply example values to user
Database.saveUser(newUser) {
example = newUser
completion()
}
}
}
}
But in this case it makes sense to warmup your application before you show screens that require this user to be present. You can for instance have a loading screen when your app first starts and continue to next screen once this method has finished...
// Loading screen enters
self.startLoading()
User.prepareExampleUser {
self.navigateToNextScreen()
self.stopLoading()
}
In both cases you now hold a static property to your example entry such as User.example which can be used anywhere.
But in both cases you may stumble to issue if user (if able to) deletes this entry from database. You would need to handle that case. Either prevent that action or create a new example user once the old one is deleted.
To access this manager put
let mgr = CachedUserPersistenceManager()
In a ViewModel or a View
/// Manager for the Item entity
class CachedUserPersistenceManager: PersistenceManager<CachedUser>{
let sampleUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
init(isTest: Bool = false) {
super.init(entityType: CachedUser.self, isTest: isTest)
//Preloads the user
preloadSample()
}
///Preloads a sample object to the context
func preloadSample(){
let list = retrieveObjects(sortDescriptors: nil, predicate: NSPredicate(format: "%K == %#", #keyPath(CachedUser.uuid), sampleUUID as CVarArg)
)
if list.isEmpty{
let sampleItem = createObject()
sampleItem.uuid = sampleUUID
save()
}
}
override func addSample() -> CachedUser {
let new = super.addSample() as CachedUser
//add any sample code
return new
}
override func createObject() -> CachedUser {
super.createObject()!
}
override func updateObject(object: CachedUser) -> Bool {
//Replace the uuid if needed
if object.uuid == sampleUUID{
object.uuid = UUID()
}
return super.updateObject(object: object)
}
}
The generic classes that are a part of this code are below. You don't need them per say it just makes some of the code reusable through the app.
//Manager for any Entity
class PersistenceManager<T : NSManagedObject>{
let serviceSD: CoreDataPersistenceService<T>
internal init(entityType: T.Type, isTest: Bool = false) {
self.serviceSD = CoreDataPersistenceService(isTest: isTest, entityType: entityType)
}
//MARK: convenience
func addSample() -> T {
let newItem = createObject()
return newItem!
}
//MARK: Persistence Service Methods
func createObject() -> T? {
let result = serviceSD.createObject()
return result
}
func updateObject(object: T) -> Bool {
return serviceSD.updateObject(object: object)
}
func deleteObject(object: T) -> Bool {
return serviceSD.deleteObject(object: object)
}
func deleteAllObjects(entityName: String, isConfirmed: Bool) -> Bool {
return serviceSD.deleteAllObjects(isConfirmed: isConfirmed)
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) -> [T]{
return serviceSD.retrieveObjects(sortDescriptors: sortDescriptors, predicate: predicate)
}
func retrieveObject(id: String) -> T? {
return serviceSD.retrieveObject(sortDescriptors: nil, id: id).first
}
func resetChanges() {
serviceSD.resetChanges()
}
func save() {
_ = serviceSD.save()
}
}
//Service for Any Entity
class CoreDataPersistenceService<T: NSManagedObject>: NSObject {
var persistenceController: PersistenceController
let entityType: T.Type
required init(isTest: Bool = false, entityType: T.Type) {
if isTest{
self.persistenceController = PersistenceController.preview
}else{
self.persistenceController = PersistenceController.previewAware
}
self.entityType = entityType
super.init()
}
//MARK: CRUD methods
func createObject() -> T? {
let result = entityType.init(context: persistenceController.container.viewContext)
return result
}
func updateObject(object: T) -> Bool {
var result = false
result = save()
return result
}
func deleteObject(object: T) -> Bool {
var result = false
persistenceController.container.viewContext.delete(object)
result = save()
return result
}
func deleteAllObjects(isConfirmed: Bool) -> Bool {
var result = false
//Locked in so only the Generic "Item" can be deleted like this
if entityType == Item.self && isConfirmed == true{
let deleteRequest = NSBatchDeleteRequest(fetchRequest: entityType.fetchRequest())
do {
try persistenceController.container.persistentStoreCoordinator.execute(deleteRequest, with: persistenceController.container.viewContext)
} catch {
print(error)
result = false
}
}
return result
}
func resetChanges() {
persistenceController.container.viewContext.rollback()
_ = save()
}
func save() -> Bool {
var result = false
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
result = true
}else{
result = false
}
} catch {
print(error)
}
return result
}
func retrieveObject(sortDescriptors: [NSSortDescriptor]? = nil, id: String) -> [T]{
return retrieveObjects(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "id == %#", id))
}
func retrieveObjects(sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) -> [T]
{
let request = entityType.fetchRequest()
if let sortDescriptor = sortDescriptors
{
request.sortDescriptors = sortDescriptor
}
if let predicate = predicate
{
request.predicate = predicate
}
do
{
let results = try persistenceController.container.viewContext.fetch(request)
return results as! [T]
}
catch
{
print(error)
return []
}
}
}
The previewAware variable that is mentioned goes with the Apple standard code in the PersistenceController
It automatically give you the preview container so you don't have to worry about adapting your code for samples in Canvas. Just add the below code to the PersistenceController
static var previewAware : PersistenceController{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return PersistenceController.preview
}else{
return PersistenceController.shared
}
}
I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.
I wrote a TaskSync class that is responsible for creating/updating Task objects:
class TaskSync: ISync {
typealias Model = Task
func sync(model: Task) {
let realm = try! Realm()
let inWrite = realm.isInWriteTransaction
if !inWrite {
realm.beginWrite()
}
let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)
// Persist matches as they are not getting fetched with the task
if let _task = _task {
print("matches: \(_task.matches.count)")
model.matches = _task.matches
}
realm.add(model, update: true)
if _task == nil {
var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)
if (user == nil) {
user = model.getUser()
realm.add(user!, update: true)
}
user!.tasks.append(model)
}
if !inWrite {
try! realm.commitWrite()
}
}
func sync(models: List<Task>) {
let realm = try! Realm()
try! realm.write {
models.forEach { task in
sync(model: task)
}
}
}
}
When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.
Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.
Here are the two models:
class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var title: String = ""
dynamic var desc: String = ""
dynamic var price: Float = 0.0
dynamic var calculatedPrice: Float = 0.0
dynamic var location: String = ""
dynamic var duration: Int = 0
dynamic var date: String = ""
dynamic var category: Category?
dynamic var currency: Currency?
dynamic var longitude: Double = 0.0
dynamic var latitude: Double = 0.0
dynamic var state: Int = 0
dynamic var userId: Int = 0
// Existing images
var imagesExisting = List<URLImage>()
// New images
var imagesNew = List<Image>()
// Images deleted
var imagesDeleted = List<URLImage>()
private let users = LinkingObjects(fromType: User.self, property: "tasks")
var user: User?
var matches = List<Match>()
dynamic var notification: Notification?
override static func ignoredProperties() -> [String] {
return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
}
override static func primaryKey() -> String? {
return "id"
}
func getImageMain() -> URLImage? {
for image in imagesExisting {
if image.main {
return image
}
}
return imagesExisting.first
}
func getSection() -> Int {
return state
}
func getSectionFieldName() -> String? {
return "state"
}
func getId() -> Int {
return id
}
func getURL() -> URL? {
if let image = getImageMain() {
return image.getResizedURL()
}
return nil
}
func getState() -> TaskOwnState {
return TaskOwnState(rawValue: state)!
}
func getUser() -> User {
return (user != nil ? user : users.first)!
}
}
class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var state: Int = -1
dynamic var priorityOwnRaw: Int = 0
dynamic var priorityOtherRaw: Int = 0
dynamic var user: User!
var messages = List<Message>()
private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
var task: Task?
dynamic var notification: Notification?
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["task"]
}
func getId() -> Int {
return id
}
func getSection() -> Int {
return 0
}
func getURL() -> URL? {
if let image = user.getImageMain() {
return image.getResizedURL()
}
return nil
}
func getPriorityOwn() -> PriorityType {
if priorityOwnRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getPriorityOther() -> PriorityType {
if priorityOtherRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getSectionFieldName() -> String? {
return nil
}
func getTask() -> Task {
return (task != nil ? task : tasks.first)!
}
}
I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!
This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.
List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).
In my watchOS2 app I have array of tuples like this:
var medicines = [(String, String?, String?)]()
And in refreshing function i'd like to clear this array of tuples to append it with new String items. How can i do this ? I want to avoid having the same things in my array. Or maybe there is a better idea ?
My refresh function:
let iNeedCoreData = ["Value": "CoreData"]
session.sendMessage(iNeedCoreData, replyHandler: { (content: [String: AnyObject]) -> Void in
if let meds = content["reply"] as? [String: [String]] {
self.medicines = [(String, String?, String?)]()
if let medicineNames = meds["medicines"], amountNames = meds["amount"], timeNames = meds["time"] {
if medicineNames.count != 0 {
self.addMedicines(medicineNames)
self.addQuantities(amountNames)
self.addTime(timeNames)
self.table.setHidden(false)
self.reloadTable()
} else {
self.alertLabel.setHidden(false)
}
}
}
}) { (error) -> Void in
print("We got an error from our watch device:" + error.domain)
}
Adding to tuple funcs:
func reloadTable() {
self.table.setNumberOfRows(medicines.count, withRowType: "tableRowController")
var rowIndex = 0
for item in medicines {
if let row = self.table.rowControllerAtIndex(rowIndex) as? tableRowController {
row.medicineLabel.setText(item.0)
if let quantity = item.1, time = item.2 {
row.amountLabel.setText(quantity)
row.timeLabel.setText(time)
}
rowIndex++
}
}
}
func addMedicines(medicineNames: [String]) {
for name in medicineNames {
medicines.append((name, nil, nil))
}
}
func addQuantities(quantities: [String]) {
guard medicines.count == quantities.count else { return }
for i in 0..<medicines.count {
medicines[i].1 = quantities[i]
}
}
func addTime(timeNames: [String]) {
guard medicines.count == timeNames.count else { return }
for i in 0..<medicines.count {
medicines[i].2 = timeNames[i]
}
}
Once the var has been declared, type hints are no longer needed.
self.medicines = []
I've tried to think of a few ways to overcome your problem here, but your code is very inflexible and needs to be refactored.
You are at the limit for the utility of tuples and need to turn medicine into a class or struct (use a struct) which supports Equatable.
In addition, you need to create an array of new objects, which can be merged into the existing self.medicines, building the new objects directly in self.medicines is very limiting.
Here is the tuple as a struct
struct Medicine: Equatable {
let name: String
let amount: String
let time: String
}
func == (lhs: Medicine, rhs: Medicine) -> Bool {
return lhs.name == rhs.name && lhs.amount == rhs.amount && lhs.time == rhs.time
}
Here is adding new values without removing old values or having duplicates
if let names = meds["medicines"], amounts = meds["amount"], times = meds["time"]
where names.count == amounts.count && names.count == times.count
{
for i in 0..<names.count {
let medicine = Medicine(name: names[i], amount: amounts[i], time: times[i])
if !medicines.contains(medicine) {
medicines.append(medicine)
}
}
}