I've got my view controller setup like:
class HomeViewController: UIViewController {
unowned var viewModel: HomeViewModelInterface!
private lazy var collectionViewDataSource: UICollectionViewDiffableDataSource<Segment, Int> = {
configureCollectionViewDataSource()
}()
// ...
// ...
func configureCollectionViewDataSource() -> UICollectionViewDiffableDataSource<Segment, Int> {
let dataSource = UICollectionViewDiffableDataSource<Segment, Int>(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
let cell = cell(with: HomeItemCollectionViewCell.self, for: indexPath, in: collectionView)
cell.configure(with: itemIdentifier, delegate: viewModel)
return cell
}
return dataSource
}
}
The problem occurs inside the cellProvider closure where it always says the unowned reference view model was already deallocated. I'm trying to understand why and also how to fix this. I know the cell provider closure is an escaping closure, maybe that has something to do with it that it could be called long after the function has returned and therefore not be able to access the view model any more? Does anyone know how I get around this?
The viewModel is set as a class instance from elsewhere just FYI.
Any help appreciated.
Related
I have some odd problem with UITableViewCell when I use MVVM pattern with RxSwift. I can't describe it, but I will try to explain.
Let's say we have simple UITableViewCell
class MyTableViewCell: UITableViewCell {
var disposeBag = DisposeBag()
override func prepareForReuse() {
super.prepareForReuse()
disposeBag = DisposeBag()
}
func bind(to viewModel: MyCellViewModel) {
viewModel.randomValue.asDriver()
.drive(onNext: { [weak self] value in
guard let self = self else { return}
print(value) // WHEN TWO CELLS I GET NEW VALUES JUST IN ONE OF THEM
})
.disposed(by: disposeBag)
}
with ViewModel with simple timer.
class MyCellViewModel: NSObject {
let randomValue = PublishSubject<Int>()
init() {
Observable<Int>.timer(.seconds(1), period: .seconds(1), scheduler: SerialDispatchQueueScheduler(qos: .userInteractive))
.map { _ in Int.random()}
.do(onNext: {
print($0) // WORK PERFECT FOR BOTH
})
.bind(to: randomValue)
.disposed(by: rx.disposeBag)
}
}
I also use RxDataSources to fill my tableView.
private func makeDataSource() -> RxTableViewSectionedAnimatedDataSource<Section> {
RxTableViewSectionedAnimatedDataSource<Section>(configureCell: { _, tableView, indexPath, item in
switch item {
case let .myItem(cellViewModel):
let cell = tableView.dequeueReusableCell(with: MyTableViewCell.self, for: indexPath)
cell.bind(to: cellViewModel)
return cell
}
})
}
The problem is that when I have two cells, I get new values in one of them and not in the other.
Pay attention to the method bind(to viewModel
But everything is ok when I change RxTableViewSectionedAnimatedDataSource to RxTableViewSectionedReloadDataSource.
How to make it work with RxTableViewSectionedAnimatedDataSource? I understand the difference between them, but in this case I don't know how does that affect it?
I also checked memory graph and saw just two instances of cells and two instances of cellViewModels. So I hope there are no leaks.
Thanks for any help!
You implemented your ItemMode.== incorrectly. It doesn't actually check for equality, it only checks the identity. Remove the function and let the compiler generate the correct one for you.
In my app, it has two ViewControllers on below same screen.
ViewControllerA is an UIViewController, which includes a tableView.
ViewControllerB is a containerView, which is on top of UITabBarController(root VC) as a child.
For now, I load data(songs array, index, etc.) in viewWillAppear of ViewControllerA, that would be in background queue. Meanwhile, ViewControllerB requires the same data when ViewControllerA's data loading is complete.
Currently, I'm using Dependency Injection to keep a unique data source. Declare modelController reference in ViewController A and B, and use getter and setter to binding the data changes.
The problem is the songs array fetched from modelController in ViewControllerB is nil. Because ViewControllerA will set data on modelController when loading is completed. And ViewControllerB will use it from modelController in its viewWillAppear. But these two VC are on the same screen, how could I guaranty which one will go first? Also data loading is in background queue, so it is suppose to be delayed. Then if ViewControllerB needs that data in first place, it can't be.
My current thought is to load data on ViewControllerB too, so it will not rely on ViewControllerA's result. Or add some observer/listener to message ViewControllerB when data is ready. I think it is kind of architecture stuff, I need advices from experienced guys, any hints are appreciated!
Create and inject the ModelController
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
guard let rootViewController = window?.rootViewController as? TabBarController else {
fatalError("Unexpected Root View Controller")
}
// create dependency injection here
rootViewController.modelController = ModelController()
AudioManager.shared.modelController = rootViewController.modelController
}
ModelController
enum PlayMode: String {
case cycle = "repeat"
case cycleOne = "repeat.1"
case shuffle = "shuffle"
}
class ModelController {
var songs: [Song]? = nil
var position: Int = 0
var playMode = PlayMode.cycle
}
Getter and Setter
var modelController: ModelController!
var songs: [Song]?
{
get { modelController.songs }
set { modelController.songs = newValue }
}
var position: Int
{
get { modelController.position }
set { modelController.position = newValue }
}
Load data in ViewControllerA.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
loadData()
}
func loadData() {
self.showSpinner()
songs?.removeAll()
DispatchQueue.global(qos: .background).async { [weak self] in
self?.songs = DataFetcher.shared.fetchMetaData().0
DispatchQueue.main.async {
print("data load complete")
self?.table.reloadData()
self?.removeSpinner()
}
}
}
so what you said is also possible
observer/listener to message ViewControllerB when data is ready
You can have a shared data class which both the vcA and vcB use. For example
class SharedData {
static let shared = SharedData()
// Use didLoad as a flag for both ViewControllers
var didLoad: Bool {
return self.data != nil
}
var data: SomeData!
private init() {
fetchData()
}
}
In this answer there is a function called fetchData which is in the same class. You can invoke the fetchData function in the background, and use the didLoad or data as flags for displaying/calling any other function you require. This class is also a singleton meaning there will be one instance of this class throughout your applications - So you can use this anywhere in your application.
This is merely an example. You can extend this to your needs such as adding a delegate to listen for callbacks when data is loaded so you can do something in both VCs.
Or you can simply use a protocol from vcA (assuming vcA triggers the network call) and once the data is loaded you can pass it to vcB
I know that our IBOutlets should be private, but for example if I have IBOutlets in TableViewCell, how should I access them from another ViewController? Here is the example why I'm asking this kind of question:
class BookTableViewCell: UITableViewCell {
#IBOutlet weak private var bookTitle: UILabel!
}
if I assign to the IBOutlet that it should be private, I got an error in another ViewController while I'm accessing the cell property: 'bookTitle' is inaccessible due to 'private' protection level
If I understand your question correctly, you are supposing the #IBOutlet properties should be marked as private all the time... Well it's not true. But also accessing the properties directly is not safe at all. You see the ViewControllers, TableViewCells and these objects use Implicit unwrapping on optional IBOutlets for reason... You don't need to init ViewController when using storyboards or just when using them somewhere in code... The other way - just imagine you are creating VC programmatically and you are passing all the labels to the initializer... It would blow your head... Instead of this, you come with this in storyboard:
#IBOutlet var myLabel: UILabel!
this is cool, you don't need to have that on init, it will just be there waiting to be set somewhere before accessing it's value... Interface builder will handle for you the initialization just before ViewDidLoad, so the label won't be nil after that time... again before AwakeFromNib method goes in the UITableViewCell subclass, when you would try to access your bookTitle label property, it would crash since it would be nil... This is the tricky part about why this should be private... Otherwise when you know that the VC is 100% on the scene allocated there's no need to be shy and make everything private...
When you for example work in prepare(for segue:) method, you SHOULD NEVER ACCESS THE #IBOutlets. Since they are not allocated and even if they were, they would get overwritten by some internal calls in push/present/ whatever functions...
Okay that's cool.. so what to do now?
When using UITableViewCell subclass, you can safely access the IBOutlets (ONLY IF YOU USE STORYBOARD AND THE CELL IS WITHIN YOUR TABLEVIEW❗️)
and change their values... you see
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// We shouldn't return just some constructor with UITableViewCell, but who cares for this purposes...
guard let cell = tableView.dequeueReusableCell(withIdentifier: "bookTableViewCell", for: indexPath) else { return UITableViewCell() }
cell.bookTitle.text = "any given text" // This should work ok because of interface builder...
}
The above case should work in MVC pattern, not MVVM or other patterns where you don't use storyboards with tableViewControllers and embed cells too much... (because of registering cells, but that's other article...)
I will give you few pointers, how you can setup the values in the cell/ViewController without touching the actual values and make this safe... Also good practice (safety) is to make the IBOutlets optional to be 100% Safe, but it's not necessary and honestly it would be strange approach to this problem:
ViewControllers:
class SomeVC: UIViewController {
// This solution should be effective when those labels could be marked weak too...
// Always access weak variables NOT DIRECTLY but with safe unwrap...
#IBOutlet var titleLabel: UILabel?
#IBOutlet var subtitleLabel: UILabel?
var myCustomTitle: String?
var myCustomSubtitle: String?
func setup(with dataSource: SomeVCDataSource ) {
guard let titleLabel = titleLabel, let subtitleLabel = subtitleLabel else { return }
// Now the values are safely unwrapped and nothing can crash...
titleLabel.text = dataSource.title
subtitleLabel.text = dataSource.subtitle
}
// WHen using prepare for segue, use this:
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = myCustomTitle
subtitleLabel.text = myCustomSubtitle
}
}
struct SomeVCDataSource {
var title: String
var subtitle: String
}
The next problem could be this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? SomeVC else { return }
let datasource = SomeVCDataSource(title: "Foo", subtitle: "Bar")
// This sets up cool labels... but the labels are Nil before the segue occurs and even after that, so the guard in setup(with dataSource:) will fail and return...
destinationVC.setup(with: datasource)
// So instead of this you should set the properties myCustomTitle and myCustomSubtitle to values you want and then in viewDidLoad set the values
destinationVC.myCustomTitle = "Foo"
destinationVC.myCustomSubtitle = "Bar"
}
You see, you don' need to set your IBOutlets to private since you never know how you will use them If you need any more examples or something is not clear to you, ask as you want... Wish you happy coding and deep learning!
You should expose only what you need.
For example you can set and get only the text property in the cell.
class BookTableViewCell: UITableViewCell {
#IBOutlet weak private var bookTitleLabel: UILabel!
var bookTitle: String? {
set {
bookTitleLabel.text = newValue
}
get {
return bookTitleLabel.text
}
}
}
And then, wherever you need:
cell.bookTitle = "It"
Now outer objects do not have access to bookTitleLabel but are able to change it's text content.
What i usually do is configure method which receives data object and privately sets all it's outlets features.
I haven't come across making IBOutlets private to be common, for cells at least. If you want to do so, provide a configure method within your cell that is not private, which you can pass values to, that you want to assign to your outlets. The function within your cell could look like this:
func configure(with bookTitle: String) {
bookTitle.text = bookTitle
}
EDIT: Such a function can be useful for the future, when you change your cell and add new outlets. You can then add parameters to your configure function to handle those. You will get compiler errors everywhere, where you use that function, which allows you to setup your cell correctly wherever you use it. That is helpful in a big project that reuses cells in different places.
I don't understand this concept that a closure captures data.. Can someone write a sample code using closures that show how data never gets destroyed.. I already read the Apple documents and I'm still confused. And also how does 'unowned' and 'weak' make any difference in a closure...
class TableViewController: UITableViewController {
var allWords = [String]()
var usedWords = [String]()
override func viewDidLoad() {
super.viewDidLoad()
if let allWordsPath = Bundle.main.path(forResource: "start", ofType: "txt"){
if let startWords = try? String(contentsOfFile: allWordsPath){
allWords = startWords.components(separatedBy: "\n")
}else{
allWords = ["Cake"]
}
startGame()
}
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Make Word", style: .plain, target: self, action: #selector (makeWord))
}
func startGame(){
allWords = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: allWords) as! [String]
title = allWords[0]
usedWords.removeAll(keepingCapacity: true)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return usedWords.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Word", for: indexPath)
cell.textLabel?.text = usedWords[indexPath.row]
return cell
}
func makeWord() {
let ac = UIAlertController(title: "Add Word", message: nil, preferredStyle: .alert)
ac.addTextField(configurationHandler: nil)
let submit = UIAlertAction(title: "Submit", style: .default){ [unowned self,ac]
(action: UIAlertAction!) in
let answer = ac.textFields?[0]
self.submit(answer: (answer?.text)!)
}
ac.addAction(submit)
present(ac,animated: true)
}
var number = 10
func submit(answer: String){
usedWords.append(answer)
tableView.reloadData()
}
how does unowned work here if we are not explicitly deallocating things..
You should first search for the difference between strong, weak, and unowned. There are PLENTY of answers here on SO about it.
Anyways, in this specific case:
Your closure has this code:
[unowned self,ac]
This is called a "capture list". It indicates the things that should be "captured" by value WHEN THE BLOCK is created. (if you don't specify them here, and you change the value somewhere after the block, the value inside the block would also be changed).
The reason why self is unowned and doesn't require to be deallocated is because unowned means:
"Don't worry about memory management for this variable, it will ALWAYS
have a value for the duration of my closure"
So, going back to unowned self, the reason you should declare weak or unowned the self variable from a closure is because if not you would create a retain cycle. Things can't be deallocated as long as something is referencing them. So in this case, your TableViewController is keeping your closure alive, and your closure is keeping your TableViewController alive. So because they are referencing each other none of them can get deallocated properly. -> Memory Leak
So we can conclude that self has to either be weak or unowned. For all intents and purposes in this example they are exactly the same. They both serve the purpose of "breaking the retain cycle" by removing the ability of the closure to keep self alive. So who will dealloc self you ask? your closure doesn't care. But think outside your closure. Your closure is being called by your TableViewController, so since there's no weird things going on here we can safely assume that, if there is an alert being shown it must definitively be shown over your TableViewController. So once you dismiss the alert or whatever, your TableViewController will keep working as usual. One you dismiss your TableViewController though, self WILL be deallocated (since the closure is referencing it as unowned), but by this point there is no way the alert is being shown. If you however do some weird things that make your TableViewController be dismissed WHILE the alert is still being shown, then once the user "submits" your app will crash. BECAUSE by declaring your variable unowned you basically made a promise to your closure that it wouldn't have to worry about the self entity, as it would always exist as long as your closure was alive.
Check this out. I'm creating 2 objects of the same kind. One has a reference to a closure that retains itself, so even if the function that created it goes out of scope, the object and the closure retain each other and never get released. The second object's closure has a weak reference to the object, and so when the object creating function goes out of scope, the reference count is 0, and when it gets released it releases the closure as well.
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class B {
deinit {
print("\(name) deinit")
}
var name: String
init(name: String) {
self.name = name
}
var zort: (() -> ())?
func someMethod() {
print("")
}
}
func createStuffThatNeverGoesAway() {
var b: B = B(name: "bad");
b.zort = {
b.someMethod()
}
}
func createStuffThatGoesAway() {
var b: B = B(name: "good");
b.zort = { [weak b] in
b?.someMethod()
}
}
createStuffThatNeverGoesAway()
createStuffThatGoesAway()
Output:
good deinit
I have the following code in one of my classes.
override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
parkCode = tableView.cellForRowAtIndexPath(indexPath).text
RTATab.codeText.text = parkCode.substringToIndex(3)
RTATab.codeLetter.text = parkCode.substringFromIndex(3)
self.dismissModalViewControllerAnimated(true)
}
The RTATab referenced above is another class I have made (type UIViewController) and in that class I have declared it as a global class as show below as I need to access some of the textfields (codeText and codeLetter) in its view.
import UIKit
import messageUI
import CoreData
import QuartzCore
var RTATab : ViewController = ViewController()
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate {
//some code
}
When I run this, I get a can't unwrap optional.none error on the line RTATab.codeText.text = parkCode.substringToIndex(3).
Can someone please help. Do I need to have an initialiser in viewController class?
Thanks
You are getting this error the text of the cell is nil. Before calling methods on parkCode, you must first check if it is nil:
let possibleParkCode = tableView.cellForRowAtIndexPath(indexPath).text
if let parkCode = possibleParkCode {
RTATab.codeText.text = parkCode.substringToIndex(3)
RTATab.codeLetter.text = parkCode.substringFromIndex(3)
}
You're getting the error because RTATab.codeText and RTATab.codeLetter are nil -- the way you're initializing RTATab doesn't actually link up its properties with the text fields in your storyboard. If you truly just need a global version of the view controller, you'd need to give it a storyboard ID and load it using something like:
var RTATab: ViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("RTA") as ViewController
However, my guess is that it's a view controller you've already displayed elsewhere that you want to update, in which case you're better off just setting up a data structure that can hold the "parkCode" values and pulling them back into the correct view controller when it's time to display them.
Either parkCode or RTATab.codeText don't exist (are nil). You need to check for their existence prior to dereferencing either of them.
override func tableView (tableView: ...) {
if let theParkCode = tableView.cellForRowAtIndexPath(indexPath).text {
parkCode = theParkCode;
if let theCodeText = RTATab.codeText {
theCodeText.text = parkCode?.substringToIndex(3)
}
if let theCodeLetter ... {
// ...
}
}
}
Note: the above code depends on how your ViewController (the class of RTATab) declares its instance variables for codeText and codeLetter - I've assumed as optionals.