Swift - Why is Class Instance Appended to Array Deinitialized? - arrays

I'm having memory problems with the Swift code below.
In GameScene's sceneDidLoad(), I add a GKEntity to the array "entities," but in GameScene's update(), the array "entities" is empty. Shouldn't the reference to "entity" be preserved in "entities"? Why is "entity" being deinitialized at the end of sceneDidLoad()?
import GameplayKit
class GraphicsComponent: GKComponent {
override func update(deltaTime: TimeInterval) {
// ...
}
}
class GameScene: SKScene {
var entities = [GKEntity]()
override func sceneDidLoad() {
let entity = GKEntity()
entity.addComponent(GraphicsComponent())
entities.append(entity)
}
override func update(_ currentTime: TimeInterval) {
// ...
for entity in entities {
entity.update(deltaTime: dt)
}
}
}
I was expecting the "entity" instance to be preserved because a reference to it was added to the "entities" array. If that reference remained, then why was the instance destroyed?
EDIT:
I found that if I move the code in sceneDidLoad() to didMove(), everything works as expected. I still didn't know why, so I looked around in the boilerplate that Xcode puts in GameViewController and found where sceneDidLoad() and didMove() are each called. I found that if I add an entity to the scene.entities array in sceneDidLoad(), the scene.entities array will be empty when the code steps into the if statement that begins with if let scene = GKScene(fileNamed: "GameScene"). Is the if let creating a temporary GKScene?
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// This "if let" will call sceneDidLoad(), which appends a new entity to scene.entities.
if let scene = GKScene(fileNamed: "GameScene") {
// scene.entities.count = 0
if let sceneNode = scene.rootNode as! GameScene? {
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
sceneNode.scaleMode = .aspectFill
if let view = self.view as! SKView? {
view.presentScene(sceneNode) // This calls didMove()
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
}

Related

Wait till image is done loading to an array before appending the array to another array

I have an array with data I get from Firebase like this:
profileImage = UIImage(data: data!)!
array.append(CustomModel(creatorID:creatorID, creatorPhoto: profileImage, creatorName: creatorName, documentID: documentID))
Now I need to append that array into another array like this
self.arrayOfArrays.append(array)
However when I append array it seems to leave it as empty, as (im guessing) the image is still loading onto array.
My Question Is: Can I use some sort of Dispatch or delayed that would wait till array is done loading before appending it to arrayOfArrays?
PS: I tried using DispatchQueue.main.asyncAfter(deadline: .now() + 5) and it works, but I need something that is not time based.
Also I cannot have the line just under the code:
array.append(CustomModel(creatorID:creatorID, creatorPhoto: profileImage, creatorName: creatorName, documentID: documentID))
because this will ruin the structure of the code.
why don't you use reactive approach using RxSwift & RxCocoa. You can achieve that this way:
Let assume that this is your struct as Codable:
struct CustomModel: Codable {
let creatorID: String
let creatorPhoto: Data
let creatorName: String
let documentID: String
}
Next create your firebase client that will create observable for fetching CustomModel from firebase database
class FirebaseClient {
static var shared = FirebaseClient()
lazy var firebaseRequestObservable = FirebaseRequestObservable()
func getCustomModel() throws -> Observable<CustomModel> {
return requestObservable.getCustomModel()
}
}
Next you must implement your observable with getCustomModel method that will return your CustomModel from firebase. I have set child name as CustomModel, but you can set it depending on your firebase structure. Also here you could return an array of data like [CustomModel].Also we add onNext, onError and onCompleted methods that will return data or error or complete our subscription to observable.
public class FirebaseRequestObservable {
let citiesRef = db.collection("CustomModels")
public init() {
}
//MARK: function for URLSession takes
public func getCustomModel<CustomModel: Decodable>() -> Observable<CustomModel> {
//MARK: creating our observable
return Observable.create { observer in
Database.database().reference().child("CustomModel").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
do {
let customModel = try FirebaseDecoder().decode(CustomModel.self, from: value)
observer.onNext(customModel)
} catch let error {
observer.onError(error)
}
//MARK: observer onCompleted event
observer.onCompleted()
})
return Disposables.create {
task.cancel()
}
}
}
}
And finally in your ViewController call client getCustomModel method from client class that will return your data asynchronously.
class ViewController: UIViewController {
var customModel: CustomModel
var array: [CustomModel] = []
var arrayOfArrays: [[CustomModel]] = []
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let client = FirebaseClient.shared
do{
try client.getCustomModel().subscribe(
onNext: { result in
//result is custom model from firebase
self.customModel = result
//append your data
self.array.append(self.customModel)
self.arrayOfArrays.append(array)
},
onError: { error in
print(error.localizedDescription)
},
onCompleted: {
print("Completed event.")
}).disposed(by: disposeBag)
}
catch{
}
}
}

How do i refresh a TableView after some data will changed?

I want to change the list of users in only one class, called Network. And i don't understand how to make a TableView update after the userList has changed. I'll show you an example and detailed question in code below.
// Network.swift
class Network {
var userList: [User] = []
// Next functions may change userList array
// For example, the data came from the server, and update the userList with new data
}
// App delegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var network: Network = Network()
..
}
// File TableViewController.swift
class TableViewController: UITableViewController {
…
var userList: [User] = [] // Here I want to have a full copy of the array from Network class
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
self.userList = appDelegate.network.userList // Just copy an array
// And I want that after each update appDelegate.network.userList I updated the table, how to do it better?
self.tableView.reloadData()
}
}
As #JDM mentioned in comment your architecture is messed.
Try to do this delegation using protocols:
// Network.swift
protocol UsersUpdatedProtocol {
func usersListUpdated(list: [User])
}
class Network {
var userList: [User] = [] {
didSet {
delegate?.usersListUpdated(list: userList)
}
}
var delegate: UsersUpdatedProtocol?
init(delegate d: UsersUpdatedProtocol) {
super.init()
delegate = d
}
}
// File TableViewController.swift
class TableViewController: UITableViewController, UsersUpdatedProtocol {
var userList: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
let _ = Network(delegate: self)
}
func usersListUpdated(list: [User]) {
self.userList = list
self.tableView.reloadData()
}
}
You could use a notification. Whenever the userlist gets updated, post a notification like this:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
Then, in viewDidLoad add:
NotificationCenter.default.addObserver(self, selector: #selector(TableViewController.reloadData), name: NSNotification.Name(rawValue: "UserlistUpdate"), object: nil)
P.S. regarding your architecture so far, I would make the TableViewController hold a variable for Network rather than hold its own user array. Then, in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let network = Network()
// Access the TableViewController and set its network variable
let tableViewController = window!.rootViewController as! TableViewController
tableViewController.network = network

How to avoid a retain cycle when using an array of delegates in Swift [duplicate]

This question already has answers here:
Using as a concrete type conforming to protocol AnyObject is not supported
(6 answers)
Closed 5 years ago.
In one of my classes I use an array of delegates (the class is a singleton). This is causing an retain cycle. I know I can avoid the retain cycle when I use only one delegate by making the delegate weak. But this is not working for my array of delegates.
How can I avoid this retain cycle.
Example:
protocol SomeDelegate: class {
func someFunction()
}
My Class
class SomeClass {
// This class is a singleton!
static let sharedInstance = SomeClass()
var delegates = [SomeDelegate]() // this is causing a retain cycle
weak var delegate: SomeDelegate? // this is ok.
... other code...
}
The problem is that weakDelegates is a strong reference and its reference to its elements of type WeakDelegateContainer is a strong reference.
Your situation is why the class NSHashTable exists. Initialize using weakObjects(). This will give you a set of ARC-weak references, each of which will be nilified and removed when the referenced object goes out of existence (with no need for any extra bookkeeping on your part, and no need for your WeakDelegateContainer type).
Your set will have to be typed as holding AnyObject, but you can easily mediate to ensure that you are supplying and retrieving SomeDelegate-conformant objects:
let list = NSHashTable<AnyObject>.weakObjects()
func addToList(_ obj:SomeDelegate) {
list.add(obj)
}
func retrieveFromList(_ obj:SomeDelegate) -> SomeDelegate? {
if let result = list.member(obj) as? SomeDelegate {
return result
}
return nil
}
func retrieveAllFromList() -> [SomeDelegate] {
return list.allObjects as! [SomeDelegate]
}
The function retrieveAllFromList() lists only objects that still exist. Any object that has gone out existence has been changed to nil in the NSHashTable and is not included in allObjects. That is what I mean by "no extra bookkeeping"; the NSHashTable has already done the bookkeeping.
Here is code that tests it:
func test() {
let c = SomeClass() // adopter of SomeDelegate
self.addToList(c)
if let cc = self.retrieveFromList(c) {
cc.someFunction()
}
print(self.retrieveAllFromList()) // one SomeClass object
delay(1) {
print(self.retrieveAllFromList()) // empty
}
}
Alternatively, you can use NSPointerArray. Its elements are pointer-to-void, which can be a little verbose to use in Swift, but you only have to write your accessor functions once (credit to https://stackoverflow.com/a/33310021/341994):
let parr = NSPointerArray.weakObjects()
func addToArray(_ obj:SomeDelegate) {
let ptr = Unmanaged<AnyObject>.passUnretained(obj).toOpaque()
self.parr.addPointer(ptr)
}
func fetchFromArray(at ix:Int) -> SomeDelegate? {
if let ptr = self.parr.pointer(at:ix) {
let obj = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
if let del = obj as? SomeDelegate {
return del
}
}
return nil
}
Here is code to test it:
let c = SomeClass()
self.addToArray(c)
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // called
}
}
delay(1) {
print(self.parr.count) // 1
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // not called
}
}
}
Interestingly, after our SomeClass goes out of existence, our array's count remains at 1 — but cycling through it to call someFunction, there is no call to someFunction. That is because the SomeClass pointer in the array has been replaced by nil. Unlike NSHashTable, the array is not automatically purged of its nil elements. They do no harm, because our accessor code has guarded against error, but if you would like to compact the array, here's a trick for doing it (https://stackoverflow.com/a/40274426/341994):
self.parr.addPointer(nil)
self.parr.compact()
I found the solution in Using as a concrete type conforming to protocol AnyObject is not supported. All credits to Kyle Redfearn.
My solution
protocol SomeDelegate: class {
func someFunction()
}
class WeakDelegateContainer : AnyObject {
weak var weakDelegate: SomeDelegate?
}
class SomeClass {
// This class is a singleton!
static let sharedInstance = SomeClass()
fileprivate var weakDelegates = [WeakDelegateContainer]()
func addDelegate(_ newDelegate: SomeDelegate) {
let container = WeakDelegateContainer()
container.weakDelegate = newDelegate
weakDelegates.append(container)
}
func removeDelegate(_ delegateToRemove: SomeDelegate) {
// In my case: SomeDelegate will always be of the type UIViewController
if let vcDelegateToRemove = delegateToRemove as? UIViewController {
for i in (0...weakDelegates.count - 1).reversed() {
if weakDelegates[i].weakDelegate == nil {
// object that is referenced no longer exists
weakDelegates.remove(at: i)
continue
}
if let vcDelegate = weakDelegates[i].weakDelegate as? UIViewController {
if vcDelegate === vcDelegateToRemove {
weakDelegates.remove(at: i)
}
}
}
}
}
... other code ...
}

Using KVO to tell when elements have been added to an array

I want to check if elements have been added to an array in swift using KVO, and I essentially copied the example from Apple's documentation, but when the code runs, it does not catch when the size of the array updates. Here is what I have now:
class ShowDirectory: NSObject {
var shows = [Show]()
dynamic var showCount = Int()
func updateDate(x: Int) {
showCount = x
}
}
class MyObserver: NSObject {
var objectToObserve = ShowDirectory()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "showCount", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("\(newValue) shows were added")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
After I add the shows to the array, I set showCount equal to the number of elements in the array, however, it does not print "X shows were added" to console. My viewDidLoad() function simply calls the function that adds elements to the array, and nothing else at the moment.
You unfortunately cannot add as an observer to an Int, as it does not subclass NSObject
See the Apple Docs and search for "Key-Value Observing"
You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class.
Otherwise, your KVO boiler-plate code looks good to me.
If you want to be notified when your array's contents change, you could try what #Paul Patterson recommends and use a proxy object

How to observe an array of NSObjects in swift?

I am new in swift language and my problem is about how to use observable/observer pattern in swift.
I want to make my array to be observable in my SocketManager class so it can be observed by my UIViewController class. I have used the Observable class written by Andrew J Wagner which I got from this link:
http://www.drewag.me/posts/swift-kvo-substitute-observable-variables
I have the array:
var marketIndexList: Array< MarketIndex > = []
which will get its data from a server. This list will be updated every time a new data received from server. After I got the values of my Array from server I want to make it of type Observable class which is implemented by the above link:
marketIndexList = Observable(marketIndexList)
But I got this error:
'MarketIndex' is not identical to 'AnyObject'
MarketIndex is a class of type NSObject which has some properties of type String.
This is the Observable class that I have used:
import Foundation
class Observable {
typealias DidChangeHandler = (oldValue: Array<MarketIndex>?, newValue: Array<MarketIndex>) -> ()
var value : Array<MarketIndex> = [] {
didSet {
for (owner, handlers) in self.observers {
for handler in handlers {
handler(oldValue: oldValue, newValue: value)
}
}
}
}
init(_ value: Array<MarketIndex>) {
self.value = value
}
func addObserverForOwner(owner: IndexViewController, triggerImmediately: Bool, handler: DidChangeHandler) {
if let index = self.indexOfOwner(owner) {
// since the owner exists, add the handler to the existing array
self.observers[index].handlers.append(handler)
} else {
// since the owner does not already exist, add a new tuple with the
// owner and an array with the handler
self.observers.append(owner: owner, handlers: [handler])
}
if (triggerImmediately) {
// Trigger the handler immediately since it was requested
handler(oldValue: nil, newValue: self.value)
}
}
func removeObserversForOwner(owner: AnyObject) {
if let index = self.indexOfOwner(owner) {
self.observers.removeAtIndex(index)
}
}
// #pragma mark - Private Properties
var observers: [(owner: IndexViewController, handlers: [DidChangeHandler])] = []
// #pragma mark - Private Methods
func indexOfOwner(owner: AnyObject) -> Int? {
var index : Int = 0
for (possibleOwner, handlers) in self.observers {
if possibleOwner === owner {
return index
}
index++
}
return nil
}
}
Can anyone tell me what the problem is?
Also does anyone know a way to observe an array of objects in swift?
I would appreciate any help.
Thanks in advance.
The error is because marketIndexList is defined as Array<MarketIndex> but you assigned Observable instance. Perhaps you wanted to do something like this:
var observableList: Observable = Observable([])
var marketIndexList: Array<MarketIndex> = [MarketIndex(), MarketIndex()]
observableList.value = marketIndexList
// Or maybe
observableList = Observable(marketIndexList)
By the way, you can also use Objective-C KVO from Swift. Just mark the property as dynamic and make sure the class inherits NSObject to make the property observable. For example:
class ObservableClass: NSObject {
dynamic var value = [Int]()
}
This post is good to read for KVO in Swift in addition to what you referred to.
https://medium.com/proto-venture-technology/the-state-of-kvo-in-swift-aa5cb1e05cba

Resources