Showing posts with label runtime. Show all posts
Showing posts with label runtime. Show all posts

Sunday, November 5, 2017

Swift 4 Method Swizzling (Part 2/2)

In the Swift 4 Method Swizzling (Part 1/2) post, We have seen the approach of using Base classes for a notification needed 
whenever a view appears, Which resulted in code redundancy.

In this post, Let’s see how that can be solved using Method Swizzling.

Take an extension of UIViewController and apply swizzling for it’s viewWillAppear method.

--------------------------------------------------------------------------

extension UIViewController {

    func newViewWillAppear(_ animated: Bool) {
        self.newViewWillAppear(animated) //Incase we need to override this method
        print("Display Notification!")
    }
    
    static func swizzleViewWillAppear() {
         //Make sure This isn't a subclass of UIViewController, So that It applies to all UIViewController childs
        if self != UIViewController.self {
            return
        }
        let _: () = {
            let originalSelector = #selector(UIViewController.viewWillAppear(_:))
            let swizzledSelector = #selector(UIViewController.newViewWillAppear(_:))
            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
            method_exchangeImplementations(originalMethod!, swizzledMethod!);
        }()
    }    
}

--------------------------------------------------------------------------

Here, I am swapping, viewWillAppear with my custom Method newViewWillAppear using method_exchangeImplementations, Which is from ObjectiveC runtime.

--------------------------------------------------------------------------
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
--------------------------------------------------------------------------

At run time, calling viewWillAppear on UIViewController calls newViewWillAppear method, 
Where we will have the notification logic.

Now, In the child classes of UIViewControllers, super.ViewWillAppear, Calls the newViewWillAppear.

The beauty here is, The same will get called for UITableViewController and UINavigationController also as they are also extended from UIViewController.

Thus, Using Method Swizzling, There will not be any code redundancy. 

Unlike in Swift3x, In swift 4, We can not write this swizzling the initialise method, And we can call it in AppDelegate.

--------------------------------------------------------------------------

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        // Override point for customization after application launch.

        UIViewController.swizzleViewWillAppear()

        return true
    }

--------------------------------------------------------------------------

So, Whenever app launches, This swizzling applies and stays.

Hope this post is useful. Feel free to comment incase of any queries.




Swift 4 Method Swizzling (Part 1/2)

Method Swizzling is the process of changing the implementation of methods of a class at runtime.

Let’s see some use cases of Method Swizzling.

I want to do something before all of my ViewController’s ViewWillAppear, 

Like,

  • I need to display an alert to the user If the auth token expires and user has to Re-Login
  • I need to display an offer to the user as a Banner whatever may be the screen that user presently is in.


In these cases, Actually we can go with a Base class and write our logic in the Base class.

But this is not the correct approach. 

Because, We need to write Base classes for not only UIViewController, But also for UITableViewController, UINavigationController, UITabBarController and all which we use in our app.

If we go with Base classes, Our code will be redundant and there will be multiple base classes for the same piece of code.

Let’s see a code example of Base class approach, And then we will see the how Method Swizzling solves that without code redundancy.

I have two View Controllers.

------------------------------------------------------------------------

class ViewController: BaseViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
}

class ViewController2: BaseViewController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }    
}

------------------------------------------------------------------------

In my Base class I will just write the log and consider that as my logic I needed to get executed before my ViewWillAppear.

------------------------------------------------------------------------

class BaseViewController: UIViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("UIViewController - Display Notification!")
    }
    
}

------------------------------------------------------------------------

We are good here, As both View Controllers are Inherited from BaseViewControllerOur notification will get fired at the time of these controllers viewWillAppear.

Now, There is a TableViewController.

------------------------------------------------------------------------

class TableViewController: UITableViewController {
    
    let users = ["user1", "user2", "user3", "user4"]

    // MARK: - Table view data source
    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return users.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
        cell.textLabel?.text = users[indexPath.row]
        return cell
    }
    
}

------------------------------------------------------------------------

Obviously, I can not extend it from BaseViewController, Cause, If I do, We will end up with errors like shown below.



















That means, UITableViewController will lose it’s characteristics If we go with BaseViewController as a base class and there is no point in taking a UITableViewController

Same applies to UINavigationController.

------------------------------------------------------------------------

class NavigationController: UINavigationController {
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
}

------------------------------------------------------------------------

If we take BaseViewController as base class, UINavigationController loses it’s characteristics and your push and pop will not work.

So for UITableViewController and UINavigationControllers also, We need to take Base Classes.

------------------------------------------------------------------------

class BaseTableViewController: UITableViewController {

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("UITableViewController - Display Notification")
    }  
}

------------------------------------------------------------------------

Such a bad approach with such a code redundancy.

In the next tutorial, Let’s see how this can be solved using Method Swizzling.

Hope this post is useful. Feel free to comment incase of any queries.