In the “Principle analysis of Class (part 1) – Understanding the structure of class”, we know the basic structure of class, this chapter will introduce the method of class, attribute, member variables related knowledge.

Member variables and attributes

Definition of member variables and attributes

We think of a member variable as an instance variable with an _ enclosed in parentheses in a class or extension header file.

In fact, an instance variable is just a special member variable.

Property is actually a combination of member variables and setter-getter methods. The system provides a wrapper for us, which can be named as @property (XXX, XXX) Type ivarName.

So, the realization of an attribute, what kind of process the bottom actually experienced, let us explore together!

2. Analyze C++ code for an attribute


#pragmaMark - Defined class

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@end

#pragmaMark - The file compiled to CPP

static NSString * _Nonnull _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool.bool);

static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0.1); }

static int _I_LGPerson_age(LGPerson * self, SEL _cmd) { return(* (int((*)char *)self + OBJC_IVAR_$_LGPerson$_age)); }

static void _I_LGPerson_setAge_(LGPerson * self, SEL _cmd, int age) { (*(int((*)char *)self + OBJC_IVAR_$_LGPerson$_age)) = age; }

Copy the code

As you can see, the properties and methods in the class, when converted to c++ code, generate setter and getter methods, respectively.

① Use the keyword copy to modify the name, using the objc_setProperty method to assign the value

(2) Age without keyword modification is directly assigned by memory translation

The other option is to assign properties using objc_setProperty. Since this involves a coding problem, we’ll look at the implementation of objc_setProperty later.

3. Method body coding


{{(struct objc_selector *)"init"."@ @ 0:8 16", (void *)_I_LGPerson_init},
{(struct objc_selector *)"saySomething"."v16@0:8", (void *)_I_LGPerson_saySomething},
{(struct objc_selector *)"name"."@ @ 0:8 16", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:"."v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"age"."i16@0:8", (void *)_I_LGPerson_age},
{(struct objc_selector *)"setAge:"."v20@0:8i16", (void *)_I_LGPerson_setAge_},
{(struct objc_selector *)"name"."@ @ 0:8 16", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:"."v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"age"."i16@0:8", (void *)_I_LGPerson_age},
{(struct objc_selector *)"setAge:"."v20@0:8i16", (void *)_I_LGPerson_setAge_}}

Copy the code

OC method bodies are named according to certain rules, as shown above, where “v20@0:8i16” or something like that represents a specific TypeEncoding, and apple’s official website states that (we can use Command + Shift + 0 to look at TypeEncoding, then go to the end of the document, Go to the Apple Community website), let’s go straight to the image.

So, knowing these rules, let’s find a random code to analyze: for example: v20@0:8i16

1, v denotes void

2. 20 indicates that the memory usage is 20

3. @ indicates an ID

4. 0 indicates to start from position 0

5. : indicates SEL

6 and 8 indicate starting from position 8

7, I stands for int

8, 16 indicates the start of 16 (if there is a parameter, depending on the parameter type, the number of bytes allocated, multi-parameter and so on)

Now that we know the C++ low-level implementation of member variables and attributes, we will explore the implementation process of objc_setProperty from a deeper level.

Setter-getter method analysis for attributes

1. We can find the objc_Property function from the Runtime source


#pragmaMark - The statement part

OBJC_EXPORT void
objc_setProperty(id _Nullable self, SEL _Nonnull _cmd, ptrdiff_t offset,
                 id _Nullable newValue, BOOL atomic, signed char shouldCopy)
    OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);


#pragmaMark - Implementation section

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true.false.false);
}

Copy the code

When we assign, we need to pass in some parameters, _cmd, new value, offset, according to these parameters to store the attributes of a class, the following is the concrete implementation part.

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    #pragmaMark - If offset is 0, first assignment, direct assignment
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }
    
    #pragmaMark - if not the first assignment
    id oldValue;
    id *slot = (id*) ((char*)self + offset);
    
    #pragmaMark-copy logic
    if (copy) {
        #pragmaMark - If copy keyword is used, shallow copy, address
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        #pragmaMark - using the mutablecopy keyword, the address copy+ pointer
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        #pragmaMark - Otherwise, return new value, reference count + 1
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
    
    #pragmaMark - Atomic type judgment
    
    if(! atomic) {#pragmaMark - Non-atomic type, directly assigned
        oldValue = *slot;
        *slot = newValue;
    } else {
    
        #pragmaMark - Atomic type, lock operation
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock(a); oldValue = *slot; *slot = newValue; slotlock.unlock(a); }objc_release(oldValue);
}

Copy the code

This is a run-time analysis of attributes. Let’s trace LLVM to see what optimizations have been made for attributes at the compile level.

2. Implementation process of LLVM source analysis attribute

We don’t initially know how LLVM is implemented at the bottom, so let’s start with objc_setProperty.

  • 2.1. Start with objc_setProperty and find the implementation process backwards

    #pragmaImplementation of Mark-GetGetPropertyFn
    
    llvm::FunctionCallee getGetPropertyFn(a) {
    CodeGen::CodeGenTypes &Types = CGM.getTypes(a); ASTContext &Ctx = CGM.getContext(a);// id objc_getProperty (id, SEL, ptrdiff_t, bool)
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType()); CanQualType SelType = ... llvm::FunctionType *FTy = ... (IdType, Params));#pragmaMark - This creates an objc_getProperty method
    return CGM.CreateRuntimeFunction(FTy, "objc_getProperty");
  }


    #pragmaImplementation of Mark-GetSetPropertyFn

  llvm::FunctionCallee getSetPropertyFn(a) {
    CodeGen::CodeGenTypes &Types = CGM.getTypes(a); ASTContext &Ctx = CGM.getContext(a);// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType()); CanQualType SelType = ... llvm::FunctionType *FTy = ... (IdType, Params));#pragmaMark - This creates an objc_setProperty method
    return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
  }
Copy the code
  • 2.2. Then look at getSetPropertyFn, setter method execution process analysis

 llvm::FunctionCallee getSetPropertyFn(a) {
    CodeGen::CodeGenTypes &Types = CGM.getTypes(a); ASTContext &Ctx = CGM.getContext(a);// void objc_setProperty (id, SEL, ptrdiff_t, id, bool, bool)
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
    CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
    CanQualType Params[] = {
        IdType,
        SelType,
        Ctx.getPointerDiffType() - >getCanonicalTypeUnqualified(),
        IdType,
        Ctx.BoolTy,
        Ctx.BoolTy};
    llvm::FunctionType *FTy =
        Types.GetFunctionType(
          Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
  }

Copy the code
  • 2.3. Find the call layer function according to getSetPropertyFn, and then find the implementation of the logic code GetPropertySetFunction of the call layer.


#pragmaMark - Calls layer functions
 
 llvm::FunctionCallee GetPropertySetFunction(a) override {
    return ObjCTypes.getSetPropertyFn(a); }#pragmaMark - Calls the layer function to implement generateObjCSetterBody
  void
CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
                                        const ObjCPropertyImplDecl *propImpl,
                                        llvm::Constant *AtomicHelperFn) {
  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl(a); ObjCMethodDecl *setterMethod = propImpl->getSetterMethodDecl(a);// Just use the setter expression if Sema gave us one and it's
  // non-trivial.
  if (!hasTrivialSetExpr(propImpl)) {
    ...
  }
  PropertyImplStrategy strategy(CGM, propImpl);
  
  
 #pragmaMark - This is a conditional judgment. Passing in different values creates different methods
  
  switch (strategy.getKind()) {
  
 #pragmaMark-native type
  
  case PropertyImplStrategy::Native: {
  
    // We don't need to do anything for a zero-size struct.
    // Member variable size is 0 bytes, do not create setter-getter directly
    if (strategy.getIvarSize().isZero())
      return;
    // Get the starting position of the offset
    Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());
    LValue ivarLValue =
      EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0); Address ivarAddr = ivarLValuegetAddress(*this);

    // Currently, all atomic accesses have to be through integer
    
    // All atomic modifiers must be of type INTEGER
    llvm::Type *bitcastType =
      llvm::Type::getIntNTy(getLLVMContext(),
                            getContext().toBits(strategy.getIvarSize()));

    // Cast both arguments to the chosen operation type.
    argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
    ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);

    // This bitcast load is likely to cause some nasty IR.
    llvm::Value *load = Builder.CreateLoad(argAddr);

    // Perform an atomic store. There are no memory ordering requirements.
    llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
    store->setAtomic(llvm::AtomicOrdering::Unordered);
    return;
  }
  
  
 #pragmaThe mark-getSetProperty type
  
  case PropertyImplStrategy::GetSetProperty:
  case PropertyImplStrategy::SetPropertyAndExpressionGet: {

    llvm::FunctionCallee setOptimizedPropertyFn = nullptr;
    llvm::FunctionCallee setPropertyFn = nullptr;
    if (UseOptimizedSetter(CGM)) {
      And iOS 6.0 code and GC is off
      setOptimizedPropertyFn =
          CGM.getObjCRuntime().GetOptimizedPropertySetFunction(
              strategy.isAtomic(), strategy.isCopy());
      if(! setOptimizedPropertyFn) { CGM.ErrorUnsupported(propImpl, "Obj-C optimized setter - NYI");
        return; }}else {
    #pragmaMark - !!!!!!!!!!!!!!!!!!!!!!!!!!!
    #pragmaMark - Here GetPropertySetFunction is called to create a method
    #pragmaMark - !!!!!!!!!!!!!!!!!!!!!!!!!!!
      setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction(a);if(! setPropertyFn) { CGM.ErrorUnsupported(propImpl, "Obj-C setter requiring atomic copy");
        return; }}...return;
  }
  
Copy the code

So far, we can deduce that LLVM performs different creation operations according to different conditions, depending on the value of PropertyImplStrategy.

 class PropertyImplStrategy {
  public:
    enum StrategyKind {
      /// The 'native' strategy is to use the architecture's provided
      /// reads and writes.
      Native,

      /// Use objc_setProperty and objc_getProperty.
      GetSetProperty,

      /// Use objc_setProperty for the setter, but use expression
      /// evaluation for the getter.
      SetPropertyAndExpressionGet,

      /// Use objc_copyStruct.
      CopyStruct,

      /// The 'expression' strategy is to emit normal assignment or
      /// lvalue-to-rvalue expressions.
      Expression
    };

Copy the code

As shown above, when the property is initially created, it is determined which type it belongs to according to the type of the modifier, and then different setter-getter methods are generated respectively. Then the final caller is found according to the method body PropertyImplStrategy.


#pragmaMark - Here is the entry point, including some key word judgment, method generation strategy, etc

PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
  // prop delimiter
  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl(a);// Get the type of Kind in the attribute
  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind(a);Pragam mark - check whether copy, atomic, strong keywords are used
  
  IsCopy = (setterKind == ObjCPropertyDecl::Copy);
  IsAtomic = prop->isAtomic(a); HasStrong =false; // doesn't matter here.
  
  #pragmaMark calculates the size and alignment of member variables
  // Evaluate the ivar's size and alignment.
  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl(a); QualType ivarType = ivar->getType(a);auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
  IvarSize = TInfo.Width;
  IvarAlignment = TInfo.Align;
#pragma mark **********************************************

#pragmaMark will be positioned here if there is a copy or nonatomic modified property

  #pragmaMark left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down
  #pragmaMark left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down
  #pragmaMark left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left down down down down down down down down left left left left left left left left left left down down down down down down down down down left left left left left left left left left down down down down down down down down

  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }
  
  #pragmaMark write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write
  #pragmaMark write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write
  #pragmaMark write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write write

  
#pragmaMark if there is a retain processing logic, here is the MRC processing, not concern, reference counting
  // Handle retain.
  if (setterKind == ObjCPropertyDecl::Retain) {
    .......
  }


#pragmaMark if not atomic type processing logic
  // If we're not atomic, just use expression accesses.
  if(! IsAtomic) { Kind = Expression;return;
  }

  // Properties on bitfield ivars need to be emitted using expression
  // accesses even if they're nominally atomic.
  if (ivar->isBitField()) {
    Kind = Expression;
    return; }...Copy the code

Summarize the execution logic of attributes in LLVM

✨ Above, we used the way of backward reasoning to check the execution rules of attribute generator in LLVM, and then summarized the execution process forward and backward.

If @property is declared in a class, the system will decide whether to use the memory translation method or the objc_setProperty method based on the modified type.

② If it is a basic data type, the computer will assign the value by memory translation

(3) If it is an object type, LLVM will first determine the modifier keyword and generate different setter methods through the keyword.

Above is to explore the process of system setter – getter method, although the exploration process is more troublesome, but it is a layer of understanding, later in the process of development, will pay special attention to the use of some key words, fair use can avoid the waste of memory caused by improper, from deep promoted their abilities, the exploration, is only a preliminary attempt, For more details, explore again when you have time!