Lazy function in Swift - ios

Could anyone tell me why in this 'odd' code (I'm having fun with Swift ;D) in lazy functions runEngine and stopEngine, print method is never executed?
(please run this code in playground).
Thanks!
protocol EngineDelegate {
func engineDidStart()
func engineDidStop()
}
class Engine {
var delegate: EngineDelegate?
lazy var runEngine : () -> () = {
print("Engine has been started")
self.delegate?.engineDidStart()
}
lazy var stopEngine : () -> () = {
print("Engine has been stoped")
self.delegate?.engineDidStop()
}
}
class Car: EngineDelegate {
let engine = Engine()
init() {
engine.delegate = self
}
func engineDidStop() {
print("MyOwnStop")
}
func engineDidStart() {
print("MyOwnStart")
}
}
let car = Car()
car.engine.runEngine()

The code runs as expected for me.
At first I thought that the lazy modifier was unnecessary but it is. When Engine is instantiated, its delegate is nil and that value is what is captured by the closure. Using lazy deferred that capture until its use which by that time engine.delegate had been set. While we might be able to use #autoclosure somehow, the best solution is to just make runEngine and stopEngine functions.
func runEngine() {
print("Engine has been started")
delegate?.engineDidStart()
}
func stopEngine() {
print("Engine has been stoped")
delegate?.engineDidStop()
}

Related

How can I move a loadCategories() func from SomeViewController to SomeViewModel?

So I have the following function:
var categoryArray: [Category] = []
private func loadCategories() {
downloadCategoriesFromFirebase { (allCategories) in
self.categoryArray = allCategories
self.collectionView.reloadData()
}
}
that is currently in my SomeViewController. I need to move it to SomeViewModel.
Sorry if my question is not well formulated, please do ask if you need any more information since I am so new at all this.
Thanks in advance
Assuming you have a reference to SomeViewModel in your SomeViewController, you can move the code over there.
For example:
import UIKit
class ViewController: UIViewController {
private var viewModel: ViewModel!
override func viewDidLoad() {
super.viewDidLoad()
callToViewModel()
}
func callToViewModel() {
viewModel.loadCategories {
self.collectionView.reloadData()
}
// rest of your code
}
The ViewModel could look something like this:
import Foundation
class ViewModel: NSObject {
var categoryArray: [Category] = []
override init() {
super.init()
}
func loadCategories(completion: (() -> Void)?) {
downloadCategoriesFromFirebase { (allCategories) in
self.categoryArray = allCategories
completion?()
}
}
}
When reloading your data, make sure to refer to viewModel.categoryArray instead of self.categoryArray now.
Please notice that I'm making use of a completion handler to notify the ViewController the call in the ViewModel was finished. There are other (perhaps better) ways to do this, like the Combine framework.

Initialising member to class function causes 'self' used in method call error

I have a class attribute that points to one of the class functions. However when I try to initialize this variable with one of the functions, I get the following error:
'self' used in method call before all stored properties are initialized.
I'm able to initialize any other variable to those functions, but the error makes it sound like I'm calling the function even though I'm not.
import UIKit
import AudioToolbox
class BeatMaker {
// iPhone 7 and up use beat function, iPhone 6s use beatFallback
let hapticFunction: () -> ()
let impactGenerator = UIImpactFeedbackGenerator.init(style: .heavy)
init(supportsImpactGenerator: Bool) {
// error 1: 'self' used in method call 'beat' before all stored properties are initialized
// error 2: 'self' used in method call 'beatFallback' before all stored properties are initialized
self.hapticFunction = (supportsImpactGenerator) ? beat : beatFallback
}
private func beat() {
impactGenerator.impactOccurred()
}
private func beatFallback() {
AudioServicesPlaySystemSound(1520)
}
func makeABeat() {
hapticFunction()
}
}
In this specific case I want to make use of the Taptic Engine and simulate a click through the UIImpactFeedbackGenerator. The iPhone 6s doesn't support this engine, so I want to call a fallback function that produces a similar effect.
I also tried initializing the variable on the spot:
// this works
var hapticFunction: (BeatMaker) -> () -> () = beat
init(supportsImpactGenerator: Bool) {
if !supportsImpactGenerator {
// error: Cannot assign value of type '() -> ()' to type '(BeatMaker) -> () -> ()'
self.hapticFunction = beatFallback
// produces same error
self.hapticFunction = beatFallback.self
}
}
I know that I could make everything static or put everything out of the class, but this feels like it should work yet it doesn't. Am I missing something?
EDIT
Setting the type of type of hapticFunction to an optional seems to work, but this doesn't make any sense to me. What's the difference?
// this works
var hapticFunction: (() -> ())?
init(supportsImpactGenerator: Bool) {
self.hapticFunction = (supportsImpactGenerator) ? beat : beatFallback
}
It might be better to not use a Bool, but rather a nested Enum, which is also more extendible if you wanna add some other modes of haptic feedback later on.
I have a generalized solution for a generalized problem of your question. So either you do:
public class FunctionOwner {
private let mode: Mode
public init(`do` mode: Mode = .default) {
self.mode = mode
}
}
public extension FunctionOwner {
enum Mode {
case foo, bar
}
func fooOrBar() {
switch mode {
case .foo: foo()
case .bar: bar()
}
}
}
private extension FunctionOwner {
func foo() {
print("doing foo")
}
func bar() {
print("doing bar")
}
}
public extension FunctionOwner.Mode {
static var `default`: FunctionOwner.Mode {
return .foo
}
}
// USAGE
FunctionOwner(do: .bar).fooOrBar() // prints "doing foo"
FunctionOwner(do: .foo).fooOrBar() // prints "doing bar"
Or if you for some reason do want to keep the stored Mode, you can do this (might be relevant for your actual question on how you do a workaround of referencing self in the init.):
public class FunctionOwner {
private let _function: (FunctionOwner) -> Void
public init(`do` mode: Mode = .default) {
_function = { functionOwner in
switch mode {
case .foo: functionOwner.foo()
case .bar: functionOwner.bar()
}
}
}
}
public extension FunctionOwner {
enum Mode {
case foo, bar
}
func fooOrBar() {
_function(self)
}
}
// The rest of the code is the same as the example above
There are two method to fix this
You can:
Give hapticFunction a initial value like {}
or fix like:
class BeatMaker {
let impactGenerator = UIImpactFeedbackGenerator.init(style: .heavy)
let supportsImpactGenerator: Bool
init(supportsImpactGenerator: Bool) {
self.supportsImpactGenerator = supportsImpactGenerator
}
private func beat() {
if supportsImpactGenerator {
impactGenerator.impactOccurred()
} else {
AudioServicesPlaySystemSound(1520)
}
}
func makeABeat() {
beat()
}
}

How to show/hide the progressHUD, with MVVM and RxSwift in swift

I'm beginning with MVVM in order to well separate logic code from the view. But I have some concern about where to put the progressHUD related code when tapping a button that makes a request.
Before, I used to do that:
//Before
#IBAction func startRequestTapped() {
SVProgressHUD.show()
self.apiClient.requestObservable().subscribe(onError: { (error) in
SVProgressHUD.hide()
}, onCompleted: {
SVProgressHUD.hide()
})
}
But when I use mvvm, I do like that:
//In the viewModel
public var validateButtonDidTap = PublishSubject<Void>()
init() {
validateButtonDidTap.flatMap { (_)
return self.apiClient.requestObservable()
}
}
// In the viewController
viewDidLoad() {
let tap = self.validateButton.rx.tap
tap.bindTo(self.viewModel.validateButtonDidTap)
}
And amongst that, I don't know where to put the the ProgressHUD hide or show.
Mark answer is right, but I am going to guide you step by step.
Let's supose you're going to try signing in.
Copy ActivityIndicator.swift file in your project.
In the viewModel:
//MARK: - Properties
/// The http client
private let apiClient: YourApiClient
/// Clousure when button is tapped
var didTappedButton: () -> Void = {}
/// The user
var user: Observable<User>
/// Is signing process in progress
let signingIn: Observable<Bool> = ActivityIndicator().asObservable()
//MARK: - Initialization
init(client: YourApiClient) {
self.client = client
self.didTappedButton = { [weak self] in
self.user = self.apiClient
.yourSignInRequest()
.trackActivity(self.signingIn)
.observeOn(MainScheduler.instance)
}
}
Create an extension of SVProgressHUD: (I don't know SVProgressHUD library, but it would be something like this. Please fix it if needed)
extension Reactive where Base: SVProgressHUD {
/// Bindable sink for `show()`, `hide()` methods.
public static var isAnimating: UIBindingObserver<Base, Bool> {
return UIBindingObserver(UIElement: self.base) { progressHUD, isVisible in
if isVisible {
progressHUD.show() // or other show methods
} else {
progressHUD.dismiss() // or other hide methods
}
}
}
}
In your viewController:
#IBAction func startRequestTapped() {
viewModel.didTappedButton()
}
override func viewDidLoad() {
// ...
viewModel.signingIn
.bindTo(SVProgressHUD.rx.isAnimating)
.addDisposableTo(disposeBag)
}
Accepted answer updated to Swift 4, RxSwift 4.0.0 and SVProgressHUD 2.2.2:
3- Extension:
extension Reactive where Base: SVProgressHUD {
public static var isAnimating: Binder<Bool> {
return Binder(UIApplication.shared) {progressHUD, isVisible in
if isVisible {
SVProgressHUD.show()
} else {
SVProgressHUD.dismiss()
}
}
}
}
4- Controller:
viewModel.signingIn.asObservable().bind(to: SVProgressHUD.rx.isAnimating).disposed(by: disposeBag)
You could try using an ActivityIndicator.
See the example here:
https://github.com/RxSwiftCommunity/RxSwiftUtilities

How to use #objc protocol with optional and extensions at the same time?

This code does not compile and might sound stupid as it is, but i'll explain why it's so important!
#objc protocol p {
optional func f1()
func f2()
}
extension p {
func f1() { }
func f2() { }
}
class foo: p {
}
Compiler says Type c does not conform to protocol 'p' and that's maybe because you can not use #objc optional and extensions at the same time (and does not make sence in this scenario either). But consider the following example:
I want to set a selector on a non-optional method defined in protocol in my extension (main reason i used #objc):
func f1() { } -> func f1() { ... #selector(Self.f2) ... }
And i also want my f2() function to have default behaviour. If i mark f2() as optional, it can not be used in #selector because compiler does not know if this method actually exists in the case of need. Sure there're lots of nasty workarounds like global methods, sending Selectors to methods as input and etc, but is there a clean way to achieve it?
This is the practical issue
#objc
protocol Refreshable {
weak var refreshControl: UIRefreshControl? { get set }
optional func setupRefreshControl()
func refresh()
}
#objc
protocol ContentLoader {
func load(reset: Bool)
}
extension Refreshable where Self: ContentLoader {
func refresh() {
delay(0.75) { [weak self] in
self?.load(true)
}
}
}
extension Refreshable where Self: UICollectionViewController {
func setupRefreshControl() {
let newRefreshControl = UIRefreshControl()
newRefreshControl.tintColor = UIColor.grayColor()
newRefreshControl.addTarget(self, action: #selector(Self.refresh), forControlEvents: .ValueChanged)
collectionView?.addSubview(newRefreshControl)
refreshControl = newRefreshControl
}
}
Now if a ViewController implements Refreshable and ContentLoader, it does not find the default refresh function, but it does find setupRefreshControl. So i figured let's mark refresh as optional too, but by doing that, you can not send it to selector any more.
I even tried this:
func refresh() -> optional func refresh()
and
let str = "refresh"
let sel = Selector(str)
It silents the compiler yes, but does not work either... rises unrecognized selector sent to instance....
I think this is not possible in swift (because of the way it bridges to #objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.
fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil
func trigger() {
closure?()
}
}
// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"
protocol CustomProtocol: class {
func setup()
}
extension CustomProtocol where Self: NSObject {
fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}
set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}
let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)
// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)
self.associatedObject = object
}
func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}
class CustomClass: NSObject, CustomProtocol {}
let instance = CustomClass()
instance.setup()
I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.

block in Swift : return error " is not convertible to "

I made a mistake but I cannot see how to solve it. I would like to load all the assets from GameScene and send a Bool in a completion method. I use typealias : should it be renamed twice for the two files (gameScene and gameController)?
Then I have got an error on this line GameScene.loadSceneAssetsWithCompletionHandler{ :
((Bool) -> Void) is not convertible to 'GameScene'
Here is the code :
//gameController:
typealias OnComplete = (Bool) -> ()
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler{ (success:Bool)->Void in
println("2/ yes")
return
}
//gameScene : rewrite typealias?
typealias OnComplete = (Bool) -> ()
func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), { () -> Void in
self.loadSceneAssets()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
println("1/ yes")
completion(true)
})//main
})//global
}
I read some threads that said to add a "return", but it does not solve the error here.
Thanks
It's almost working, but you've got a couple things going wrong here. First of all, you can't redeclare a typealias. Second of all you're calling loadSceneAssetsWithCompletionHandler as a class function when it's set up as an instance function. Note changes:
typealias OnComplete = (Bool) -> ()
class GameController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
GameScene.loadSceneAssetsWithCompletionHandler { success in
println("2/ yes")
return
}
}
}
class GameScene: UIViewController {
func loadSceneAssets() {
}
class func loadSceneAssetsWithCompletionHandler( completion:OnComplete ) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let gameScene = GameScene()
gameScene.loadSceneAssets()
dispatch_async(dispatch_get_main_queue()) {
println("1/ yes")
completion(true)
}
}
}
}

Resources