Here is my real code:
#IBOutlet weak var contentTextView: SmartTextView! {
didSet {
self.contentTextView.onDidBeginEditing = {
$0.layer.borderColor = Util.green.CGColor
}
self.contentTextView.onDidEndEditing = {
$0.layer.borderColor = Util.gray.CGColor
}
self.contentTextView.layer.borderWidth = 1 / Util.screenScale
self.contentTextView.layer.borderColor = Util.gray.CGColor
self.contentTextView.minHeight = 148
self.contentTextView.maxHeight = 148
self.contentTextView.onChange = { [unowned self] text in
var content = text.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "\n\t"))
self.contentLenthLabel.text = "\(self.MAX_CONTENT - count(content))"
}
}
}
If I remove [unowned self] statement, I can see a retain cycle problem in Instruments.
Is the KVO or something else make a weak var can still cause retain cycle?
The weak reference is a red herring; it has nothing to do with the story here. Without the [unowned self], you are retaining this view and this view is retaining you. That's a retain cycle:
UIViewController retains its view
View retains its subviews; one of those subviews is the SmartTextView
SmartTextView retains the onChange function
Function retains self (the UIViewController) unless you say unowned self.
Related
I'm implementing a simple Master-Detail app where the Master viewController manages a table view which shows the results of a call to a REST service. The Detail viewController manages a view where I show more information about the item selected in the Master. Common scenario.
I'm trying to apply MVVM pattern. In the Master viewController, I create and initialize its viewModel this way:
lazy private var viewModel: ListViewModel = {
return ListViewModel()
}()
override func viewDidLoad() {
super.viewDidLoad()
initViewModel()
}
private func initViewModel() {
viewModel.onModelChange = { [weak self] () in
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
viewModel.fetchData()
}
My question is: in the closure provided to the viewModel, should self be weak or unowned instead? I found an example implementing an scenario similar to mine that was setting it to weak, and another one setting it to unowned, so I'm not completely clear.
[unowned self]. This tells your model not to hold a strong reference to ViewController
Apple document states it clearly that:
“Like weak references, an unowned reference does not keep a strong
hold on the instance it refers to. Unlike a weak reference, however,
an unowned reference is assumed to always have a value”.
In your case, there is always be ViewController. So benefit of unowned reference is it’s nonoptional. No unwrap required each time it is used
Unowned is used when you 100% sure the object won't be deallocated.
weak you then need to take care of its reference count issues.
viewModel.onModelChange = { [weak self] () in
guard let strongSelf = self else { return }
strongSelf.tableView.reloadData()
}
I generally do it like this. You then hold a strong reference of self to avoid it being allocated during the block is running.
The difference between unowned and weak is that weak is declared as an Optional while unowned isn't. If you know that self will not be nil use unowned, if you don't know: Use weak
lazy var headerView: WatchlistModifierHeaderView = {
let view = WatchlistModifierHeaderView()
view.translatesAutoresizingMaskIntoConstraints = false
view.heightAnchor.constraint(equalToConstant: HEADER_VIEW_HEIGHT).isActive = true
view.tapEventer.handler = { [unowned self] in
print("HeaderView tapped")
}
return view
}()
Here is an example. I like this style because everything pertaining to the view is captured in the lazy var. However, I am curious if the variable won't deinit because of the callback.
At the end of the day, I need to read up on memory, init, and deinit.
That looks fine to me. If you actually end up using self in the callback as long as you keep that unowned or add a weak, it shouldn't cause a retain cycle and will deinit correctly.
I inherited a project that uses disposeBags everywhere, but disposeBag seems to be a massive memory leak. None of the view controllers utilizing the bag ever get deallocated, which leads to subscriptions piling up. I'm
class TestViewController: UIViewController
{
#IBOutlet weak var testLabel: UILabel!
var basePresenter: BasePresenter = BasePresenter()
var disposeBag: DisposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindPresenter()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//self.disposeBag = DisposeBag() <------------
}
func bindPresenter() {
//super.bindPresenter()
basePresenter.testVariable.asDriver().drive(onNext: { test in
if !test.id.isEmpty {
self.testLabel.text = "Test text" //<------------
}
}).addDisposableTo(disposeBag)
}
deinit{
print("TestView was dealloc'd")
}
}
The key issue is the reference in the handler to "self."
My theory is that self is a strong reference, which is leading to a situation where even when the view controller gets popped and there are no other references to the view controller, it still doesn't get deallocated because the bag has a strong reference to it. Circular logic where the bag doesn't get disposed because the VC doesn't dealloc and the VC doesn't dealloc because the bag doesn't get disposed.
The commented out line
//self.disposeBag = DisposeBag()
when called allows for the view to properly dealloc.
On top of the memory leak, the issue that I'm facing is that I don't want to dispose the bag on viewWillDisappear, but rather when the view is popped. I need it to stick around if I add a view on top in case I pop back to this view.
Any help would be greatly appreciated!
Your theory is correct. You need to use a weak or unowned reference to self in your subscribe methods rather than a strong reference. And get rid of the assignment to disposeBag in the viewWillDissapear. The disposeBag will properly dispose of your subscribers when the object gets deinted.
You setup a weak reference to self like this:
basePresenter.testVariable.asDriver().drive(onNext: { [weak self] test in
if !test.id.isEmpty {
self?.testLabel.text = "Test text" // no longer a strong reference
}
}).disposed(by: disposeBag)
I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}
[Problem soluted!Just want to know why there is such a difference in ios8 and ios9] I was making a register view controller these days and face with some problem about weak reference.
and below is some part of the code(swift)
problem come when I use an iphone6 ios8.1
it crashed. And then I noticed that the weak reference is not proper here. But the code runs well in my ios9 iphone6s. I ran this code on an iphone6 ios8 simulator, app crashed. So I think there is some thing different in processing weak reference in ios8 and ios9, But who can explain why..?
class VC: UIViewController {
weak var verifyTextField: UITextField?
override func viewdidload() {
//....
verifyTextField = newTextField();
view.addSubview(verifyTextField!);
}
func newTextField() -> UITextField {
let ntf = UITextField();
//do some settings to ntf;
return ntf;
}
}
You set your new UITextField instance to the weak var verifyTextField but before you add it as a subview (which increments the retain count) it is deallocated (the count is 0 since the var is weak) so verifyTextField! crashes, the crash you're getting is most likely the famous
Unexpectedly found nil while unwrapping an Optional
It's easy to fix it
Don't use a weak var
Don't force unwrap (use if let instead)
The code should be as follows:
class VC: UIViewController {
var verifyTextField: UITextField? //should not be weak
override func viewdidload() {
//....
verifyTextField = newTextField()
if let verifyTextField = verifyTextField {
view.addSubview(verifyTextField!)
}
}
func newTextField() -> UITextField {
let ntf = UITextField()
//do some settings to ntf
return ntf
}
}
Looks like your object is deallocated instantly after initialization because you don't store any strong reference for it.
Try this code:
override func viewdidload() {
//....
let verifyTextField = newTextField();
view.addSubview(verifyTextField);
self.verifyTextField = verifyTextField;
}
Also no need to use weak reference here, because verifyTextField doesn't have reference to your VC, so you won't get a retain cycle.