IOS-Runtime knowledge point sorting, ios-runtime knowledge point
Contents
- 1. Introduction to Runtime
- 2. Runtime-related header files
- 3. Technical Points and application scenarios
- 3_1. Get the attribute \ member variable list
- 3_2. exchange method implementation
- 3_3. correlated object of class \ object, false attribute
- 3_4. dynamically add methods to intercept unimplemented Methods
- 3_5. dynamically create a class
- 4. Interview Questions
-1. The Runtime introduction is back to the top. 1. The Runtime Introduction
Because Objc is a dynamic language, it always finds a way to delay some decision work from compilation connection to runtime. That is to say, only the compiler is not enough. A runtime system is also required to execute the compiled code. This is the meaning of the existence of the Objective-C Runtime system. It is a cornerstone of the entire Objc Runtime framework.
Runtime actually has two versions: "modern" and "legacy ". Currently, Objective-C 2.0 uses the current (Modern) version of the Runtime system and can only run 64-bit programs after iOS and OS X 10.5. The older 32-bit OS X programs still use the (earlier) Legacy version of the Runtime system in Objective-C 1. The biggest difference between the two versions is that when you change the layout of instance variables of a class, you need to re-compile its subclass in earlier versions, but the current version does not.
Runtime is basically written in C and sink. It can be seen that Apple has made great efforts for the efficiency of dynamic systems. You can go to the open source code maintained by Apple here. Both Apple and GNU maintain an open-source runtime version.
-2. The Runtime header file is returned to the top 2. The Runtime header file.
The usr/include/objc folder in ios sdk contains the following files:
List.hNSObjCRuntime.hNSObject.hObject.hProtocol.ha.txthashtable.hhashtable2.hmessage.hmodule.mapobjc-api.hobjc-auto.hobjc-class.hobjc-exception.hobjc-load.hobjc-runtime.hobjc-sync.hobjc.hruntime.h
These are header files related to the runtime. The main functions used are defined in the message. h and runtime. h files. Message. h mainly contains some functions for sending messages to objects, which is the underlying implementation of OC object method calls. Runtime. h is the most important file during runtime, which contains the methods for running operations. It mainly includes:
1. Definition of the operation object type
/// An opaque type that represents a method in a class definition. A type represents a Method in the class definition, typedef struct objc_method * Method; // An opaque type that represents an instance variable. the typedef struct objc_ivar * Ivar variable representing the instance (object); // An opaque type that represents a category. typedef struct objc_category * Category; // An opaque type that represents an Objective-C declared property. represents the attribute typedef struct o declared by OC Bjc_property * objc_property_t; // Class represents a Class. It defines typedef struct objc_class * Class in objc. h; struct objc_class {Class isa OBJC_ISA_AVAILABILITY; # if! _ OBJC2 _ Class super_class comment; const char * name comment; long version comment; long info comment; long instance_size comment; struct objc_ivar_list * ivars comment; struct objc_method_list ** methodLists comment; struct objc_cache * cache OBJC2_UNAVAILABLE; struct objc_protocol_list * protocols OBJC2_UNAVAILABLE; # endif} OBJC2_UNAVAILABLE;
The definitions of these types completely decompose a class and abstract each part of the class definition or object into a type, which is very convenient for operating a class attribute and method.
OBJC2_UNAVAILABLE
The marked attributes are not supported by Ojective-C 2.0, but they can be obtained using the response function. For example, if you want to obtain the name attribute of the Class, you can obtain the attributes as follows:
Class classPerson = Person. class; // printf ("% s \ n", classPerson-> name); // you cannot get the name using this method because OBJC2_UNAVAILABLEconst char * cname = class_getName (classPerson ); printf ("% s", cname); // output: Person
2. Function Definition
Generallyobject_
Start
Generallyclass_
Start
The method for operations on methods of classes or objects is generallymethod_
Start
Generallyivar_
Start
GenerallyStart with property _
Start
Generallyprotocol_
Start
Based on the prefix of the above functions, you can get a general understanding of the hierarchical relationship. Forobjc_
The method at the beginning is the final manager of runtime, which can obtain the loading information of classes in the memory, the list of classes, associated objects and associated attributes, and other operations.
For example, use runtime to print the classes loaded in the current application.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { unsigned int count = 0; Class *classes = objc_copyClassList(&count); for (int i = 0; i < count; i++) { const char *cname = class_getName(classes[i]); printf("%s\n", cname); }}
-3. Technical Points and application scenarios go back to the top 3. Technical Points and application scenarios
In the following code, the Person class is used. The Person class defines a member variable and two attributes.
@interface Person : NSObject{ @private float _height;}@property (nonatomic, copy) NSString *name;@property (nonatomic, assign) int age;@end
3_1. Get the attribute \ member variable list and return to the top. You can use
class_copyIvarList
Function. If you want to obtain the attribute list, you can use
class_copyPropertyList
Function. The example is as follows:
-(Void) touchesBegan :( NSSet *) touches withEvent :( UIEvent *) event {Class classPerson = NSClassFromString (@ "Person"); // The effect is the same as that of the following sentence, you do not need to import the header file // Class clazz = Person. class; unsigned int count = 0; Ivar * ivarList = class_copyIvarList (classPerson, & count); // obtain the array of member variables for (int I = 0; I <count; I ++) {const char * cname = ivar_getName (ivarList [I]); // obtain the name of the member variable NSString * name = [NSString stringwithuf8string: cname]; NSLog (@ "% @", name);} NSLog (@ "------------------- split line ------------------"); objc_property_t * propertyList = class_copyPropertyList (classPerson, & count ); // obtain the attribute array for (int I = 0; I <count; I ++) {const char * cname = property_getName (propertyList [I]); NSString * name = [NSString stringwithuf8string: cname]; NSLog (@ "% @", name );}}
The output of the above Code is:
22:28:16. 194 runtime ultimate [4192: 195757] _ height2015-06-05 22:28:16. 195 runtime ultimate [4192: 195757] _ age2015-06-05 22:28:16. 195 runtime ultimate [4192: 195757] _ name2015-06-05 22:28:16. 195 runtime ultimate [4192: 195757] ------------------- split line ------------------ 22:28:16. 195 runtime ultimate [4192: 195757] name2015-06-05 22:28:16. 195 runtime ultimate [4192: 195757] age
Why is there the above output result, because @ property will do three jobs:
1. generate an underlined member variable
2. get method for generating this member variable
3. set Method for generating this member variable
Therefore, three member variables _ height, _ age, and _ name are output. Note that the attribute name is not underlined, and is the same as the name in definition. Therefore, ivarList can obtain the attribute defined by the @ property keyword, while propertyList cannot obtain the member variable. That is, ivarList can be used to obtain all member variables and attributes.
This problem occurs when the attribute is readonly and the getter is overwritten. For example, if an attribute is a compute attribute, it must be calculated based on the values of other attributes. In this case, the generated member variables with underlines are no longer available and the attribute cannot be obtained through ivarList. Therefore, when such a value exists, neither ivarList nor propertyList can be used to obtain all attributes or variables.
Before proceeding to the next topic, you need to find out another question: Is the readonly attribute good for didSet + set, or is it better to rewrite getter?
Most readonly attributes are computed and still dependent on other attributes. Therefore, you can use didSet + set, that is, set this attribute in the set Method of other attributes. However, didSet + set is sometimes unnecessary and does not comply with the lazy loading rules. It wastes computing power and is better to rewrite the getter method. Therefore, it is always better to rewrite getter.
Regression question: What should I do if I want to obtain all the member variables and attributes in KVC?
First, you must understandsetValue: forKeyPath:
Underlying implementation of the method: Taking the name attribute as an Example
1. First, go to the method list of the class to search for methods with wood and setName:. If yes, call [person setName: value] directly.
2. Check whether the member Variable _ name is underlined. If _ name = value exists;
3. Check whether the member variable name exists. If there is a name = value;
4. If none of them are found, an error is reported directly.
Therefore, if the getter attribute is rewritten for readonly: If kvc is used for the propertyList attribute at one time, an error is reported. Therefore, to ensure the code is normal, the attribute of propertyList cannot be used for kvc;
In addition: This type of attribute is compute type. Why do we assign values to it? Therefore, it is unreasonable to perform kvc on it.
When ivaList is used, this attribute cannot be obtained directly, so it is the best solution for kvc. Furthermore, using propertyList cannot obtain the member variable (_ height) and assign values to the member variables. IvaList can be used to obtain all the member variables assigned by the assignment.
The above demonstrates whether to use ivar or property for kvc.
Off-topic: Some member variables of many classes are neither exposed to external call getter nor setter, but are declared with @ private: Why ??
Guess: it is the intermediate variable used for method calling. Because it is generated by following the object, it is not suitable for static, and because the external is not used, it is not necessary to provide external interfaces, however, this amount may be required for several methods and is not suitable for local variables.
In this case, if you do not want to assign values to such member variables, you can improve the value in KVC by using ivarList to remove the member variables not in propertyList, in this way, the above member variables are filtered out.
3_1_1. Application 1: KVC dictionary to model Back to Top
An important application for obtaining the attribute/member list is to retrieve the attribute/member variable in the model at a time, obtain the key in the dictionary according to its name, and then retrieve the value corresponding to the key in the dictionary.setValue: forKeyPath:
Method setting value. Why, instead of using methods?setValuesForKeysWithDictionary:
. Because insetValuesForKeysWithDictionary:
Method will execute such a process internally
Traverse all the keys in the dictionary and retrieve them one by one. Traverse each key according to the following process:
1. Retrieve the key,
2. Retrieve the value of the key, that is, dict [key], and directly assign values to the attributes and member variables of the model.
3. How to assign values to model attributes?setValue:value forKeyPath:key
Assign values. The execution process of this method is mentioned above.
Therefore, if the dictionary contains more keys than the modelthis class is not key-value compliant for ‘xxx’
This bug is well explained, usually because the keys in the dictionary are more than the attribute \ member variables in the model. When the model has more attributes than the dictionarysetValuesForKeysWithDictionary:
Will there be no bugs? Tested: when the multiple attributes are of the object data type, they are null. When the attribute is of the basic data type, a default value (for example, int is 0) is generated ).
Therefore, KVC is performed by assigning values to attributes one by one:
Class clazz = Person. class; unsigned int count = 0; Person * person = [[Person alloc] init]; NSDictionary * dict ={ @ "name": @ "zhangsan ", @ "age": @ 19, @ "height": @ 1.75}; Ivar * ivars = class_copyIvarList (clazz, & count); // NSLog (@ "% tu ", count); // 3for (int I = 0; I <count; I ++) {const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; // remove '_' [person setValue: dict [key] forKey: key];} NSLog (@ "% @", person); // The description method has been rewritten.
The output is:
<Person, 0x7ff15b80f230>{ name = zhangsan, height = 1.750000, age = 19}
Using kvc in this way, even if the dictionary contains many keys, there will be no bugs, but a new problem arises. If the model has more attributes than the keys in the dictionary, a bug will occur and: if there is no bug in the object type, the value of this attribute is null. If it is a basic data type, an error will occur.could not set nil as the value for the key ‘xxx’
. For example, change the dictionary above:
NSDictionary * dict =@ {@ "age": @ 19, @ "height": @ 1.75}; // The name NSString type is removed.
After modification, the output is:
<Person, 0x7f996263fbd0>{ name = (null), height = 1.750000, age = 19}
If you change the dictionary:
NSDictionary * dict =@ {@ "name": @ "zhangsan", @ "age": @ 19}; // The height float type is removed.
The program crashes.
How to solve the above bug:setValue:value forKeyPath:key
Perform the following operations before calling the method: retrieve the type corresponding to the property. If the type is basic data type, replace the value with the default value (for example, the default value of int is 0 ).
Provided by runtimeivar_getTypeEncoding
The function can obtain the attribute type. The returned value indicates the following:
The TypeCode corresponding to the float type of height is "f", so you can filter it. The code changes are as follows:
Class clazz = Person. class; unsigned int count = 0; Person * person = [[Person alloc] init]; NSDictionary * dict ={ @ "name": @ "zhangsan ", @ "age": @ 19, @ "height": @ 1.75}; Ivar * ivars = class_copyIvarList (clazz, & count); for (int I = 0; I <count; I ++) {const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; const char * coding = ivar_getTypeEncoding (ivars [I]); // obtain the type NSString * strCode = [NSString stringwithuf8string: coding]; id value = dict [key]; if ([strCode isEqualToString: @ "f"]) {// determines whether the type is float value = @ (0.0);} [person setValue: value forKey: key];} NSLog (@ "% @", person );
In this way, the execution is normal and the output is:
<Person, 0x7fc75d004a00>{ name = zhangsan, height = 0.000000, age = 19}
3_1_2. application 2: The NSCoding archive and archive file are returned to the top to obtain the attribute \ member list. Another important application is archive and archive. The principle is basically the same as that of the preceding kvc, here is just some code:
-(Void) encodeWithCoder :( NSCoder *) ACO {unsigned int count = 0; Ivar * ivars = class_copyIvarList (self. class, & count); for (int I = 0; I <count; I ++) {const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; id value = [self valueForKey: key]; // retrieve the value [ecoder encodeObject: value forKey: key]; // encoding}-(id) initWithCoder :( NSCoder *) corresponding to the key *) aDecoder {if (self = [super init]) {unsigned int count = 0; Ivar * ivars = class_copyIvarList (self. class, & count); for (int I = 0; I <count; I ++) {const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; id value = [aDecoder decodeObjectForKey: key]; // decode [self setValue: value forKey: key]; // set the value of the key} return self ;}
3_2. Switch method implementation Back to Top
Scenario for implementing the exchange method: You have created a functional method that is referenced multiple times in the project. When the project requirements change, you must use another function to replace this function, the requirement is that the old project is not changed (that is, the implementation of the original method is not changed ).
You can write a new method (meeting the new requirements) in the class classification, and then exchange the implementation of the two methods. In this way, the project is improved without changing the project code, but with the addition of new code.
The implementation of the two methods is generally written in the load method of the class, because the load method will be loaded once before the program runs, and the initialize method will be called when the class or subclass is used for the first time, it is called multiple times when there is a classification.
// Call + (void) load {// if it is a class method, use class_getClassMethod, if the object Method uses class_getInstanceMethod Method methodOne = equals (self, @ selector (methodOne :)); Method methodTwo = class_getInstanceMethod (self, @ selector (methodTwo :)); // implement method_exchangeImplementations (methodOne, methodTwo) for exchanging two methods );}
Note that
1. The parameters of the two methods that can be exchanged must be matched and the types of parameters are consistent.
2. If you want to call the two method within the method one, you should use one to call the two method within the method one, but actually call two. Otherwise, an endless loop will occur.
For example:
// Pre-exchange-(NSString *) methodOne :( NSString *) str {NSLog (@ "% @", [self methodTwo: str]); return "suc ";} // after the switch, you must replace the place where two is called with your own name-(NSString *) methodOne :( NSString *) in the implementation of the method *) str {NSLog (@ "% @", [self methodOne: str]); return "suc ";}
Each method has two important attributes: SEL is the number of the method, IMP is the implementation of the method, and the call process of the method actually searches for IMP Based on SEL.
In this example, assume that the method of SEL as methodOne before the switch points to IMP1, and the method of SEL as methodTwo points to IMP2.
In fact, the underlying implementation of the exchange is to switch the direction of the Method number, that is, let methodOne: Point to IMP2, methodTwo points to IMP1. Application Scenario 3_3. the associated object of the class \ object is returned to the top. The associated object does not add attributes or member variables to the class \ object (because it cannot be obtained through ivarList or propertyList after the association is set ), instead, you can add a related object to the class, which is usually used to store class information, such as an array of Storage Class Attribute lists, to facilitate future dictionary-to-model conversion. For example, save the attribute name to the array and set the Association
Const char * propertiesKey = "propertiesKey"; unsigned int count = 0; Ivar * ivars = class_copyIvarList ([Person class], & count); NSMutableArray * arrayM = [NSMutableArray records: count]; for (unsigned int I = 0; I <count; ++ I) {Ivar pty = ivars [I]; const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; // remove _ [arrayM addObject: key];} free (ivars); objc_setAssociatedObject (self, propertiesKey, arrayM, callback); NSLog (@ "% @", arrayM );
The output is
( age, height, name)
objc_setAssociatedObject
Description of method parameters:
Id object of the first parameter, current object
The second parameter const void * key, the associated key, is a c string
The third parameter id value is the associated object.
The fourth parameter, objc_AssociationPolicy, is used to associate the referenced rules with the following values:
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403};
If you want to obtain the associated object, use the key to get it.
NSArray *pList = objc_getAssociatedObject(Person, propertiesKey);
You can encapsulate the preceding two operations to add a properties class method to the Person class and encapsulate the preceding operations to obtain the attribute list of the class.
Const char * propertiesKey = "propertiesKey"; @ implementation Person + (NSArray *) properties {// if it is already associated, retrieve the associated object based on the key and return NSArray * pList = objc_getAssociatedObject (self, propertiesKey); if (pList! = Nil) {return pList;} // if there is no association, set the association object and return the object unsigned int count = 0; Ivar * ivars = class_copyIvarList ([self class], & count); NSMutableArray * arrayM = [NSMutableArray arrayWithCapacity: count]; for (unsigned int I = 0; I <count; ++ I) {Ivar pty = ivars [I]; const char * cname = ivar_getName (ivars [I]); NSString * name = [NSString stringwithuf8string: cname]; NSString * key = [name substringFromIndex: 1]; [arrayM addObject: key];} free (ivars); objc_setAssociatedObject (self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC); return arrayM. copy;} @ end
3_4. dynamically add methods to intercept unimplemented methods and return to the top. Each class has two class methods (from NSObject)
+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel
The above two are used in class methods and one is applicable to object methods. When no implemented method is called in the code, that is, if the method identified by sel is not implemented, one of the two methods will be called now (if it is a class method, the first method will be called, if it is an object method, the second method will be called) interception. The common practice is to specify the IMP corresponding to the sel in the resolve, so as to complete the two processes of dynamic creation and calling of the method. You can also directly return the error message without specifying the IMP.
If the-sayHi method is not used in the Person class, if the object p uses [p performSelector: @ selector (sayHi) withObject: nil]; then it must pass through the Person classresolveInstanceMethod:(SEL)sel
Method. Here, the implementation is specified for-sayHi.
Void abc (id self, SEL _ cmd) {NSLog (@ "% @ said hello", [self name]) ;}@ implementation Person // Add the method dynamically: add the corresponding method in resolve. check whether it is a class method or an object method. + (BOOL) resolveInstanceMethod :( SEL) sel {if ([NSStringFromSelector (sel) is1_tostring: @ "sayHi"]) {class_addMethod (self, sel, abc, "v @: "); // specify the implementation as abc} return YES for sel;} @ end
Description of the first two parameters of implementation (abc)
Each method contains two parameters by default, which are called implicit parameters.
Id type self (representing class or object) and SEL type _ cmd (Method number)
class_addMethod
Description of function parameters:
The first parameter Class cls, Type
Second parameter SEL name, Resolved Method
The third parameter, IMP imp, specifies the implementation
The fourth parameter const char * types is the method type. For details, refer to the codeType diagram of the type. Note the following: since the function must take at least two arguments-self and _ cmd, the second and third characters must be "@:" (the first character is the return type ). because the function must have at least two parameters self and _ cmd, the second and third characters must be "@:". If you want to add another parameter, you can calculate it from the third parameter of the Implementation. You can see the following example.
Returned value: YES if the method was found and added to the handler er, otherwise NO.
Add parameters for the implementation of the-sayHi Method
Call time:
Person * p = [[Person alloc] init]; p. name = @ "zhangsan"; p. age = 10; [p performSelector: @ selector (sayHi :) withObject: @ "world"]; // added a parameter with a colon
Modified the code in the Person class.
Void abc (id self, SEL _ cmd, NSString * content) {// added a parameter content NSLog (@ "% @ said hello % @", [self name], content) ;}@ implementation Person // dynamic addition method: add the corresponding method in resolve, and pay attention to whether it is a class method or an object method. + (BOOL) resolveInstanceMethod :( SEL) sel {if ([NSStringFromSelector (sel) is1_tostring: @ "sayHi:"]) {class_addMethod (self, sel, abc, "v @: @ "); // added an object type parameter with @} return YES;} @ end
Output:
Zhangsan said helloworld
3_5. dynamically create a class and return to the top to dynamically create a class, add member variables and methods to the class, and create objects of this type:
# Import "ViewController. h "# import <objc/runtime. h> # import <objc/message. h> # import "Person. h "static void printSchool (id self, SEL _ cmd) {NSLog (@" my school is % @ ", [self valueForKey: @" schoolName "]);} @ implementation ViewController-(void) touchesBegan :( NSSet *) touches withEvent :( UIEvent *) event {Class classStudent = objc_allocateClassPair (Person. class, "Student", 0); // Add an NSString variable, the fourth parameter is the method, and the fifth parameter is the parameter type if (class_addIvar (classStudent, "schoolName", sizeof (NSString *), 0, "@") {NSLog (@ "added member variable schoolName succeeded ");} // Add the method "v @:" For Student class, see parameter type connection if (class_addMethod (classStudent, @ selector (printSchool), (IMP) printSchool, "v @: ") {NSLog (@" add method printSchool: Successful ");} // register this class to the runtime system and use objc_registerClassPair (classStudent ); // return void // use the created class id student = [[classStudent alloc] init]; NSString * schoolName = @ ""; // assign a value to the added variable // object_setInstanceVariable (student, "schoolName", (void *) & str); [student setValue: schoolName forKey: @ "schoolName"]; // call the printSchool method, that is, send the printSchool message to the student receiver. // objc_msgSend (student, "printSchool "); // I tried to call this method but it was not successful [student notify mselector: @ selector (printSchool) withObject: nil]; // dynamically call methods not explicitly declared in the class} @ end
The output result is:
The member variable schoolName is successfully added to printSchool. My school is Tsinghua University.