Do I need [unowned self] or [weak self] in this closure? - ios

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(netHex: 0xfc3158)
fadeBackground()
NSTimer.scheduledTimerWithTimeInterval(self.fadeTime, target: self, selector: Selector("fadeBackground"), userInfo: nil, repeats: true)
}
func fadeBackground(){
UIView.animateWithDuration(self.fadeTime, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: {
var randomIndex = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
self.view.backgroundColor = CONSTANTS.MainColorScheme[randomIndex]
}) { (stuff Bool) -> Void in
}
}
I'm a little confused about why I need to use [unowned self]. According to this answer, I should only use [unowned self] if I don't care that self is still around when the closure is called. But I never see why that would ever be the case. Why would I not care if self is around? I want self to be around when the closure is called -- that's why I wrote the code there.
Do I need unowned self in the animations closure?

In this case you don't need to use capture list, because both closures are UIView and not retained by self. The retain cycle is not created in your example.

Related

want to set a self var using guard statement

I have this code, where I'm trying to set a self variable (self?.users) from a view model call. The code snippet looks like this.
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.global().async { [weak self] in
self?.model?.findAll() { [weak self] users, exception in // network call
guard users != nil, self?.users = users else { // Optional type ()? cannot be used as a boolean; test for !=nil instead
}
}
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
I'm capturing [weak self] twice, is that okay?, can I capture it once as weak in the enclosing closure?
Should I use this instead of guard statement?
self?.model?.findAll() { [weak self] users, exception in
if exception != nil {
self?.users = users
}
}
DispatchQueue closures don't cause retain cycles so capture lists are not necessary.
Something like this, to avoid confusion I'd recommend to rename the incoming users and the code to reload the table view must be inside the closure
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global().async {
self.model?.findAll() { [weak self] foundUsers, exception in // network call
guard let foundUsers = foundUsers else { return }
self?.users = foundUsers
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
And don’t forget to call super

View Controller is not deinitialized when expected

I am trying to understand the difference between doing a task in serial async DispatchQueue and then doing the same task in a method/function by referencing it through a weak self from the same DispatchQueue.
Code - 1
An asyncAfter block is executed after 1.5 seconds while task() is processing which dismisses the view controller but deinit() is only called after task() in finished.
class NewViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
self.dismiss(animated: false, completion: nil)
})
let queue = DispatchQueue(label: "myQueue", qos: .default)
queue.async {[weak self] in
self?.task()
}
}
private func task() {
print("start")
for i in 0...10000000 {
if i%500 == 0 {
}
}
print("stop")
}
deinit {
print("deinit")
}
}
Console output = start stop deinit
Code - 2
In this case deinit() is called as expected just after the view controller is dismissed.
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
self.dismiss(animated: false, completion: nil)
})
let queue = DispatchQueue(label: "myQueue", qos: .default)
queue.async {[weak self] in
print("start")
for i in 0...10000000 {
if i%500 == 0 {
}
}
print("stop")
}
}
deinit {
print("deinit")
}
Console output = start deinit stop
Can somebody please explain why is there a difference between the two?
Although both closures use weak self, the first invokes a function on the view controller. This causes the view controller to be retained until that function returns.
If it wasn't then the view controller would be released while the function was still executing, which would be a Bad Thing
In the second closure, there is no reference to self so the closure is simply retained by the dispatch queue.

Cell isn't being deallocated after table view is dismissed, being referenced by closure's context

I'm creating a custom table view cell, which allows the user to add, take, or view uploaded photos.
I discovered that this cell stays in memory forever even after the table view's dismissal, creating weird memory graph. I want the cell to be dismissed properly, but I have a hard time understanding what is going on.
The graph shows that my cell is being strongly referenced by a addPhotoTapAction.context.
addPhotoTapAction: ((ItemInfoCell) -> Void)? is the cell's class variable, used to store the closure handling user input. The closure is defined in the view controller:
let infocell = tableView.dequeueReusableCell(withIdentifier: K.infocellID) as! ItemInfoCell
if item?.imageUrl == nil {
self.imageManager.actionController?.actions[2].isEnabled = false
} else {
self.imageManager.actionController?.actions[2].isEnabled = true
}
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
self.imageManager.pickImage(self) { [weak self] image in
self?.imageToSave = image
infocell.itemPhoto.image = self?.imageToSave
infocell.addPhotoButton.tintColor = UIColor(ciColor: .clear)
infocell.addPhotoButton.isEnabled = true
self?.imageManager.actionController?.actions[2].isEnabled = true
}
The pickImage method is shown below. It's used to present action controller with image picker options (take photo or choose from lib):
func pickImage(_ viewController: UIViewController, _ callback: #escaping ((UIImage) -> ())) {
picker.delegate = self
picker.mediaTypes = ["public.image"]
picker.allowsEditing = true
pickImageCallback = callback
self.viewController = viewController
actionController!.popoverPresentationController?.sourceView = viewController.view
viewController.present(actionController!, animated: true, completion: nil)
}
...and the callback is stored to be used in picker's didFinishPickingMediaWithInfo call:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
if let image = info[.editedImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
} else if let image = info[.originalImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
}
viewController = nil
}
I've tried to manually set variable with closure to nil, then switched from [weak self] to [unowned self] to the combination of both. No luck.
I think that either the pickImage(self), or using the class' properties within the closure are creating a strong reference even when using [weak/unowned] capture lists, but I'm still not sure and not being able to fix it.
Update: ItemInfoCell class' code
class ItemInfoCell: UITableViewCell {
#IBOutlet weak var itemPhoto: UIImageView!
#IBOutlet weak var itemLabel: UILabel!
#IBOutlet weak var addPhotoButton: UIButton!
var addPhotoTapAction: ((ItemInfoCell) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func takePhoto(_ sender: Any) {
if let addPhoto = self.addPhotoTapAction {
addPhoto(self)
}
}
}
The problem is that you access the infocell inside it's callback. If you use variable inside own callback, you should mark it as a weak by adding it to the capture list.
In your code it should be like this:
infocell.addPhotoTapAction = { [unowned self, weak infocell] _ in
...
}
In your code, there is a strong reference from infocell to closure and inside closure you are referring to infocell i.e. both have a strong reference to each other.
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
...
}
This leads to a retain cycle and doesn't allow the cell to be deallocated.
Since you are already using capture list for self, you can easily solve this issue by adding infocell to the capture list, like so:
infocell.addPhotoTapAction = { [weak self, weak infocell] _ in
guard let self = self else { return }
infocell.addPhotoButton.isEnabled = false
...
}
Generally speaking, it is suggested to use unowned only when you are completely sure that the reference will not become nil before execution of the closure, because using unowned is like forcefully unwrapping an optional value. If it is nil your app will crash. So weak self is generally the safer way to go.

Use closure instead selector argument for UIBarButtonItem BUT without using weak self

In order to use closure in argument of UIBarButtonItem I am using a subclass:
class ActionBarButtonItem: UIBarButtonItem {
private var actionHandler: (() -> Void)?
convenience init(title: String?, style: UIBarButtonItemStyle, actionHandler: (() -> Void)?) {
self.init(title: title, style: style, target: nil, action: #selector(barButtonItemPressed))
self.target = self
self.actionHandler = actionHandler
}
convenience init(image: UIImage?, style: UIBarButtonItemStyle, actionHandler: (() -> Void)?) {
self.init(image: image, style: style, target: nil, action: #selector(barButtonItemPressed))
self.target = self
self.actionHandler = actionHandler
}
#objc func barButtonItemPressed(sender: UIBarButtonItem) {
actionHandler?()
}
}
but now I need to weak [weak self] :
self.add(barButton: .menu, position: .left) { [weak self] in
guard let strongSelf = self else {return}
strongSelf.openMenu()
}
is there a way to still use closure as selector but not save the closure to avoid using weak self everywhere and you may forget it somewhere ?
In a word, no.
You have to save the closure if you're going to call it later. If you're saving a closure, and that closure refers to self, you should make self part of a capture list to avoid a retain cycle. That's what capture lists are for, and is the correct coding pattern for this situation.
Any time you refer to self in a closure you need to stop and think about retain cycles.

Typewriter effect text animation

I'm trying to create a typewriter animation effect with a UILabel, but can't find any answers. Is the UILabel the correct object to use? I want the text to print to the screen an array of strings like, "Logging in... Opening Folder... Rebooting system.." etc. I should mention that I'm new to coding and I've tried searching the Documentation and API reference but no luck. I'm currently learning SWIFT if thats worth mentioning
Based on this Answer:
Letter by letter animation for UILabel?
I've updated it to Swift 4 and solved the CPU animation problem with DispatchWorkItem in order to create a queue.
Swift 4
extension UILabel {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 5.0) {
text = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.text!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
Usage
label.setTextWithTypeAnimation(typedText: text, characterDelay: 10) //less delay is faster
Swift 5
func setTyping(text: String, characterDelay: TimeInterval = 5.0) {
self.text = ""
let writingTask = DispatchWorkItem { [weak self] in
text.forEach { char in
DispatchQueue.main.async {
self?.text?.append(char)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
let queue: DispatchQueue = .init(label: "typespeed", qos: .userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: writingTask)
}
Usage
label.setTyping(text: "Your text")
update: Xcode 7.0 GM • Swift 2.0
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myTypeWriter: UITextField!
let myText = Array("Hello World !!!".characters)
var myCounter = 0
var timer:NSTimer?
func fireTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "typeLetter", userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
myTypeWriter.text = myTypeWriter.text! + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval, target: self, selector: "typeLetter", userInfo: nil, repeats: false)
} else {
timer?.invalidate()
}
myCounter++
}
override func viewDidLoad() {
super.viewDidLoad()
fireTimer()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have written a subclass of UILabel called CLTypingLabel, available on GitHub. This should do what you want.
After installing CocoaPods, add the following like to your Podfile to use it:
pod 'CLTypingLabel'
Sample Code
Change the class of a label from UILabel to CLTypingLabel;
#IBOutlet weak var myTypeWriterLabel: CLTypingLabel!
At runtime, set text of the label will trigger animation automatically:
myTypeWriterLabel.text = "This is a demo of typing label animation..."
You can customize time interval between each character:
myTypeWriterLabel.charInterval = 0.08 //optional, default is 0.1
You can pause the typing animation at any time:
myTypeWriterLabel.pauseTyping() //this will pause the typing animation
myTypeWriterLabel.continueTyping() //this will continue paused typing animation
Also there is a sample project that comes with cocoapods
my version of the typewriter effect animation using a timer:
var text = "text"
_ = Timer.scheduledTimer(
withTimeInterval: 0.1,
repeats: true
) { [weak self] timer in
let char = text.removeFirst()
self?.yourLabel.text?.append(char.description)
if text.isEmpty {
timer.invalidate()
}
}

Resources