Reference counting of UIViewController - ios

I just experienced a strange behavior when I do the following steps,
In a view controller method, create a View controller instance (local instance).
Add the view as a subview to the view controller's view.
The view is displayed properly. The view has a button and when I click the button, it crashes with EXEC_BAD-ACCESS. After debugging a while, found that the view controller instance is release and button click event is not fired since the controller doesnot exist.
When I moved the declaration of the view controller to class level, it started working. I feel if a view controller's view is on the screen, shouldn't the view controller instance be retained.
Any thoughts?
Some code pointer.
class SomeViewController:UIViewController{
var workingVC:SomeVC?
func crashingMethod()
{
let vc:SomeVC = SomeVC(nibName:"SomeVC", bundle:NSBundle.mainBundle())
let delegate:AppDelegate = UIApplication.sharedApplication().delegate
let applWindow:UIWindow = delegate.window!
applWindow.addSubview(vc.view)
}
func workingMethod()
{
self.workingVC = SomeVC(nibName:"SomeVC", bundle:NSBundle.mainBundle())
let delegate:AppDelegate = UIApplication.sharedApplication().delegate
let applWindow:UIWindow = delegate.window!
applWindow.addSubview(self.workingVC!.view)
}
}

When you create object like this
let vc:SomeVC = SomeVC(nibName:"SomeVC", bundle:NSBundle.mainBundle())
object lifetime is equal to scope where it is created.
When scope ends, ARC release this object and all associated objects.
But, when you add it on class level, lifetime of this object equal to lifetime of owner(SomeViewController). So, it is released only when owner released.
Is it clear for you?
Also you can read about it in Memory Management Section in Apple Documentation

Related

How to prevent timer reset using pushViewController method?

I'm trying to keep a timer running even if I switch view controllers. I played around with the Singleton architecture, but I don't quite get it. Pushing a new view controller seems a little easier, but when I call the below method, the view controller that is pushed is blank (doesn't look like the view controller that I created in Storyboards). The timer view controller that I'm trying to push is also the second view controller, if that changes anything.
#objc func timerPressed() {
let timerVC = TimerViewController()
navigationController?.pushViewController(timerVC, animated: true)
}
You need to load it from storyboard
let vc = self.storyboard!.instantiateViewController(withIdentifier: "VCName") as! TimerViewController
self.navigationController?.pushViewController(timerVC, animated: true)
Not sure if your problem is that your controller is blank or that the timer resets. Anyway, in case that you want to keep the time in the memory and not deallocate upon navigating somewhere else I recommend you this.
Create some kind of Constants class which will have a shared param inside.
It could look like this:
class AppConstants {
static let shared = AppConstants()
var timer: Timer?
}
And do whatever you were doing with the timer here accessing it via the shared param.
AppConstants.shared.timer ...
There are different parts to your question. Sh_Khan told you what was wrong with the way you were loading your view controller (simply invoking a view controller’s init method does not load it’s view hierarchy. Typically you will define your view controller’s views in a storyboard, so you need to instantiate it from that storyboard.)
That doesn’t answer the question of how to manage a timer however. A singleton is a good way to go if you want your timer to be global instead of being tied to a particular view controller.
Post the code that you used to create your singleton and we can help you with that.
Edit: Updated to give the TimeManager a delegate:
The idea is pretty simple. Something like this:
protocol TimeManagerDelegate {
func timerDidFire()
}
class TimerManager {
static let sharedTimerManager = TimerManager()
weak var delegate: TimeManagerDelegate?
//methods/vars to manage a shared timer.
func handleTimer(timer: Timer) {
//Put your housekeeping code to manage the timer here
//Now tell our delegate (if any) that the timer has updated.
//Note the "optional chaining" syntax with the `?`. That means that
//If `delegate` == nil, it doesn't do anything.
delegate?.timerDidFire() //Send a message to the delegate, if there is one.
}
}
And then in your view controller:
//Declare that the view controller conforms to the TimeManagerDelegate protocol
class SomeViewController: UIViewController, TimeManagerDelegate {
//This is the function that gets called on the current delegate
func timerDidFire() {
//Update my clock label (or whatever I need to do in response to a timer update.)
}
override func viewWillAppear() {
super.viewWillAppear()
//Since this view controller is appearing, make it the TimeManager's delegate.
sharedTimerManager.delegate = self
}

How to pass data between views. When should I use what?

I have a View-Hierarchy like this:
UIViewController (SingleEventViewController)
UIScrollView (EventScrollView)
UIView (contentView)
3xUITableView (SurePeopleTV, MaybePeopleTV, NopePeopleTV (all inherited from the same UITableView)), & all other UI-Elements
The SingleEventViewController stores one Event (passed within the initializer). (All Events are stored in Core-Data).
The three UITableViews are there for displaying the users which are participating (or not or maybe) at the Event. My question is, what are the possibilities to fill the tableViews with the data and what would you recommend in which situation.
Currently I have a property parentVC: SingleEventViewController in all Subviews and get the data like this:
override func loadUsers() {
//class SurePeopleTV
guard let parentController = parentVC else { return }
users = (parentController.thisEvent.eventSureParticipants?.allObjects as! [User])
finishedLoading = true
super.loadUsers()
}
.
func applyDefaultValues() {
//class EventScrollView
guard let parent = parentVC else { return }
titleLabel.text = parent.eventName
}
I'm new to programming but I got a feeling that I should not create a parentVC reference in all of my classes.
An object should not (ideally) know about its parent - if it does they are "tightly coupled". If you change the object's parent, your code may break. In your case, your parent object must have a thisEvent property.
You want your objects to be "loosely coupled", so the object doesn't know about a specific parent object.
In Swift, the usual ways to pass information "back up the chain" is to use the delegate design pattern… https://developer.apple.com/documentation/swift/cocoa_design_patterns or to use closures.
See also https://www.andrewcbancroft.com/2015/04/08/how-delegation-works-a-swift-developer-guide/ for info on delegation
First of all, if you create a reference to the parent ViewController make sure it is weak, otherwise you can run into memory management issues.
Edit: As Ashley Mills said, delegates the way to handle this
The recommended way to pass data between ViewControllers is using something like this
Every time a segue is performed from the view controller this function is in this function is called. This code first checks what identifier the segue has, and if it is the one that you want, you can access a reference to the next view controller and pass data to it.

Collection View returns nil when called in function

I want to run a function which involves adding a sublayer to a collection view. However when I run the function the app crashes saying Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value When I print the collection view it shows up in the log as none so I know the collection view is the problem. The view has already been loaded when I call the function and I can see all of it's cells. The function is being called from another class, which I think might have something to do with the problem.
Here is the function that I am calling...
func displayCircle() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.green.cgColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.green.cgColor
//you can change the line width
shapeLayer.lineWidth = 3.0
print(shapeLayer)
print(collectionView)
collectionView!.layer.addSublayer(shapeLayer)
}
Here is how I am calling this function from another class...
ViewController().displayCircle()
EDIT: This is my storyboard layout...
What could the problem be?
As you can see, I am using a page view controller. Hope this helps
There's likely a few issues here.
If you wanted to say ViewController.displayCircle() then displayCircle would need to be a static function. But I don't think that was your intention, you probably don't want to do that in this case, and also your static function syntax is wrong (ViewController().displayCircle() is wrong). But moving on... :)
ViewController().displayCircle() isn't how you properly reference the collectionView. First you need a reference to the other view controller. Then inside displayCircle you need to grab a reference to the collectionView if it's in another View Controller. So that would be otherViewController.collectionView provided the collectionView is public of course, and provided you have a reference to that other view controller somehow. Note that you can't just make a new reference of the other view controller, otherwise you'll be adjusting the layer on the new instance, not the original.
Last but not least, you're force unwrapping the collectionView - don't do that. Your app will crash if it's ever nil. Instead, take advantage of Swift's paradigms:
if let collView = collectionView {
collView.layer...// etc
}
This last bit isn't the issue, but just good practice.
If the collectionView is part of this same viewController, use self:
self.collectionView.layer.addSublayer(shapeLayer)
If the collectionView is in a different viewController, as it seems to be, you would need to get a reference to that view controller. How you do this depends on the structure of your app. You mention using a UIPageViewController and presumably both the view controllers are presented on it.
From one view controller, you can refer to another like this:
let pvc = self.parent as? UIPageViewController // Or your custom class
let targetViewController = pvc.viewControllers[index] as? YourTargetViewControllerClass
You might need to figure out what index you need. An alternative is to make sure each child view controller of the UIPageViewController has its own subclass, then find the one you want like this:
let pvc = self.parent as? UIPageViewController
let viewControllers = pvc.viewControllers.filter { $0 is CustomSubclass }
if let viewController = viewControllers.first as? CustomSubclass {
viewController.displayCircle()
}
As the other answer states, using ViewController() creates a brand new view controller instance, not the one that already exists.
This
ViewController()
creates a new instance other than the presented one ,when you reference the collectionView from it it's actually nil as you have to load the VC either from storyboard / xib , so you have to use delegate to reference the current VC that contains the collectionView

Is it fine to nest a UIViewController within another without using addChildViewController?

I'm trying to make a custom ContainerViewController, but due to lots of difficulties with the ViewController transitions and making everything interactive, I've decided to mimic that functionality myself.
What I basically want to do, is have a paginated UIScrollView (the HeaderView) on the top control different another UIScrollView (the ControllersView) below that contains ViewControllers as pages so that as you swipe to a new page on the HeaderView, it also swipes to the next viewcontroller on the ControllersView. This is what the setup would look like.
My question is, is there anything wrong with having the aforementioned setup? All I'll do to add the view controllers to the ControllersView is just something like: controllersView.addSubview(pagecontroller1.view).
Some posts online seem to say that "the appropriate ViewController functions won't be called" or whatever. What do I seem to be missing here? I'm guessing there's a lot of dismissing and admitting of ViewControllers that I need to call every time a ViewController is out of frame right?
To clarify the question: Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call? I'm realizing that if I were to set things up this way, I need to manage a lot of things that are usually handled automatically, but as I mentioned before, custom ContainerViewControllers have failed me and I'm going with this.
PS. If you seem to still be lost on how this will look like, see my previous question here where I originally wanted to use a Container ViewController. There's a much better mockup there.
You can add and remove VC In Container Views
For - Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call?
As, We need to call WillAppear and Disappear Func when Adding and removing a VC , Thus Try using below Functions That will Handle these Responses
I use the Two specific Functions to add and remove Controller in ContainerView/UIView/SubView in ScrollView inside a UIView
To Add
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
To Remove
private func remove(asChildViewController viewController: UIViewController)
{
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
secondContainer.willRemoveSubview(viewController.view)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
Creating Object
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
For - controllersView.addSubview(pagecontroller1.view)
Answer - Yes Approbate func wont be called if pagecontroller1 is not loaded in to memory stack, to load that you need to notify pagecontroller1 that it is going to be added to memory stack as Child View , Just as We initiate a Controller and basically notifies the Controller to get its component loaded to memory stack to get some memory allocations
For Question - Is it fine to nest a UIViewController within another without using addChildViewController?
Check apple Documentation - https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchildviewcontroller
This is necessary just as to notify the controller who is going to be added in Another Parent View as Child
Sample Project
https://github.com/RockinGarg/Container_Views.git
Or
https://github.com/RockinGarg/ContainerView-TabBar.git
If Question is Still not answered Please Tell me what Func Exactly you want to handle by yourself

UIViewController variables persist after deallocation

I am attempting to write tests to check for retain cycles but came across this odd behavior. A UIViewController's properties do not get deallocated when setting the view controller to nil. Take this mock object for example:
class BasicViewController: UIViewController {
var someObject = NSObject()
.....
}
All it has is a variable. You would assume that when calling basicViewController = nil would cause someObject to be nil, but its not.
it("releases someObject") {
var controller: MockController? = MockController()
weak var something = controller?.something
expect(controller).toNot(beNil())
controller = nil
expect(controller).to(beNil())
expect(something).to(beNil())
}
it("doesn't release someObject") {
var controller: MockController? = MockController()
weak var something = controller?.something
expect(controller).toNot(beNil())
_ = controller?.view
controller = nil
expect(controller).to(beNil())
expect(something).toNot(beNil())
}
When calling vc.view this invokes loadView as well as the UIViewController's life cycle functions - viewDidLoad, viewDidAppear and viewWillAppear. My question is why? Why is it that when I reference a UIViewController's view property, that all objects the UIViewController owns persists, even after setting the UIViewController to nil.
FWIW, I am using Quick and Nimble for testing, as well as Swift 3.1
Short answer:
Add an autoreleasepool, which will dictate precisely when objects are drained from the pool, and it works as you would have expected.
Long answer:
I'm experiencing the same behavior that you describe. But the problem isn't the properties of the view controller. It's the view controller itself.
In your examples, you're setting controller to nil and appear using the fact that it is now nil to infer whether the controller has been deallocated or not. But that's only testing whether that particular reference to the view controller is nil, but the view controller itself might not yet be deallocated. But you can use your weak var test with the view controller itself. Consider this view controller:
class BasicViewController: UIViewController {
// this is intentionally blank
}
I can write tests where the view controller manifests the behavior that you describe, where that XCTAssertNil test after loading the view fails:
class MyApp2Tests: XCTestCase {
func testWithoutView() {
var controller: BasicViewController? = BasicViewController()
weak var weakController = controller
XCTAssertNotNil(weakController)
controller = nil
XCTAssertNil(weakController) // this succeeds
}
func testWithView() {
var controller: BasicViewController? = BasicViewController()
weak var weakController = controller
XCTAssertNotNil(weakController)
controller?.loadViewIfNeeded()
controller = nil
XCTAssertNil(weakController) // this fails
}
}
But when I added an autoreleasepool to explicitly control when the pool is drained, it worked as expected:
func testWithViewAndAutoreleasePool() {
weak var weakController: BasicViewController?
autoreleasepool {
var controller: BasicViewController? = BasicViewController()
weakController = controller
XCTAssertNotNil(weakController)
controller?.loadViewIfNeeded()
controller = nil
}
XCTAssertNil(weakController) // this succeeds
}
BTW, if you're looking for additional confirmation on the timing of the deallocation of the view controller, itself, add a print statement in deinit (as well as where you set controller = nil) and you'll see that the timing of deinit changes in the presence of doing anything that loads the view.
I can't explain this behavior. Why should doing something with the view affect the view controller lifecycle? BTW, I also performed the above tests with properties of the view controller, like in your question, and I see the exact same behavior (but IMHO, that's unsurprising because it's just because the view controller itself hasn't been deallocated).
At least we can explicitly control the autorelease pool lifecycle timing with autoreleasepool.

Resources