OC the most practical summary of runtime, interview, work, you see, I will be enough!

Preface

Runtime has a lot of information online, some obscure, I summarize it again through their own learning methods, mainly about some common methods, with practical give priority to, I think is the most profound impression is used, and the implementation principle of the last two demo is MJExtension, the interview can also pull point.
also, there’s a lot more knowledge about runtime. For more information, I can read my official translation document (a bit boring), the demo download address of this article

What is runtime?

Runtime is the bottom of the OC set of C language API (objc/runtime.h&gt or &lt into <; objc/message.h>;), the compiler will eventually OC code into the runtime code, compile the.M file through the terminal command: clang -rewrite-objc xxx.m can see the compiled xxx.cpp (C++ file).
, for example, we created an object called [[NSObject alloc]init], which was eventually translated into tens of thousands of lines of code. The most critical sentence was to capture the underlying object created by runtime,

OC the most practical summary of runtime, interview, work, you see, I will be enough!
.Cpp file

Delete some mandatory conversion statements, you can see that the calling method is essentially the message, [[NSObject alloc]init] statement sent two messages, the first time sent a alloc message, the second time to send init messages. With this feature, we can explore the underlying principles, such as the implementation of block.
should note that using the objc_msgSend () sel_registerName () method requires importing the header file < objc/message.h>

OC the most practical summary of runtime, interview, work, you see, I will be enough!
message mechanism

In addition, using runtime can do some functions that OC is not easy to implement

  • The implementation of two methods of dynamic switching (especially the way the switching system comes with them)
  • Dynamically add member variables and member methods for objects
  • Get all members, methods, and all member variables of a class

How do I apply the runtime?

1. some OC code to run code, explore the bottom, such as block (it has talked about the realization principle); call method with
2. intercept system (Swizzle, imageNamed:, such as dark) to intercept viewDidLoad, alloc;
3. class can also increase the property; automatic archiving
4. and NSCoding automatic solution file;
5. automatic conversion dictionary and model.

Now I’m going through demo and I’ll explain them one by one

First, the exchange of two methods of implementation, the interception system comes with the method call function

Need to use the method of < objc/runtime.h> a class method of Method class_getClassMethod (Class CLS, SEL name) for instance object method of a class Method class_getInstanceMethod (Class CLS, SEL name) void method_exchangeImplementations in exchange for two methods (Method M1, Method m2)

Case 1: a simple exchange of methods

Create a Person class that implements the following two class methods in the class and declares in the.H file

+ (void) run {NSLog (@ run);} + (void) study {NSLog (@ learning ')}

Call in the controller, then print, run, and print learning

[Person run]; [Person study];

The following is done by runtime implementation methods, class methods using class_getClassMethod, and object methods using class_getInstanceMethod

/ / obtain two class Method M1 = class_getClassMethod ([Person class], @selector (run)); Method M2 = class_getClassMethod ([Person class], @selector (Study)); / / to the implementation of the method_exchangeImplementations method (M1, M2); exchange / exchange, print and print run learning! [Person run]; [Person study];
Case 2: interception system approach

Demand: for iOS6 upgrade iOS7 to version adaptation, according to different systems use different style pictures (skeuomorph and Bian Pinghua), how to modify each UIImage go through a manual imageNamed method can be achieved for the added version of the method in judgment statement?

1 steps:
, UIImage built a classification (UIImage+Category)
2, in implementing a custom method, method of writing to join in the system of statements, such as version of judgment

+ (UIImage *) xh_imageNamed: (* NSString) name version currentDevice].systemVersion doubleValue] {double = [[UIDevice; if (version > = 7) {/ / if the system version is more than 7, using another file name ends with "_os7" flat picture name [name stringByAppendingString:@ = "_os7"]}; return [UIImage xh_imageNamed:name];}

3, the classification of UIImage rewriting load method, exchange methods (as long as they can perform a method exchange statement, load is more appropriate)

(void + load) {/ / get the two class Method M1 = class_getClassMethod ([UIImage class], @selector (imageNamed:)); Method M2 = class_getClassMethod ([UIImage class], @selector (xh_imageNamed:)); / / to the implementation of the method_exchangeImplementations method (M1, M2); exchange}

Note: the custom method must finally calling system method, so that the picture of the loading function, but because the exchange method, system method has become the method of our custom name (around a bit, is to use our name to call the system, can we use the method call system the name of this system is realized), intercept method! Using the above ideas, we can also add to the NSObject classification, statistics to create many objects, add the classification to the controller, has created many statistical controller, especially when the company needs the total change, add a function on the original control or module, recommend the use of this method!

Two. Set attributes in the category and set properties for any object

As everyone knows, is to set the attribute classification, if only write @property in the classification statement to generate get and set methods for the statement, but is unable to generate a member variable, although some grammar can call out, but the execution of the program will be crash, people will think of the use of global variables? Like this:

Int _age; - - (int) age {return _age;} - - (void) setAge: (int) age {_age = age;}

But the memory program global variables of the entire implementation process in only one, we create multiple objects to modify its attribute value will modify the same variable, it will not be able to guarantee the same attribute like every object has its own attribute value. At this point, we need to use runtime to add attributes to classification.

Need to use the method of < objc/runtime.h> set method, the value of value with object (object associated with the value stored in the value object in object
: object) parameters to which object set property parameters of
key: an attribute corresponding to a Key, the future can key remove this stored value, key can be any type: double, int and so on, it is recommended to use char can save
parameters: value bytes to property values of the
parameters: policy storage strategy (assign, copy, retain strong void (ID) objc_setAssociatedObject object, const void *key, ID value, objc_AssociationPolicy Policy) with parameter key the corresponding storage object value in object take out the ID objc_getAssociatedObject (ID object, const void *key)

Step 1:
, create a classification, such as to any object to add a name attribute is added NSObject classification (NSObject+Category)
2, the first in the.H @property statement in a get and set method, convenient call syntax

@property (nonatomic, copy) NSString *name;

3. Rewrite the set and get methods in.M, internally using runtime to assign values and values to attributes

Char nameKey; - (void) setName: (NSString * name) {/ / a value to an object associated to a value stored in a objc_setAssociatedObject object (self, & nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);} - {return (NSString *) name objc_getAssociatedObject (self, & nameKey);}

Three. Get all the member variables of a class

The need for decodeObjectForKey: and encodeObject: of the object all the properties of the most typical usage is an object in the archive file reconciliation encodeWithCoder and initWithCoder: method, the runtime statement we no matter how many write attributes, do not need to modify the implementation of the code.

Need to use the method of < objc/runtime.h> all member variables of a class (the total number of outCount will return the member variables)
parameters:
1,
2, which set a value for receiving address, number of storage properties of
3, the return value: store all access to the property, the the following two methods can bring up the name and type of Ivar *class_copyIvarList (Class CLS, unsigned int *outCount) to obtain the member variable name const char *ivar_getName (Ivar V) to obtain the char *ivar_getTypeEndcoding member variable type const (Ivar V)

Case 1: get the names and types of all member variables in the Person class
Unsigned int outCount = 0; Ivar = *ivars class_copyIvarList ([Person class], & outCount); / / traversal of all member variables for (int i = 0; I < outCount; i++) {/ / I remove members corresponding to the position of the variable Ivar Ivar = ivars[i]; const = char * name ivar_getName const char (Ivar); *type = ivar_getTypeEncoding (Ivar); NSLog (@ "Member Name:%s member variable types:%s, name, type);} / / Note release memory! Free (Ivars);
Case 2: use runtime to retrieve all the properties to rewrite archive archiving methods
Do not need to set / attribute solution file - (NSArray *) ignoredNames @[@ {return "_aaa", "_bbb", "@ @ _ccc"]}; / / solution method - (instancetype) initWithCoder: (NSCoder * aDecoder) {if (self = [super initWithCoder:aDecoder]) {/ / get all the member variable unsigned int outCount = 0; Ivar = *ivars class_copyIvarList ([self class], & outCount); for (int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; / / every member variable name is converted into a NSString object type NSString *key = [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / don't need to ignore the attribute if the solution file ([[self ignoredNames] containsObject:key]) {continue}; According to the variable name solution file / / value, no matter what type of ID value = [aDecoder decodeObjectForKey:key]; / / remove the value set to setValue:value property [self forKey:key]; / / this two step is equivalent to the previous self.age [aDecoder decodeObjectForKey:@ = "_age"];} free (Ivars return self);};} / / call - filing method (void) encodeWithCoder: (NSCoder * aCoder) {/ / get all the member variables unsigned int outCount = 0; Ivar = *ivars class_copyIvarList ([self class], & outCount); for (int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; / / every member variable name is converted into a NSString object type NSString *key = [NSString stringWithUT F8String:ivar_getName (Ivar)]; / / if attribute ignored needs to be filed ([[self ignoredNames] containsObject:key]) {continue}; / / the member name, remove the value of ID value = [self valueForKeyPath:key]; / / [aCoder encodeObject:value forKey:key] and the value of filing; / / the two step is the equivalent of [aCoder encodeObject:@ (self.age) forKey:@ _age "]; free (Ivars);}}

Based on the above principle, we can make a classification for NSObject, so that we don’t have to write such a long string of code each time, and we can make an object have the ability to return files only if we implement a small amount of code.

Notice that I change a method name for the following code (otherwise it will overwrite the original method of the system) Adds a judgment that ignores whether the property method is implemented and adds a loop to the parent class property.

NSObject+Extension.h

#import, < @interface; NSObject (Extension) - - (NSArray *) ignoredNames; - - (void) encode: (NSCoder *) aCoder; - - (void) decode: (NSCoder *) aDecoder; @end; Foundation/Foundation.h>

NSObject+Extension.m

#import "NSObject+Extension.h" #import < objc/runtime.h> @implementation; NSObject (Extension) - (void) decode: (NSCoder * aDecoder) {/ / layers to find the parent class, the parent class attribute execution to file Class C while (C = self.class; & & c! = [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList (C, & outCount); for (int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; NSString = *key [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / if the implementation of the method to call the if ([self respondsToSelector:@selector (ignoredNames)] {if ([[self) ignoredNames] containsObject:key]) continue; ID value = [aDecoder decodeObjectForKey:key]}}; [self setValue:value forKey:key]; free (Ivars); C [c = superclass];}} - (void) encode: (NSCoder * aCoder) {/ / layers to find the parent class, the parent class attribute execution to file Class C while (C = self.class; & & C! = [NSObject class]) {unsigned int outCount = 0; Ivar = *ivars class_copyIvarList ([self class], & outCount); for (int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; NSString = *key [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / if the implementation of the method to call if ([self respondsToSelector:@selector (ignoredNam Es if ([[self)]) {ignoredNames] containsObject:key] continue}); ID value = [self valueForKeyPath:key]; [aCoder encodeObject:value forKey:key];} free (Ivars); C [c = superclass];}} @end

Using the method above: you can implement the following method in the object that needs to be found in the file:

Attribute settings need to be ignored - / (NSArray * ignoredNames) @[@ {return "bone"];} / / in the system within the method to call our method - (instancetype) initWithCoder: (NSCoder * aDecoder) {if (self = [super init]) {[self decode:aDecoder]}; return self;} - (void (encodeWithCoder:) NSCoder * aCoder [self) {encode:aCoder]};

So, every time we have to write the same code, we can return unpacking file two for macros, fix where needed a macro, if there is no need to file the solution properties for ignoredNames, specific can see my demo, this is MJExtension in a macro that can to solve the realization of the principle of solution file.

Case 3: use runtime to get all the attributes for the dictionary to model

We are to turn the dictionary model by using KVC, but it still has some limitations, such as: the model attribute and the key is not on crash to the corresponding (though it can override the setValue:forUndefinedKey: method to prevent error), model attribute is an object or an array of bad treatment problems, so both the efficiency and function on the use of runtime model to the dictionary are a better choice.

The dictionary conversion model we need to consider three special cases:
1. when the key attribute dictionary and model matching on
nested 2. models (the model property is another model object)
array with 3. models (the model property is an array, the array is a model object)

According to the above three special cases, we deal with each other, first of all, the dictionary key and the attributes of the model are not correct.
does not have two, one is the keys of the dictionary is greater than the model number of attributes, then we don’t need any treatment, because the runtime is the first traversal model all properties to the dictionary according to the attribute name to find the corresponding value assignment, the excess value is not to see; another is the model number of attributes over the keys of the dictionary, this time because of property has no corresponding value will be set to nil, will lead to crash, we only need to add a judgement, the data of JSON and sample are as follows:

OC the most practical summary of runtime, interview, work, you see, I will be enough!
JSON data
- (void) setDict: (NSDictionary * dict) {Class C = self.class (C & while; & c! = [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList (C, & outCount); for (int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; NSString = *key [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / member variable name to the attribute name (key = [key _ remove the underline) substringFromIndex:1]; / / remove dictionary value ID value = dict[key]; / / if the model attribute is greater than the number of pairs of mathematical model of attribute dictionary, will be assigned to nil and if error continue (value = = Nil); / / set the value will be in the dictionary To the model, [self, setValue:value, forKeyPath:key];} free (Ivars); C = [c superclass];}

The second case is that the property of the model is another model object

OC the most practical summary of runtime, interview, work, you see, I will be enough!
JSON data

This time we need to obtain the model object type using ivar_getTypeEncoding runtime method, the model of the object type is the dictionary conversion model, recursion, note that we must remove the object types, such as NSString system, the following method I added a kind of convenient recursive method.

OC the most practical summary of runtime, interview, work, you see, I will be enough!
Print allows you to see each property type
#import "NSObject+JSONExtension.h" #import < objc/runtime.h> @implementation; NSObject (JSONExtension) - (void) setDict: (NSDictionary * dict) {Class C = self.class (C & while; & c! = [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList (C, & for (outCount); int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; NSString = *key [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / member variable name to the attribute name (key = [key _ remove the underline) substringFromIndex:1]; / / remove dictionary value ID value = dict[key]; / / if the model is greater than the number of attribute dictionary keys the mathematical model of attribute Will be assigned to nil and if error (value = = nil continue); / / get the member variable type NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding (Ivar)]; / / if the attribute is the object type NSRange rangeOfString:@ @ range = [type]; if (range.location! = NSNotFound) {/ / then intercept the name of the object (for example @ "Dog", Dog type = [type) from substringWithRange:NSMakeRange (2, type.length - 3)]; / / remove object type if system ([type! HasPrefix:@ "NS"]) {/ / object name is converted to the type of the object, the new object dictionary conversion model (recursive) Class class = NSClas SFromString (type) [class; value = objectWithDict:value];}} / / dictionary value is set to the model [self setValue:value forKeyPath:key] free (Ivars);}; C [c = superclass];}} + (instancetype) objectWithDict: (NSDictionary * dict) {NSObject *obj = [[self alloc]init]; [obj setDict:dict]; return obj;}

The third case is that the property of the model is an array, the array is a model object, such as the following data, I can get the C programming language through books[0].name

OC the most practical summary of runtime, interview, work, you see, I will be enough!
JSON data

If we can get to the type of attribute, it can capture the array attribute model, and then the array traversal and dictionary conversion model of each model, but we do not know in the array model is what type, we can declare a method, this method is not allowed to call, but to achieve its type and return model.
this language may not explain clearly, you can refer to my demo, you can run directly.

NSObject+JSONExtension.h

What type of object model / / are returned in an array - (NSString * arrayObjectClass);

NSObject+JSONExtension.m

#import "NSObject+JSONExtension.h" #import < objc/runtime.h> @implementation; NSObject (JSONExtension) - (void) setDict: (NSDictionary * dict) {Class C = self.class (C & while; & c! = [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList (C, & for (outCount); int i = 0; I < outCount; i++) {Ivar Ivar = ivars[i]; NSString = *key [NSString stringWithUTF8String:ivar_getName (Ivar)]; / / member variable name to the attribute name (key = [key _ remove the underline) substringFromIndex:1]; / / remove dictionary value ID value = dict[key]; / / if the model is greater than the number of attribute dictionary keys the mathematical model of attribute Will be assigned to nil and if error (value = = nil continue); / / get the member variable type NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding (Ivar)]; / / if the attribute is the object type NSRange rangeOfString:@ @ range = [type]; if (range.location! = NSNotFound) {/ / then intercept the name of the object (for example @ "Dog", Dog type = [type) from substringWithRange:NSMakeRange (2, type.length - 3)]; / / remove object type if system ([type! HasPrefix:@ "NS"]) {/ / object name is converted to the type of the object, the new object dictionary conversion model (recursive) Class class = NSClas SFromString (type); value = [class objectWithDict:value];}else if ([type isEqualToString:@ "NSArray"]) {/ / if it is an array type, each model in the array dictionary conversion model, first create a temporary array storage model NSArray = *array (NSArray * value); NSMutableArray *mArray = [NSMutableArray array]; / / get to each type of model ID class; if ([self respondsToSelector:@selector (arrayObjectClass)] *classStr arrayObjectClass]) {NSString = [self; class = NSClassFromString (classStr);} / / All the models in the array of for (int dictionary conversion model I = 0; I < array.count; i++) {[mArray addObject:[class objectWithDict:value[i]]]}; value = mArray;}} / / dictionary value is set to the model [self setValue:value forKeyPath:key] free (Ivars);}; C [c = superclass];}} + (instancetype) objectWithDict: (NSDictionary * dict) {NSObject *obj = [[self alloc]init]; [obj setDict:dict]; return obj @end;}