Objective-c class member Variable depth analysis

Source: Internet
Author: User


Catalogue
    • Non Fragile Ivars
    • Why non fragile Ivars is critical
    • How to address class member variables
    • True "how to address class member variables"
    • Non Fragile ivars Layout adjustment
    • Why the Objective-c class cannot dynamically add member variables
    • Summarize


Look at the code below to consider one of the most common operations in Objective-c-class member variable access.


- (void)doSomething:(SomeClass *)obj
{
     Obj->ivar1 = 42; // access the public member variable of the obj object
     Int n = self->ivar2; // access member variables of the current class instance
     Ivar2 = n + 1; // access member variables of the current class
}


What most people may not be aware of is:


    • The objective-c->operator is not a C-language pointer operation!
    • Objective-c objects cannot simply correspond to a C struct, access member variables do not equal access to C struct members!


I didn't know that until noon yesterday. When the truth is known, there is no article really clear about the Objective-c class member variables (ivar,instance variables, class instance variables), so it is necessary to do a depth analysis.


Non Fragile Ivars


We often say that objective-c is "a superset of C language", intuitively think that C language grammar and characteristics in Objective-c, objective-c only on the basis of C to increase the object-oriented, dynamic characteristics, block and so on. I have always thought that the OBJECTIVE-C member variable is the same as C + +. In C + +, access to member variables is translated into a directive by the compiler, and the value of the member variable is accessed with the object address plus the member variable offset value.



Yesterday a friend asked me about the runtime problem, and I looked at the concept of "non-fragile instance variables" and suddenly realized that this could not be solved with C + + object memory model.


The most notable new feature was that instance variables in the modern runtime was "Non-fragile":

In the legacy runtime, if your change the layout of instance variables in a class, you must recompile classes that inherit From it.
In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes tha T inherit from it.


This is a paragraph of Apple's official document OBJECTIVE-C Runtime Programming Guide, meaning that in "modern runtime", if you modify the base class's member variable layout (such as adding member variables), the subclass does not need to be recompiled. This is a huge change that was raised in the document as the most important modification point of the "Modern runtime".



Cocoa Samurai's article Understanding the OBJECTIVE-C runtime clearly explains non fragile ivars with a few diagrams. Here is an example of his diagram.



1) The MyObject class member variable layout compiled with the legacy OSX SDK is such that the member variables of the MyObject are arranged sequentially behind the members of the base class NSObject.






2) When Apple released a new version of OSX SDK, NSObject added two member variables. Without the non fragile Ivars feature, our code will not function properly because the MyObject class member variable layout has been determined at compile time, with two member variables and the memory area of the base class overlapping. At this point, we can only recompile the MyObject code before the program can run on the new version of the system. If it is more tragic, the MyObject class is a static library from a third party, and we can only wait for the library author to update the version.






3) Non fragile Ivars features appeared. After the program starts, runtime loads the MyObject class, by computing the size of the base class, the runtime dynamically adjusts the MyObject class member variable layout, moving the MyObject member variable position backwards by 8 bytes. So our program can run on a new version of the system without compiling it.





Why non fragile Ivars is critical


The significant significance of this feature is that the OBJECTIVE-C library has a "binary Compatibility"from this point forward. For example, you use a static library SDK provided by a third party in your project, including some.hand a.afile. When the iOS SDK version has risen from 6 to 7 and from 7 to 8 o'clock, you don't need to update this SDK. While the iOS SDK version was upgraded, Apple added more member variables to base classes such as UIView, but the previously published Static library SDK did not need to be recompiled to work properly.



Fortunately we are not in the Dark Ages, iOS from the beginning is the use of the modern runtime. You can imagine how the previous Mac developers put up with the problem: every time MacOS releases a new version, it compiles its own programs and then releases the new version.



This is the basic principle of Non fragile ivars. It doesn't sound very advanced, and many programming languages can do it, like Java, C #, which have binary compatibility. But objective-c is not "so" dynamic language after all, Objective-c code compiled is the real native binary, not byte code. The OBJECTIVE-C program is not running on the VM, there is only a small runtime at the bottom. These two points, Java, C # do not.



How did the non fragile Ivars be realized? The key point is that, when the member variable layout is adjusted, how can the statically compiled native program find the new offset position of the variable ?


How to address class member variables


We use two tools to explore the answer: objective-c runtime source and LLVM.



First go to http://opensource.apple.com/download runtime source code, in the "OSX" category, the current version is the latest objc4-646.tar.gz. Unpack and open the Xcode project to find thestruct objc_objectdefinition.



We already know that each Objective-c object corresponds tostruct objc_object, the latter of the pointer to theisaclass definition, that isstruct objc_class.


struct objc_object {
private:
    isa_t isa;
    //...
};

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //...
};


Alongobjc_classthe waydata()->ro->ivarsdown,struct ivar_list_tis the list of definitions for all member variables of the class.


struct ivar_list_t {
    uint32_t entsize;
    uint32_t count;
    ivar_t first;
};


By using afirstfield, you can get the definition of any class member variable in the class.


struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    //...
};


We see the sensitive wordoffset, which must be recorded in the offset position of the member variable in the object. In other words, runtimeoffsetupdates the offset value of a subclass member variable by modifying it when it discovers a change in the base class size. That Objective-c gets the nth member variable offset position of the object requires such a long string of code:


*((&obj->isa.cls->data()->ro->ivars->first)[N]->offset)


So many addresses, it looks awful. Each member variable is accessed in such a way that performance must be unacceptable. Let's see how the compiler actually implements it, and we sacrifice LLVM.


True "how to address class member variables"


LLVM at compile time, first generates an intermediate language (ir,intermediate representation); Some of the following optimizations and analysis steps are performed on IR, and finally the IR is converted into a native executable file. Since IR is better than compilation readability, we use IR to analyze how the compiled Objective-c program executes.



Create the test codetest.m.


#import <Foundation/Foundation.h>

// Deliberately choose a larger base class for easy viewing
@interface MyClass : NSError {
@public
     Int myInt;
}
@end

@implementation MyClass
@end

Int main()
{
     MyClass *obj = [[MyClass alloc] init];
     Obj->myInt = 42;
}


Executing at the command line


clang -cc1 -S -emit-llvm -fblocks test.m


The compiled resulttest.llis the LLVM IR code. It is recommended to install LLVM plugin with sublime text, which has syntax highlighting. You can see that the IR format is cumbersome, simpler than assembler, and more complex than C. This does not write the IL analysis process, directly said the conclusion.



The compiledobj->myInt = 42call corresponds to the following simple C-language code.


Int32_t g_ivar_MyClass_myInt = 40; // global variable

*(int32_t *)((uint8_t *)obj + g_ivar_MyClass_myInt) = 42;


Two CPU instructions are done. The first takesg_ivar_MyClass_myIntthe value, the second one addresses and assigns a value. There is no need to call a long string of pointers at all. LLVM assigns a global variable to each member variable for each class that stores the offset value of the member variable.



This is whyivar_t.offsetthe int pointer is used to store the offset value, rather than the reason for placing an int directly. In this design, the address that really holds the offset value is fixed and is determined at compile time. Therefore, the member variables of the dynamic layout can be fixed with a trivial 2 instructions.



This is how the Objective-c class member variable is addressed. In this way, the compiler achieves the perfect balance of flexibility and execution efficiency!


Non Fragile ivars Layout adjustment


With this flexible and efficient way of addressing, when does runtime adjust the member variable offset value? As can be seen from IR, at compile time, LLVM calculates the size of the base class Nserror object to be 40 bytes, and then records it in the class definition of MyClass, which is the corresponding C code. In the compiled executable program, wrote dead "40" This magic number, recorded at this compile time MyClass base class size.


class_ro_t class_ro_MyClass = {
    .instanceStart = 40,
    .instanceSize = 48,
    //...
}


Now, if Apple releases OSX, the Sdk,nserror class size increases to 48 bytes. When our program starts, runtime loads the MyClass class definition and finds that the true size of the base classinstanceStartdoes not match the MyClass, knowing that the size of the base class has changed. The runtime iterates through all the member variable definitions of MyClass andoffsetincrements the value by 8. The specific implementation code is inruntime/objc-runtime-new.mmthemoveIvars()function.



Also, the MyClass class definition isinstanceSizeincreased by 8. This allows the runtime to allocate memory blocks of the correct size when creating MyClass objects.


Why the Objective-c class cannot dynamically add member variables


The answer to this question is not related to non fragile ivars, but since this article is about class member variables, it is discussed together. Many people have doubts when they are learning the category, so why not add the member variable if the class is allowed to add methods and attributes to classes?



In the runtime function provided by OBJECTIVE-C, there is indeed aclass_addIvar()function to add member variables to the class, but the documentation specifically describes:


This function is called after Objc_allocateclasspair and before Objc_registerclasspair. Adding an instance variable to a existing class is not supported.


This means that this function can only be called in the process of building a class. Once the class definition is complete, you can no longer add member variables. The compiled class is loaded by runtime after the program is started, and there is no chance to call Addivar. Classes that are dynamically built at run time need to be called beforeobjc_registerClassPairthey can be used, and there is no chance of adding member variables.



Let's imagine what would happen if objective-c allowed dynamic addition of member variables. Assume that the following code can be executed.





MyObject *obj = [[MyObject alloc] init];

// base class adds a 4-byte member variable someVar
class_addIvar([NSObject class], "someVar", 4, ...);
// The base class adds the method someMethod, which uses someVar
class_addMethod([NSObject class], @selector(someMethod), ...);

// call someMethod to modify someVar
[obj someMethod];

// What happens when I access a subclass member variable?
[obj->students length]; 


Obviously, this can be a serious problem, and adding a member variable dynamically for the base class will cause all instances of the child class that have been created to be unusable. So why does runtime allow you to add methods and properties dynamically without causing problems?



Because methods and properties do not "belong" to the class instance, the member variable "belongs" to the class instance. What we call the "class instance" concept refers to an area of memory that contains the ISA pointer and all the member variables. So if the class member variable layout is allowed to be dynamically modified, the class instance that has been created does not conform to the class definition and becomes an invalid object. However, the method definition isobjc_classmanaged in, no matter how to remove or delete the class method, does not affect the memory layout of the class instance, the class instance that has been created can still be used normally.


Summarize


Objective-c's "Non fragile Ivars" feature, in exchange for binary compatibility of programs with very low runtime overhead. And the executable is still the native program on the target platform and does not need to run on the VM. It is a model of design trade-offs.



Objective-c class member Variable depth analysis


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.