Today we will discuss about one of the feature in which Objective-c is better than swift. We all have read Turtle & Rabbit story. To start with today's discussion, let's treat topic as turtle & rabbit race where Objective-c is Turtle & Swift is Rabbit.
Today Discussion Topic:
1) Run Time Flexibility
2) Dynamism
On Your Mark:
Apple introduced swift with the tagline "more safer than Objective-C". As we know to win something, have to lose something. So to become safer swift becomes Statically Typed Language. Statically Typed Language is one which does type checking at compile time. However, the old turtle (Objective-C) is a runtime language i.e. Most of the linkage between your methods, ivar and classes are checked at runtime.
What benefit you will get with run time flexibility?
These all features are great and give you more flexibility but there is a phrase that "With Great power comes great responsibility :)"
If you are using Objective-c runtime for your implementation, you need to take care of some points as behavior is unexpected and debugging is very difficult with Dynamic code.
Before going through the various feature of runtime, lets check what is object and class in Objective-c.
Just Tap on the above line in Xcode and you will land to "runtime.h" interface. Just check the definition of struct "objc_class".
It is a simple struct and contains list of members. Let's go through the members required to understand runtime of objective-c:
isa:
Points to meta class of an object. If it is a meta class then it points to super meta class. If it is root class(NSObject) then isa points to itself.
super_class:
As the name suggests points to the super class. If it is root class(NSObject) then points to Null.
ivars:
pointer to objc_ivar_list data structure containing list of all the instance variable of class.
info:
Contains bit flags used by the Objective-c runtime to check various property related to class like: CLS_METHOD_ARRAY (0x100L) bit flag indicates that the methodLists field is an array of pointers to
methodLists:
Based on the flag CLS_METHOD_ARRAY in info member It will be array of pointer to objc_method_list data structure which collectively contains all the instance methods for class if flag is not set then it is pointer to objc_method_list which contains all the instance methods. Array of Pointer to objc_method_list help to maintain category method of a class.
protocols:
pointer to objc_protocol_list data structure containing list of all the protocols implemented by the class.
Points to the class definition of this instance.
Let's check some example of isa pointer. I have declared the following class:
Let's start the Race Now and check various capability provided by the runtime of objective-c.What benefit you will get with run time flexibility?
1) Fix Critically issue in deployed application on app store: Method Swizzling can be used to fix any critical issue in the application which already distributed through the app store. Let suppose you created an application by supporting method swizzling for every class. In load method of every class you check if any new implementation comes for the method then replace with the new implementation. You can push new implementation from the server.
2) Fix Crash in Third Party Library: You can analyze the private API of Apple iOS SDK and third-party frameworks (not open source). You can even modify the third party framework source code as per your requirement but at your own risk. Let suppose you are using a third party library which has some amazing features but the developer left a bug in that framework. He/She missed to implement a method and your application start crashing because run time did not find method implementation. Now there are two option either you contact the developer and ask him to fix the issue and provide you updated code or you can use runtime feature and add(class_addMethod) a implementation in the class.
3) JSON Parsing: Runtime can be used to map between JSON and Modal Class. You can introspect the class properties and assign the value at run time.
If you are using Objective-c runtime for your implementation, you need to take care of some points as behavior is unexpected and debugging is very difficult with Dynamic code.
Get Set:
To use the objective-c runtime feature you need to have the objective-c run time library and good news is that it by default exist with Xcode. You just need to import in your code and following is the code snippet:
#import <objc/runtime.h>
Before going through the various feature of runtime, lets check what is object and class in Objective-c.
Just Tap on the above line in Xcode and you will land to "runtime.h" interface. Just check the definition of struct "objc_class".
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
It is a simple struct and contains list of members. Let's go through the members required to understand runtime of objective-c:
isa:
Points to meta class of an object. If it is a meta class then it points to super meta class. If it is root class(NSObject) then isa points to itself.
super_class:
As the name suggests points to the super class. If it is root class(NSObject) then points to Null.
ivars:
pointer to objc_ivar_list data structure containing list of all the instance variable of class.
info:
Contains bit flags used by the Objective-c runtime to check various property related to class like: CLS_METHOD_ARRAY (0x100L) bit flag indicates that the methodLists field is an array of pointers to
objc_method_list
data structures rather than a pointer to a single objc_method_list
data structure.methodLists:
Based on the flag CLS_METHOD_ARRAY in info member It will be array of pointer to objc_method_list data structure which collectively contains all the instance methods for class if flag is not set then it is pointer to objc_method_list which contains all the instance methods. Array of Pointer to objc_method_list help to maintain category method of a class.
protocols:
pointer to objc_protocol_list data structure containing list of all the protocols implemented by the class.
Let's check what is object in objective-c:
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; };
It is much simpler than objective-c class struct. Just contain a single member "isa".
isa:Points to the class definition of this instance.
Let's check some example of isa pointer. I have declared the following class:
@interface RuntimeTest : NSObject @end @implementation RuntimeTest @end
Guess !!! Output of following:
RuntimeTest *test = [[RuntimeTest alloc] init]; Class testClass = test->isa; //Guess Output testClass = testClass->isa; //Guess Output testClass = testClass->isa; //Guess Output
Go:
Hurdle 1 (Adding a Class or Member):
Adding a Class at runtime:
We saw that Class and Object are very simple struct in objective-c. This provides us a great feature to create a class at runtime. It is used in many ways like if you do not know what keys and value will come in JSON data then you can create a class at run time and add the ivars according to the current properties.
Class JsonModal = objc_allocateClassPair([NSObject class], "JsonClass", 0); objc_registerClassPair(JsonModal);
Adding a Method at runtime:
Let suppose you find an amazing library for network operation. You used the library in your application for all the network task. One day while using the library, you find a crash in the library that
[KNetworkOp logOperation:] Unrecognized selector sent to Instance KNetworkOp.
How to deal with this situation. You do not have source Code of the library. Again Objective-C Runtime here to rescue you:
You can add a new definition here as by crash logs you can see that method is only for the logging the operation. You can add an empty definition of the operation:
void logOperation ( id self, SEL _cmd, id operation) { NSLog (@"Hello"); } class_addMethod([KNetworkOp class], @selector(logOperation:), (IMP)logOperation,"v@:@");
Hurdle 2 (Introspection)
It is the capability of an object to disclose details about itself at runtime. NSObject class and protocol provide various introspection methods to know the various properties of an object at runtime. Like a class of object can respond to the message (method), whether it conforms to a specific protocol. Following is the list of introspection methods:
NSString* introspectionTest = @"Test"; //Check object type at runtime if([introspectionTest isKindOfClass:[NSString class]]){ } //Check can respond to message if([introspectionTest respondsToSelector:@selector(isEqualToString:)]){ [introspectionTest isEqualToString:@"Test"]; } //Check confirms to protocol if([introspectionTest conformsToProtocol:@protocol(NSCopying)]){ NSString *copyText = [introspectionTest copy]; }
Now question arises how at runtime these things are known?
Every Instance has isa member which point to the class of the instance and class has the list of all the protocol and method to which instance can respond. So, in this way the simple struct of object and class gives great flexibility to the runtime of objective-c.
is it possible in swift? 🤔
Yes, you can use the same method in swift if your swift object is inherited from NSObject. i.e you need to use objective-c runtime.
//Check object type at runtime if self.isKind(of: UIViewController.self){ } //Check can respond to message if self.responds(to: #selector(setter: title)){ self.title = "Test" } //Check confirms to protocol if self.conforms(to: (NSCoding.self)){ }
These methods only available if object class directly or indirectly derived from NSObject. So you cannot call these methods with swift object like String, Dictionary, Array.
Hurdle 3 (Category):
Adding a ivar in category:
One of the challenges, we as iOS developer, are facing that we cannot add a stored property in a category. Again objective-c runtime come here to help us by providing following are set of methods:- objc_setAssociatedObject
- objc_getAssociatedObject
//interface @interface NSDate (AssociatedObject) @property (nonatomic, assign) NSNumber *day; @end //Implementation #import "NSDate+AssociatedObject.h" #import <objc/runtime.h> @implementation NSDate (AssociatedObject) @dynamic day; - (void) setDay:(NSNumber*)day{ objc_setAssociatedObject(self, @selector(day), day, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSNumber*) day{ return objc_getAssociatedObject(self, @selector(day)); } @end
So, by using objective-c runtime you can add the stored property in the category and it is very helpful in various scenarios like keep request type in URLSessionTask class, maintaining the state with UIComponent like NSIndexPath with UIButton.
Swift extensions are similar to objective-c. However, to add any member variable in swift extension you need to use Objective-c runtime.
Hurdle 4 (Method Swizzling):
Changing Method Implementation at runtime:
For example, let's say you are using an analytic tool in your application and Business wants to track that which screen is most viewed by the user in your application. So, they gave you requirements to track appear on each view controller. There are multiple ways to do it:
1) Write a tracking logic for each view controller. In this way, there is duplicate code in each view controller.
2) Subclass is the other option which reduce the duplicity at some extent not fully. As you need to subclass different type of View controller like UIViewController, UITableViewController, UICollectionViewController.
3) Method Swizzling is the other way. It has also some disadvantage like code readability, Debugging but it solve our code duplicity problem.
@implementation UIViewController (Runtime) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(swizzle_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Swizzle - (void)swizzle_viewWillAppear:(BOOL)animated { [self swizzle_viewWillAppear:animated]; [TAnalyticTool trackLoadViewController:self]; }
Presentation Ceremony:
Let discuss about swift how it can handle the above use case:
Swift:
As you know that swift is statically typed language and the default types in swift are safe. It makes sure everything should be initialized before use. So, dynamism in swift can only be achieved by using objective-c.
In swift, there are two keywords @objc & @dyanmic which expose swift to Objective-c runtime. So you can use all the runtime feature in swift also using objective-c runtime.
Now the question comes what swift can do standalone without the help of objective-c lets check:
Introspection:
Yes, there is an alternative of isMemberOfClass method of objective-c in swift. You can use ' is' to introspect at run time. It works with swift value type also:
let test = ["key": "Introspection", "key1":"Swift"] if test is String{ print("test is string") }else{ print("test is dictionary") }
Category:
Swift also provide the powerful feature of adding features/methods to the existing object. Similar to objective-c, swift also does not support adding instance variable directly. But you can use objective-c runtime feature in swift to add variables.
import ObjectiveC // Declare a global var to produce a unique address as the assoc object handle private var AssociatedObjectHandle: UInt8 = 0 extension MyClass { var stringProperty:String { get { return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String } set { objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }
Ref:is-there-a-way-to-set-associated-objects-in-swift
Class & Member Addition:
You cannot add method and class in swift at runtime by using swift only. However, by using objective-c runtime method in swift you can add method & class at run time.
Method Swizzling:
Again swift does not has the capability to swizzle a method definitions at run time. But here also you can use the objective-c run time to swizzle methods in swift.
Overall, Swift is an exciting language with so many new features. Dynamism is the one feature in which swift is still lacking. Let us hope that apple soon introduce this features in swift.
Open Question:
Why the following code snippet is not infinite recursion in case of method swizzling? You can answer in comments :)
- (void)swizzle_viewWillAppear:(BOOL)animated { [self swizzle_viewWillAppear:animated]; [TAnalyticTool trackLoadViewController:self]; }
Comments
Post a Comment