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!