preface

We talked about the structure of a class. We learned about metaclasses, ISA relationships, inheritance relationships, and methods and attributes from the bits of a class. In this article we continue to dig deeper and explore what else is in the class.

Explore the underlying source code of DDAnimal.m

@interface DDAnimal : NSObject {
    NSString *_sex;
}

@property (nonatomic, copy) NSString *name;    ///<
@property (nonatomic, strong) NSString *nickName;    ///<
@property (nonatomic, assign) NSInteger height;    ///< 

@end
Copy the code

This is the upper layer of the file, using clang to compile ddAnimal. m into ddanimal. CPP.

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk DDAnimal.m -o DDAnimal.cpp

Compiled code

#ifndef _REWRITER_typedef_DDAnimal
#define _REWRITER_typedef_DDAnimal
typedef struct objc_object DDAnimal;
typedef struct {} _objc_exc_DDAnimal;
#endif

extern "C" unsigned long OBJC_IVAR_$_DDAnimal$_name;
extern "C" unsigned long OBJC_IVAR_$_DDAnimal$_nickName;
extern "C" unsigned long OBJC_IVAR_$_DDAnimal$_height;
struct DDAnimal_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_sex;
	NSString * _Nonnull _name;
	NSString * _Nonnull _nickName;
	NSInteger _height;
};


// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, strong) NSString *nickName;
// @property (nonatomic, assign) NSInteger height;

/* @end */

#pragma clang assume_nonnull end

// @implementation DDAnimal


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

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

static NSString * _Nonnull _I_DDAnimal_nickName(DDAnimal * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_DDAnimal$_nickName)); }
static void _I_DDAnimal_setNickName_(DDAnimal * self, SEL _cmd, NSString * _Nonnull nickName) { (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_DDAnimal$_nickName)) = nickName; }

static NSInteger _I_DDAnimal_height(DDAnimal * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_DDAnimal$_height)); }
static void _I_DDAnimal_setHeight_(DDAnimal * self, SEL _cmd, NSInteger height) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_DDAnimal$_height)) = height; }
// @end
Copy the code

The class is a struct, and all attributes are commented out as underlined member variables. The other difference is that attributes create set and GET methods.

static void OBJC_CLASS_SETUP_$_DDAnimal(void ) {
	OBJC_METACLASS_$_DDAnimal.isa = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_DDAnimal.superclass = &OBJC_METACLASS_$_NSObject;
	OBJC_METACLASS_$_DDAnimal.cache = &_objc_empty_cache;
	OBJC_CLASS_$_DDAnimal.isa = &OBJC_METACLASS_$_DDAnimal;
	OBJC_CLASS_$_DDAnimal.superclass = &OBJC_CLASS_$_NSObject;
	OBJC_CLASS_$_DDAnimal.cache = &_objc_empty_cache;
}
Copy the code

In this function, you can also see the inheritance of DDAnimal and the reference to ISA, as well as the METACLASS (METACLASS), confirming the isa direction and inheritance relationship explored earlier.

Type encoding

{(struct objc_selector *)"setName:"."v24@0:8@16", (void *)_I_DDAnimal_setName_}
Copy the code

Notice that this line of code should be the definition of the setName method, so what does “v24@0:8@16” mean? Refer to the type coding chart on apple’s website

According to the comparison above, it can be seen that:

V: indicates the return value. One is returnedvoid;
24: indicates the memory occupied by this string, total24Bytes; @ : represents the first parameter passed, an id parameter, in this case self;0From:0Position # starts saving the first parameter; : : represents the second argument passed, passing a method selector;8From:8Start storing the second parameter in position #; @ : indicates the third parameter passed, also an id parameter, in this case an NSString;16From:16Start storing the third parameter in position #;static void _I_DDAnimal_setName_(DDAnimal * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct DDAnimal, _name), (id)name, 0.1); } The implementation of the contrast method, as we have analyzed, is very clear and obvious.Copy the code

objc_setProperty() VS self + OBJC_IVAR_$_DDAnimal$_nickName

Self + OBJC_IVAR_$_DDAnimal$_nickName OBJC_IVAR_$_DDAnimal$_nickName self + OBJC_IVAR_$_DDAnimal$_nickName Self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName

// Assign to __OFFSETOFIVAR__,
extern "C" unsigned long int OBJC_IVAR_$_DDAnimal$_nickName __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct DDAnimal, _nickName);
// Define __OFFSETOFIVAR__
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
Copy the code

((long long) &((TYPE *)0)->MEMBER)

A constant with a value of 0 permitted by the ANSI C standard is cast to a pointer of any type, and the result is a NULL pointer, so ((type *)0) results in a NULL pointer of type *. Of course it is illegal to use this NULL pointer to access members of type, but the intent of &(((type *)0)->MEMBER) is simply to compute the address of the MEMBER field. Smart compilers do not generate code to access Type at all, but simply calculate the (constant) address at compile time based on the memory layout of Type and the initial address of the struct instance, thus completely avoiding the memory access problem via NULL Pointers. Since the initial address is 0, the value of this address is the offset of the field from the structure’s base address. The above approach avoids instantiating a Type object, and the evaluation takes place at compile time with no run-time burden.

Self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName = self + OBJC_IVAR_$_DDAnimal$_nickName What about objc_setProperty()?

objc_setProperty()

Method declarations and implementations can be found in objC source code, but there is no way to know why some use objc_setProperty() and others do not. Obviously, this should be decided at compile time, and since it is compile time, it is time to look at LLVM (put on the pain mask and look at LLVM). Search for objc_setProperty in LLVM, and a long list comes up, and the pain begins, to see where it is called one by one. On closer inspection, the search results come up quite a lot, but a lot of them are comments, except comments, and finally locked in here.

return CGM.CreateRuntimeFunction(FTy, "objc_setProperty");
Copy the code

Creates an objc_setProperty runtime method. If you create something, you must create it by calling it. This function is getSetPropertyFn(), so where is this function called

llvm::FunctionCallee CGObjCMac::GetPropertySetFunction() {
  return ObjCTypes.getSetPropertyFn();
}
Copy the code

Find GetPropertySetFunction ()

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 {
      setPropertyFn = CGM.getObjCRuntime().GetPropertySetFunction(); // this is called
      if(! setPropertyFn) { CGM.ErrorUnsupported(propImpl,"Obj-C setter requiring atomic copy");
        return; }}Copy the code

Find where to call GetPropertySetFunction(), find that it is called under these two conditions, and then find where those conditions are assigned.

  • PropertyImplStrategy: :GetSetProperty:
  • PropertyImplStrategy: :SetPropertyAndExpressionGet:
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();

  IsCopy = (setterKind == ObjCPropertyDecl::Copy);
  IsAtomic = prop->isAtomic();
  HasStrong = false; // doesn't matter here.

  // Evaluate the ivar's size and alignment.
  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
  QualType ivarType = ivar->getType();
  auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
  IvarSize = TInfo.Width;
  IvarAlignment = TInfo.Align;

  // If we have a copy property, we always have to use getProperty/setProperty.
  // TODO: we could actually use setProperty and an expression for non-atomics.
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }

  // Handle retain.
  if (setterKind == ObjCPropertyDecl::Retain) {
    // In GC-only, there's nothing special that needs to be done.
    if (CGM.getLangOpts().getGC() == LangOptions::GCOnly) {
      // fallthrough

    // In ARC, if the property is non-atomic, use expression emission,
    // which translates to objc_storeStrong. This isn't required, but
    // it's slightly nicer.
    } else if(CGM.getLangOpts().ObjCAutoRefCount && ! IsAtomic) {// Using standard expression emission for the setter is only
      // acceptable if the ivar is __strong, which won't be true if
      // the property is annotated with __attribute__((NSObject)).
      // TODO: falling all the way back to objc_setProperty here is
      // just laziness, though; we could still use objc_storeStrong
      // if we hacked it right.
      if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)
        Kind = Expression;
      else
        Kind = SetPropertyAndExpressionGet;
      return;

    // Otherwise, we need to at least use setProperty. However, if
    // the property isn't atomic, we can use normal expression
    // emission for the getter.
    } else if(! IsAtomic) { Kind = SetPropertyAndExpressionGet;return;

    // Otherwise, we have to use both setProperty and getProperty.
    } else {
      Kind = GetSetProperty;
      return; }}}Copy the code

Find PropertyImplStrategy implementation (code is too long, here only captures the GetSetProperty, SetPropertyAndExpressionGet related). We found that

  • Copy modifies GetSetProperty.
  • Retain modifies:
    1. In ARC and not atomic,if (ivarType.getObjCLifetime() == Qualifiers::OCL_Strong)Situation,Kind = Expression, otherwise,Kind = SetPropertyAndExpressionGet;
    1. If it’s not ARC or atomic, if it’s not atomic,Kind = SetPropertyAndExpressionGet, otherwise,Kind = GetSetProperty.

summary

So now we’ve figured out why there are two sets of functions. Because SetPropertyAndExpressionGet and GetSetProperty will use objc_setProperty (), So both nonatomic and atomic properties modified by copy and retain will use objc_setProperty() at the bottom.

There are other modifiers assigned in the PropertyImplStrategy implementation, too much code for those interested.

objc_getProperty()

In the same way we find out when objc_getProperty() is used. Too much code, just post key points.

return CGM.CreateRuntimeFunction(FTy, "objc_getProperty");
Copy the code
llvm::FunctionCallee CGObjCMac::GetPropertyGetFunction() {
  return ObjCTypes.getGetPropertyFn();
}
Copy the code
case PropertyImplStrategy::GetSetProperty: {
    llvm::FunctionCallee getPropertyFn =
        CGM.getObjCRuntime().GetPropertyGetFunction();
}
Copy the code

Finding the final use, you can see that only the GetSetProperty type will use objc_getProperty. Combined with the PropertyImplStrategy initializer found above, copy and the (atomic, retain) modifier kind = GetSetProperty. But is this really the case? Let’s verify this code

/ / OC code
@property (nonatomic, copy) NSString *name;
@property (atomic, retain) NSString *retainName;

/ / c + + code
static NSString * _Nonnull _I_DDAnimal_name(DDAnimal * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_DDAnimal$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

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

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _Nonnull _I_DDAnimal_retainName(DDAnimal * self, SEL _cmd) { typedef NSString * _Nonnull _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct DDAnimal, _retainName), 1); }
static void _I_DDAnimal_setRetainName_(DDAnimal * self, SEL _cmd, NSString * _Nonnull retainName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct DDAnimal, _retainName), (id)retainName, 1.0); }
Copy the code

As you can see, both properties have objc_setProperty, but copy does not have objc_getProperty. Why is that?

case PropertyImplStrategy::GetSetProperty: {
    llvm::FunctionCallee getPropertyFn =
        CGM.getObjCRuntime().GetPropertyGetFunction();
    if(! getPropertyFn) { CGM.ErrorUnsupported(propImpl,"Obj-C getter requiring atomic copy");
      return; }}Copy the code

Looking at the source code, notice that there is also a test to determine whether the method generated successfully. If not, “obj-c getter Atomic copy” is displayed, meaning atomic modifications are required to modify the code.

/ / OC code
@property (atomic, copy) NSString *name;

/ / c + + code
extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _Nonnull _I_DDAnimal_name(DDAnimal * self, SEL _cmd) { typedef NSString * _Nonnull _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct DDAnimal, _name), 1); }
Copy the code

Find the getter for name using objc_getProperty.

summary

The attributes modified by copy and retain must be atomic in order to use objc_getProperty() underneath.

supplement

The above explains when objc_getProperty is used, but it always feels a bit farfetched, so I went on to look for the relevant code in LLVM and actually found the suspect code.

unsigned Attributes = PD->getPropertyAttributes();
if (mustSynthesizeSetterGetterMethod(IMD, PD, true /*getter*/)) { bool GenGetProperty = ! (Attributes & ObjCPropertyAttribute::kind_nonatomic) && (Attributes & (ObjCPropertyAttribute::kind_retain | ObjCPropertyAttribute::kind_copy)); std::string Getr;if(GenGetProperty && ! objcGetPropertyDefined) { objcGetPropertyDefined =true;
      // FIXME. Is this attribute correct in all cases?
      Getr = "\nextern \"C\" __declspec(dllimport) "
            "id objc_getProperty(id, SEL, long, bool); \n";
    }
    RewriteObjCMethodDecl(OID->getContainingInterface(),
                          PID->getGetterMethodDecl(), Getr);
    Getr += "{";
    // Synthesize an explicit cast to gain access to the ivar.
    // See objc-act.c:objc_synthesize_new_getter() for details.
    if (GenGetProperty) {
      // return objc_getProperty(self, _cmd, offsetof(ClassDecl, OID), 1)
      Getr += "typedef ";
      const FunctionType *FPRetType = nullptr;
      RewriteTypeIntoString(PID->getGetterMethodDecl()->getReturnType(), Getr,
                            FPRetType);
      Getr += " _TYPE";
      if (FPRetType) {
        Getr += ")"; // close the precedence "scope" for "*".

        // Now, emit the argument types (if any).
        if (const FunctionProtoType *FT = dyn_cast<FunctionProtoType>(FPRetType)){
          Getr += "(";
          for (unsigned i = 0, e = FT->getNumParams(); i ! = e; ++i) {if (i) Getr += ",";
            std::string ParamStr =
                FT->getParamType(i).getAsString(Context->getPrintingPolicy());
            Getr += ParamStr;
          }
          if (FT->isVariadic()) {
            if (FT->getNumParams())
              Getr += ",";
            Getr += "...";
          }
          Getr += ")";
        } else
          Getr += "()";
      }
      Getr += "; \n";
      Getr += "return (_TYPE)";
      Getr += "objc_getProperty(self, _cmd, ";
      RewriteIvarOffsetComputation(OID, Getr);
      Getr += ", 1)";
    }
    else
      Getr += "return " + getIvarAccessString(OID);
    Getr += "; }";
    InsertText(startGetterSetterLoc, Getr);
  }
Copy the code

Here you see a string concatenation, and the concatenated string looks very similar to the one in c++ code.

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct DDAnimal, _name), 1); }
Copy the code

At this point, we can go out on a limb and guess that this is where the code in c++ is stitched together. If GenGetProperty is not nonatomic and must be copy or retain, objc_getProperty will be used. Again, you can find objc_setProperty, and you can check it out for yourself.