IOS bottom layer exploration – KVC

Time:2020-2-27

IOS bottom Exploration Series

  • IOS underlying exploration – alloc & init
  • IOS underlying exploration – calloc and ISA
  • IOS underlying exploration class
  • IOS underlying exploration cache
  • IOS bottom layer exploration method
  • IOS underlying exploration – Message lookup
  • IOS underlying exploration – message forwarding
  • IOS underlying exploration – application loading
  • IOS underlying exploration – class loading
  • IOS bottom layer exploration classification loading
  • IOS underlying exploration – class extension and associated objects
  • IOS bottom layer exploration – KVC

1、 On KVC

Key Value CodingThat is to sayKVCyesiOSA very important concept in development, Chinese translation comes fromKey value encodingThe specific definition of this concept can be found inAppleFound in the official documents section of.

Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties.
[translation]KVCIt is throughNSKeyValueCodingThis informal protocol enables a mechanism by which objects that follow this protocol provide indirect access to their properties.

We usually use the accessor method to access the properties of an objectgetterTo get the property value, use thesetterTo set the property value. While inObjective-CIn, we can also get and set property values directly through instance variables. As shown in the following code:

// JHPerson.h
@interface JHPerson : NSObject
{
    @public
    NSString *myName;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

// ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    
    JHPerson *person = [[JHPerson alloc] init];
    person.name      = @"leejunhui";
    person.age       = 20;
    person->myName   = @"leejunhui";
    NSLog(@"%@ - %ld - %@",person.name, person.age,person->myName);
}

We’re all familiar with this way. The properties are generated automatically by the compilergetterandsetterAs well as the corresponding instance variables, which we have explored before, we canroTo find their traces, interested readers can turn to the previous articles.

Here, the differences among instance variables, member variables and attributes are clarified:
Variables declared in @ interface brackets are collectively referred to asMember variables
Member variables are actually composed of two parts:Instance variables + Basic data type variable
andattribute = Member variables + Getter method + Setter method

In fact, there are two situations here: self implementation and compiler implementation.

1.1 self realizationgetterandsetter

Here we useJHPersonClassnameFor example, we rewritenameOfgetterandsetterMethod, here is another point of attention, we need to@interfaceThe following instance variables are declared in_name, the specific code is as follows:

// JHPerson.h
@interface JHPerson : NSObject
{
    @public
    NSString *myName;
    NSString *_name;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

// JHPerson.m
@implementation JHPerson

- (NSString *)name
{
    return _name;
}

- (void)setName:(NSString *)name
{
    _name = name;
}

@end

Then, we aremain.mUsing point syntax pairs innameAssign and printnameValue:

#import <Foundation/Foundation.h>
#import "JHPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JHPerson *person = [[JHPerson alloc] init];
        person.name      = @"leejunhui";
        Nslog (@ "person name is% @", person. Name);
    }
    return 0;
}

The printing results are as follows:

-[JHPerson setName:] - leejunhui
-[JHPerson name] - leejunhui
Person name: leejunhui

Obviously, the results here show thatperson.name = @"leejunhui";It’s actually calledJHPersonClasssetNameMethod andNslog (@ "person name is% @", person. Name);Is callednameMethod.

I believe that the readers should be familiar with this logic. Next, we will analyze the compiler automatic generationgetterandsetterScene.

1.2 compiler automatic implementationgetterandsetter

Before we explore, let’s think about a problem. According to our current cognition, if we don’t rewrite the attributegetterandsetterMethod and declare the corresponding instance variables. Then the compiler will help us do this. Does it mean that how many attributes there are and how many corresponding ones will be generatedgetterandsetterWhat about it? Obviously, the compiler will not be so stupid. It is clumsy in both performance and design. WelibObjcA source file can be found in the source code:objc-accessors.mm, there are many methods in this file that literally look like setting properties, as shown in the following figure:

IOS bottom layer exploration - KVC

We focus on this approach:objc_setProperty_nonatomic_copy, why? becausenameProperty declared as@property (nonatomic, copy) NSString *name;, both of which containnonatomicandcopyKeywords, we might as wellobjc_setProperty_nonatomic_copyMethod. Note that at this time, we need to comment out what we just addedgetterandsetterMethod.

IOS bottom layer exploration - KVC

IOS bottom layer exploration - KVC

Bingo~,objc_setProperty_nonatomic_copyThe method is indeed called, and the value we assigned is also correct. We come to the internal implementation of this method:

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

You can see that there is another layer wrapped here. The real implementation isreallySetProperty

This method is not very complex. Let’s go through the parameters of this method.

1. First of all, this methodoffsetParameters, we have explored aboutMemory offsetWe will not go into details here. We know thatisaPointers account for8Bytes, also sent to usJHPersonClass has an instance variable in its declarationmyNameThis is an instance variable of string type. It also takes8Bytes, so here’soffsetby16, which means offset16Bytes to set propertiesname
IOS bottom layer exploration - KVC

2. and thenatomicParameter, which depends on whether theatomicstillnonatomic, which means the atomicity of operation, and many materials on the Internet sayatomicIt is to ensure the multithreading safety of the object. In fact, it is only to ensure that when you access it, it will return a intactValueHowever, the official explanation of realm is as follows:

If thread a calls getter and thread B and thread C calls setter at the same time, there are three possibilities: the original value before B and C set, the value of B set and the value of C set. At the same time, the final value of this attribute may be the value of B set or C set. thereforeatomicIt does not guarantee the thread safety of the object. In other wordsatomicThe thread safety is only guaranteedgetterandsetterThe thread safety of access method does not guarantee the thread safety of the whole object.

nonatomicThere is no such guarantee for keywords,nonatomicReturning your object may not be completevalue。 Therefore, atomic operation is very necessary in multithreaded environment, otherwise it may cause wrong results. But only useatomicIt will not make the object thread safe. We need to addlockTo ensure thread safety.

nonatomicobjectsetterandgetterMethod implementation:

- (void)setCurrentImage:(UIImage *)currentImage
{
   if (_currentImage != currentImage) {
       [_currentImage release];
       _currentImage = [currentImage retain];

   }
}
- (UIImage *)currentImage
{
   return _currentImage;
}

atomicobjectsetterandgetterMethod implementation:

- (void)setCurrentImage:(UIImage *)currentImage
{
   @synchronized(self) {
       if (_currentImage != currentImage) {
           [_currentImage release];
           _currentImage = [currentImage retain];

       }
   }
}
- (UIImage *)currentImage
{
   @synchronized(self) {return _currentImage;}
}

3. finally,copyandmutableCopyParameters, speaking ofcopyKey words may be reviewediOSThe property identifier in and the corresponding variable identifier.


stayARCThere are several variable identifiers related to memory management in:

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing
Variable identifier Effect
__strong The identifier used by default. Only when there is a strong pointer to an object, the object will live forever
__weak Declare this referenceWill not keepThe survival of the referenced object. If the object has no strong reference, the weak reference will beSet asnil
__unsafe_unretained Declare this referenceWill not keepThe survival of the referenced object. If the object does not have a strong reference, it does notIt’s going to be nil。 If the object it references is recycled, the pointer becomesWild pointer
__autoreleasing The parameter (ID *) used to indicate the value passed by reference is automatically released when the function returns

The usage of the variable identifier is as follows:

Number* __strong num = [[Number alloc] init];

Be careful__strongShould be placed*And variable names, put in other places is strictly incorrect, but the compiler will not report errors.


Property identifier

@property (atomic/nonatomic/assign/retain/strong/weak/unsafe_unretained/copy) Number* num
Property identifier Effect
atomic It indicates that the read-write operation of this attribute is atomic, but it does not guarantee the multithreading safety of the object
nonatomic It indicates that the read-write operation of this attribute is non atomic and its performance is better thanatomicBecause there is no lock overhead
assign IndicatesetterJust oneSimple assignment operation, usually used forBasic numerical typesFor exampleCGFloatandNSInteger
strong Indicates that the attribute defines aOwner relationship。 When setting a new value for a property, the first step is toretain, old valuerelease, then assign the value
weak Indicates that the attribute defines aNon owner relationship。 When a new value is set to a property, the value will notretain, the old value will not proceedrelease, but similarassignOperation. However, when the object pointed to by the property is destroyed, the property will be destroyedSet to nil
unsafe_unretained Semantics andassignSimilar, butFor object typesOf, indicating a non owned(unretained)Is not set to when the object is destroyednil(unsafeRelationship.
copy Be similar tostrong, but when assigningcopyOperations, notretainOperation. Usually when you need to keep an immutable object(NSStringMost commonly), andPrevent it from being accidentally changedWhen used.

Consequences of incorrect use of property identifiers
If we give a primitive type settingstrong\weak\copy, the compiler will directly report an error:

Property with ‘retain (or strong)’ attribute must be of object type

Set tounsafe_unretainedIt can be compiled, just used toassignThere is no difference.
In turn, we give aNSObjectWhen the property is set to assign, the compiler will alarm:

Assigning retained object to unsafe property; object will be released after assignment

As the warning said, the object is released immediately after the assignment, and the corresponding property becomes a wild pointer. The operation related to the property will crash directly when the runtime runs. And set upunsafe_unretainedIt’s the same effect (set toweakNo crash).

unsafe_unretainedUsefulness
unsafe_unretainedIt is almost the least used identifier in practice. In use, it is mainly used in the following aspects:
1. Compatibility considerations.iOS4And it hasn’t been introduced beforeweakIn this case, if you want to express the semantics of weak references, you can only useunsafe_unretained。 This situation is now rare.
2. Performance considerations. UseweakIt has some impact on performance, so it can be used in places with high performance requirementsunsafe_unretainedreplaceweak。 One example is the implementation of yymodel, which is widely used in pursuit of higher performanceunsafe_unretainedAs a variable identifier.


static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
  • We turn our attention toreallySetPropertyCome on, let’s judge firstoffsetWhether it is0

    • If so0, calling methods directlyobject_setClassSet the current object’sclass, which obviously sets the object’sisaPointer.
  • Declare a temporary variableoldValue
  • takeselfFirst, convert it to a string pointer, then perform a memory translation to get the memory offset value of the property to be set, and then convert it toid*Type.
  • Determine whether the identifier of the property to be set needs to be setcopyoperation

    • If necessary, thenewValueThat is, the property value to be set is sentcopyWithZoneNews,The purpose of this step is to getnewValueCopy of, then overwritenewValue, making the incomingnewValueAny subsequent changes will not affect the property value
  • Determine whether the identifier of the property to be set needs to be setmutableCopyoperation

    • If necessary, thenewValueThat is, the property value to be set is sentmutableCopyWithZonenews
  • If the property to be set is neither executedcopyNor does it carry out.mutableCopy, then determine whether the value to be set is equal

    • If it is equal, it means that the new value is equal to the old value, and directly returns
    • If not, send the new valueobjc_retainMessage carried outretainOperation, and then overwrite the return valuenewValueupper
  • Then judge whether the attribute assignment operation is atomic operation

    • If it is not an atomic operation, assign the property to a temporary variableoldValue, and then assign the new value
    • If it is an atomic operation, lock the assignment operation to ensure the data integrity and prevent the data from changing during the assignment, which is also confirmedatomicIt is to ensure the thread safety of the read-write operation of the property
    • IOS bottom layer exploration - KVC
  • Final pairoldValueThat is to say, the old value is used for memory release

PS: Not all properties are automaticsetterWill comeobjc_setProperty
IOS bottom layer exploration - KVC
So, what are the specific circumstances in which the attributes will come here? Let’s do a simple test

// JHTest.h
@interface JHTest
@property (nonatomic, strong) NSMutableArray *arrayNonatomicAndStrong;
@property (nonatomic, copy)   NSMutableArray *arrayNonatomicAndCopy;
@property (nonatomic, strong) NSString *stringNonatomicAndStrong;
@property (nonatomic, copy)   NSString *stringNonatomicAndCopy;
@property (nonatomic, assign) int ageNonatomicAndAssign;
@property (nonatomic, weak) NSString *stringNonatomicAndWeak;
@property (nonatomic, retain) NSString *stringNonatomicAndRetain;

@property (atomic, strong) NSMutableArray *arrayAtomicAndStrong;
@property (atomic, copy)   NSMutableArray *arrayAtomicAndCopy;
@property (atomic, strong) NSString *stringAtomicAndStrong;
@property (atomic, copy)   NSString *stringAtomicAndCopy;
@property (atomic, assign) int ageAtomicAndAssign;
@property (atomic, weak) NSString *stringAtomicAndWeak;
@property (atomic, retain) NSString *stringAtomicAndRetain;
@end

// main.m
JHTest *test = [[JHTest alloc] init];
NSMutableArray *testMutableArray = @[].mutableCopy;
        
test.arrayNonatomicAndStrong = testMutableArray;
test.arrayNonatomicAndCopy = testMutableArray;
Test. Stringnonatomicandstrong = @ "ha ha Da";
Test. Stringnonatomicandcopy = @ "ha ha Da";
test.ageNonatomicAndAssign = 18;
Test. Stringnonatomicandweek = @ "ha ha Da";  
Test. Stringnonatomicandretain = @ "ha ha Da"; 

test.arrayAtomicAndStrong = testMutableArray;
test.arrayAtomicAndCopy = testMutableArray;
Test. Stringatomicandstrong = @ "ha ha Da";
Test. Stringatomicandcopy = @ "ha ha Da";
test.ageAtomicAndAssign = 18; 
Test. Stringatomicandweek = @ "ha ha Da";  
Test. Stringatomicandretain = @ "ha ha Da";

We use breakpoint debugging to check whether the breakpoint will comereallySetProperty, the test results are as follows:

attribute Whether to enterreallySetProperty
arrayNonatomicAndStrong no
arrayNonatomicAndCopy yes
stringNonatomicAndStrong no
stringNonatomicAndCopy yes
ageNonatomicAndAssign no
stringNonatomicAndWeak no
stringNonatomicAndRetain no
attribute Whether to enterreallySetProperty
arrayAtomicAndStrong yes
arrayAtomicAndCopy yes
stringAtomicAndStrong yes
stringAtomicAndCopy yes
ageAtomicAndAssign no
stringAtomicAndWeak no
stringAtomicAndRetain yes

It’s not hard to see from these two sets of test results, becausereallySetPropertyIn fact, there are atomic write operations inside andcopyormutableCopyOperation andretainOperation, and for property identifiernonatomicAnd notcopyIn terms of properties, there is no need for atomic operation andcopyormutableCopyOperation.
The corresponding action of attribute identifier shown earlier also proves that only when the attribute needs to becopyormutableCopyOperation or atomic operation orretainOperation will be optimized by compilerobjc_setProperty_xxx => reallySetPropertyThe process. In other words, inClangWhen compiling, the compiler will definitely judge the attributes and trigger this process only when there are required attributes.

We use a table to summarize:

Bottom method Corresponding property identifier
objc_setProperty_nonatomic_copy nonatomic + copy
objc_setProperty_atomic_copy atomic + copy
objc_setProperty_atomic atomic + retain/strong

Let’s finish the analysisreallySetPropertyAfter that, there is a question, which step is the system callingobjc_setProperty_xxxHow about something like that? The answer isLLVM。 We canLLVMSearch keywords in the source code ofobjc_setProperty

IOS bottom layer exploration - KVC

We can see inclangCompiler front endRewriteModernObjCUnder namespaceRewritePropertyImplDeclMethod:

IOS bottom layer exploration - KVC

And then weCodeGenUnder anonymous namespace under directoryObjcCommonTypesHelperOfgetOptimizedSetPropertyFnYou can see the following code at:

IOS bottom layer exploration - KVC

We thengetOptimizedSetPropertyFnSearch for keywords:

  llvm::FunctionCallee GetOptimizedPropertySetFunction(bool atomic,
                                                       bool copy) override {
    return ObjCTypes.getOptimizedSetPropertyFn(atomic, copy);
  }

Then we searchGetOptimizedPropertySetFunction

IOS bottom layer exploration - KVC

aboutLLVMLet’s explore this first, and then let’s review itKVCSeveral commonly used use scenarios.

2、 In depth KVC

2.1 access object properties

  1. adoptvalueForKey:andsetValue:ForKey:comeIndirectGet and set property values
JHPerson *person = [[JHPerson alloc] init];
        [person setValue:@"leejunhui" forKey:@"name"];
        Nslog (@ "person's name is% @", [person valueforkey: @ "name");
        
        //Print as follows
        Person's name is: leejunhui
  • valueForKey: – Returns the value of a property named by the key parameter. If the property named by the key cannot be found according to the rules described in Accessor Search Patterns, then the object sends itself a valueForUndefinedKey: message. The default implementation of valueForUndefinedKey: raises an NSUndefinedKeyException, but subclasses may override this behavior and handle the situation more gracefully.

[translation]valueForKeyReturn tokeyThe value of the property named by the parameter. If basedVisitor search modeThe rule described in cannot be found bykeyNamed property, the object will send to itselfvalueForUndefinedKey:News.valueForUndefinedKey:The default implementation ofNSUndefinedKeyExceptionException, but subclasses can override this behavior and handle this situation more gracefully.

  • setValue:forKey:: Sets the value of the specified key relative to the object receiving the message to the given value. The default implementation of setValue:forKey: automatically unwraps NSNumber and NSValue objects that represent scalars and structs and assigns them to the property. See Representing Non-Object Values for details on the wrapping and unwrapping semantics.

If the specified key corresponds to a property that the object receiving the setter call does not have, the object sends itself a setValue:forUndefinedKey: message. The default implementation of setValue:forUndefinedKey: raises an NSUndefinedKeyException. However, subclasses may override this method to handle the request in a custom manner.

[translation]setValue:forKey:: specify the message receiverkeyThe value of is set to the given value. By default, theNSNumberandNSValueObject is unpacked and assigned to a property. If specifiedkeyThere is no corresponding attributesetterImplementation, the object will send to itselfsetValue:forUndefinedKey:Message, and the default implementation of the message throws aNSUndefinedKeyExceptionException. But subclasses can override this method to handle requests in a custom way.

2.valueForKeyPath:andsetValue:ForKeyPath:
KVC in storyboard or Xib

IOS bottom layer exploration - KVC

As shown in the figure above,StoryboardThe Properties menu of a view in can set theKey Path, which leads to theRouteAnother kind ofKVCThe way, that’svalueForKeyPath:andsetValue:ForKeyPath:

A key path is a string of dot-separated keys used to specify a sequence of object properties to traverse. The property of the first key in the sequence is relative to the receiver, and each subsequent key is evaluated relative to the value of the previous property. Key paths are useful for drilling down into a hierarchy of objects with a single method call.

[translation]keypathIs a string separated by dots that represents the sequence of object properties to traverse. First in sequencekeyRelative to the recipient, and each subsequentkeyAll with the previous levelkeyRelated.keypathIt is useful for a single method call to drill down into the internal structure of an object.

adoptlayer.cornerRadiusthisKey Path, to the leftViewOflayerAttributecornerRadiusProperty.

  • valueForKeyPath: – Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key—that is, for which the default implementation of valueForKey: cannot find an accessor method—receives a valueForUndefinedKey: message.

[translation]valueForKeyPath:: returns the assignment relative to the recipientkey pathThe value above.key pathAny object in the path sequence that does not match the key value encoding of a particular key (that isvalueForKey:The default implementation of cannot find the object of the accessor method) will receivevalueForUndefinedKey:News.

  • setValue:forKeyPath: – Sets the given value at the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key receives a setValue:forUndefinedKey: message.

[translation]setValue:forKeyPath:: specify the message receiverkey pathThe value of is set to the given value.key pathAny object in the path sequence that does not match the key value encoding of a particular key will receivesetValue:forUndefinedKey:news

// JHPerson.h
@property (nonatomic, strong) JHAccount *account;

// JHAccount.h
@property (nonatomic, copy) NSString *balance;

// main.m
person.account = [[JHAccount alloc] init];
[person setValue:@"666" forKeyPath:@"account.balance"];
Nslog (@ "person's account balance is% @", [person valueforkeypath: @ "account. Balance");

//Printout
Person's account balance is 666

3.dictionaryWithValuesForKeys:andsetValuesForKeysWithDictionary:

  • dictionaryWithValuesForKeys: – Returns the values for an array of keys relative to the receiver. The method calls valueForKey: for each key in the array. The returned NSDictionary contains values for all the keys in the array.

Return thekeyThe value of the array. This method will bekeycallvalueForKey:。 ReturnedNSDictionaryContains the values of all keys in the array.

  • setValuesForKeysWithDictionary: – Sets the properties of the receiver with the values in the specified dictionary, using the dictionary keys to identify the properties. The default implementation invokes setValue:forKey: for each key-value pair, substituting nil for NSNull objects as required.

Use the dictionary key to identify the attribute, and then use the corresponding value in the dictionary to set the attribute value of the message receiver. The default implementation calls for each key value pairsetValue:forKey:。 Setting requiresnilreplace withNSNull

[person setValuesForKeysWithDictionary:@{@"name": @"junhui", @"age": @(18)}];
NSLog(@"%@", [person dictionaryWithValuesForKeys:@[@"name", @"age"]]);       
        
 //Printout
{
    age = 18;
    name = junhui;
}

Collection objects, such as NSArray, NSSet, and NSDictionary, can’t contain nil as a value. Instead, you represent nil values using the NSNull object. NSNull provides a single instance that represents the nil value for object properties. The default implementations of dictionaryWithValuesForKeys: and the related setValuesForKeysWithDictionary: translate between NSNull (in the dictionary parameter) and nil (in the stored property) automatically.
Collection objects (for exampleNSArrayNSSetandNSDictionary)Cannot containnilAs a value. But useNSNullObject representationnilValue.NSNullA single instance is provided to represent the nil value of an object property.dictionaryWithValuesForKeys:andsetValuesForKeysWithDictionary:The default implementation ofNSNull(indictionaryIn parameters) andnil(in a stored property).
IOS bottom layer exploration - KVC

2.2 accessing set properties

Let’s take a look at the following code firstJHPersonClass add a propertyarray, the type is immutable array, and then modify this property:

// JHPerson.h
@property (nonatomic, strong) NSArray *array;

// main.m
person.array = @[@"1", @"2", @"3"];
NSArray *tempArray = @[@"0", @"1", @"2"];
[person setValue:tempArray forKey:@"array"];
NSLog(@"%@", [person valueForKeyPath:@"array"]);        

//Printout
(
    0,
    1,
    2
)

Although this approach can achieve results, there is actually a better way:

// main.m
NSMutableArray *mutableArray = [person mutableArrayValueForKey:@"array"];
mutableArray[0] = @"-1";
NSLog(@"%@", [person valueForKeyPath:@"array"]);

//Printout
 (
    "-1",
    1,
    2
)

Here we use a name calledmutableArrayValueForKey:This method will be passed in thekeyReturns the proxy object of a variable array of corresponding properties.

In fact, for collection objects, we can use all kinds of reading and setting methods in the previous section, but for elements inside collection objects, a more efficient way is to useKVCProvidedVariable agent methodKVCThree different methods of variable agent are provided for us:

  • mutableArrayValueForKey:andmutableArrayValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableArray object.
    • The returned proxy object is represented as aNSMutableArrayobject
  • mutableSetValueForKey:andmutableSetValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableSet object.
    • The returned proxy object is represented as aNSMutableSetobject
  • mutableOrderedSetValueForKey: and mutableOrderedSetValueForKeyPath:

    • These return a proxy object that behaves like an NSMutableOrderedSet object.
    • The returned proxy object is represented as aNSMutableOrderedSetobject

2.3 set operators

in usevalueForKeyPath:Set operators can be used to achieve some efficient operations.

A collection operator is one of a small list of keywords preceded by an at sign (@) that specifies an operation that the getter should perform to manipulate the data in some way before returning it.
A set operator is a small number of keywords followed by an at symbol (@), which specifiesgetterThe operation that should be performed to process the data in some way before returning the data.

The structure of set operators is shown in the following figure:

IOS bottom layer exploration - KVC

A simple explanation:

  • Left key path: refers to the set to be calculated. If it is sent directly to the setvalueForKeyPath:News,left key pathIt can be omitted.
  • Right key path: indicates which attribute in the set to operate on, except@countOf all set operatorsright key pathCan’t be omitted

Set operators can be divided into three categories:

  • Aggregation Operators

    • @avg: returns theaverage value
    • @count: return operation object specificationNumber of properties
    • @max: returns theMaximum value
    • @min: returns theminimum value
    • @sum: return operation object specificationSum of attribute values
  • Array operator

    • @distinctUnionOfObjects: return operation objectSet of specified properties — de duplication
    • @unionOfObjects: return operation objectSpecifies the collection of properties
  • Nesting operator

    • @distinctUnionOfArrays: returns the operand (nested Collection)Set of specified properties — de duplication, returnedNSArray
    • @unionOfArrays: return operands (sets)Specifies the collection of properties
    • @distinctUnionOfSets: returns the operand (nested Collection)Set of specified properties — de duplication, returnedNSSet

2.4 access to non object properties

There are two types of non object attributes. One is the basic data typescalarOne is struct.

2.4.1 accessing scalar properties

IOS bottom layer exploration - KVC

As shown in the figure, common basic data types need to be wrapped asNSNumberType, and then use the corresponding reading methods when reading the value, such asdoubleUsed when reading scalar of typedoubleValue

2.4.2 access structure

IOS bottom layer exploration - KVC

Structure needs to be converted intoNSValueType, as shown in the figure above.
exceptNSPoint, NSRange, NSRect, andNSSizeFor a custom structure, you also need toNSValueFor example A kind of :

typedef struct {
    float x, y, z;
} ThreeFloats;
 
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end

//Get structure properties
NSValue* result = [myClass valueForKey:@"threeFloats"];

//Set structure properties
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];

//Extract structure attributes
ThreeFloats th;
[reslut getValue:&th];

2.5 attribute verification

KVCProperty validation is supported, and this feature is supported byvalidateValue:forKey:error:(or validateValue:forKeyPath:error:)Method. The default implementation of this verification method is to receive the object (orkeyPathAccording tokeyFind whether there is a correspondingvalidate<Key>:error:Method implementation, if not, verify the default success, returnYES
And because ofvalidate<Key>:error:Method receives values and error parameters by reference, so there are three results:

  • Validation successful, returnYES, no changes to property values.
  • Validation failed, returnNO, but do not change the property value if the caller providesNSErrorThe error reference is set to the nserror object indicating the cause of the error.
  • Validation failed, returnYES, create a new, valid property value as an alternative. Before returning, the method changes the value reference to point to the new value object. When you make changes, even if the value object is mutable, the method always creates a new object instead of modifying the old one.
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
    NSLog(@"%@",error);
}

Will the system automatically perform attribute verification?
Usually,KVCOr its default implementation does not define any mechanism to automatically perform attribute verification, that is to say, you need to provide your own attribute verification method when it is suitable for your application.
Certain otherCocoaTechnology automatically performs validation in some cases. For example, savemanaged object contextAt that time,Core DataThe validation is performed automatically. In addition,macOSMedium,Cocoa BindingAllows you to specify that validation should occur automatically.

2.6 KVCValue and setting principle

2.6.1 basicgetter

valueForKey:Method is passed in by the callerkeyThe following steps are followed for pattern search in the object:

  • 1. in order toget<Key>, <key>, is<Key>as well as_<key>Find whether there is a corresponding method in the object.

    • If found, skip to step 5 with method return value
    • If not, skip to step 2
  • 2. Check whether there iscountOf<Key>andobjectIn<Key>AtIndex:Method (corresponding toNSArrayThe original method of the class definition) and<key>AtIndexes:Method (corresponding toNSArrayMethodobjectsAtIndexes:)

    • If you find the first one(countOf<Key>), find at least one of the other two, create a response allNSArrayMethod and returns the object. (translation is eithercountOf<Key> + objectIn<Key>AtIndex:Or eithercountOf<Key> + <key>AtIndexes:Or eithercountOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)
    • If not, skip to step 3
  • 3. Find the namecountOf<Key>enumeratorOf<Key>andmemberOf<Key>These three methods (corresponding to the original method defined by the nsset class)

    • If these three methods are found, create a response to allNSSetMethod and returns the
    • If not, skip to step 4
  • 4. Judgment methodsaccessInstanceVariablesDirectlyResult

    • If returnsYESThen_<key>, _is<Key>, <key>, is<Key>Find the member variables in the order of. If found, skip to step 5 with the member variables. If not, skip to step 6
    • If returnsNO, go to step 6
  • 5. Judge the extracted attribute value

    • If the property value is an object, return directly
    • If the property value is not an object, but can be converted toNSNumberType, the attribute value is converted toNSNumberType return
    • If the property value is not an object, it cannot be converted toNSNumberType, the attribute value is converted toNSValueType return
  • 6. callvalueForUndefinedKey:。 By default, this raises an exception, butNSObjectSubclasses of can provide specifickeyAct.

This can be represented by a simple flow chart

IOS bottom layer exploration - KVC

2.6.2 basicsetter

setValue:forKey:Method default implementation is passed in by the callerkeyandvalue(if it is a non object type, it refers to the value after unpacking) after that, the following steps will be followed for pattern search in the object:

  • 1. in order toset<Key>:, _set<Key>If the method is found, the property value is passed to the method to complete the setting of the property value.
  • 2. Judgment methodsaccessInstanceVariablesDirectlyResult

    • If returnsYESThen_<key>, _is<Key>, <key>, is<Key>If found, the property value is passed to the method to complete the setting of the property value.
    • If returnsNO, go to step 3
  • 3. callsetValue:forUndefinedKey:。 By default, this raises an exception, butNSObjectSubclasses of can provide specifickeyAct.

IOS bottom layer exploration - KVC

3、 CustomKVC

Got it.KVCAfter the underlying principle, can we implement it by ourselvesKVCWhat about it? Let’s make it cleariOSClassification of attributes in:

  • Attributes: simple properties, such as basic data types, strings, and Boolean values, such asNSNumberAnd other immutable types likeNSColorIt can also be considered as a simple attribute
  • To-one relationships: These are mutable object properties with their own properties. That is, the properties of an object can be changed without changing the object itself. For example, aAccountObject may have aownerProperty, which isPersonObject, andPersonThe object itself hasaddressAttribute.ownerThe address of can be changed without changingAccountHeldownerAttribute. In other wordsAccountOfownerProperty not changed, justaddressHas been changed.
  • To-many relationships: These are collection object properties. Although you can also use custom collection classes, you usually useNSArrayorNSSetTo hold this collection.

We demonstrate the above three types of properties through code:

// Person.h
@interface Person
@property (nonatomic, copy) NSString *name; // Attributes 
@property (nonatomic, strong) Account *account; // To-one relationships
@property (nonatomic, strong) NSArray *subjects; // To-many relationships
@end

// Account.h
@interface Account
@property (nonatomic, assign) NSInteger balance; 
@end

Our implementation focuses on the most commonly usedvalueForKey:Method, which we found to be located atNSKeyValueCodingIn this classification, this design pattern can realize the decoupling function.

IOS bottom layer exploration - KVC

For example, in actual development, we willAppDelegateFor a long time, the registration and initialization of various third-party components in the source file have been done. As the project functions continue to iterate, they accumulate inAppDelegateThere will be more and more code in, making it difficult to maintain. At this time, if the initialization and registration logic are differentAppDelegateCan be greatly reducedAppDelegateThe cost of self maintenance also makes the whole business flow clearer.

3.1 custom settings

So, if we want to customizeKVCIf it is implemented, it should also operate according to this design pattern. Let’s build a new oneNSObjectThen we focus onsetValue:ForKey:Method, in order to avoidKVCMethod conflict, we add a prefix

// NSObject+JHKVC.h
@interface NSObject (JHKVC)
- (void)jh_setValue:(nullable id)value forKey:(NSString *)key;
@end

Then we need to implement this method according to what we explored earliersetValue:ForKey:Process, let’s judge the incomingkeyEmpty or not:

//1. Judge key
    if (key == nil  || key.length == 0) return;
  • IfkeybynilperhapskeyLength is 0, exit directly.

And then we have to decide if it existssetKey_setKeyThere’s an episode here, because Apple’s official documents only say these two methods, but in fact,iOSThe bottom floor also handlessetIsKey, becausekeyCan be rewrittenisKeySo here we add the rightsetIsKeyJudgement.

//2. Judge whether setkey, _setkeyand setiskey exist. If they exist, directly call the corresponding method to set the property value
    NSString *Key = key.capitalizedString;
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
    
    if ([self jh_performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self jh_performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self jh_performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }
  • For convenience, firstkeyCapitalize the first letter and then splice three different onessetMethod name, and then determine whether the response method can be implemented. If it is implemented, call the response method directly to set the property value

Here we go firstrespondsToSelectorTo determine whether the current object can respond to the incoming method, and if it can, execute the method

- (BOOL)jh_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

Here, if you follow the system’sKVCThe process of setting value should be correctNSArrayNSSetFor the sake of simplification, these processes are ignored temporarily. Let’s go straight down. The next process should be to judge class methodsaccessInstanceVariablesDirectlyThe following:

//3. Judge whether member variables can be read directly
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"JHUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }

If member variables can be read, then we need to follow the_key_isKey, key, isKeyTo find out:

//4. Query the instance variables in the order of_key, is_key, key and iskey
    NSMutableArray *mArray = [self getIvarListName];
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        //4.2 obtain the corresponding Ivar
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        //4.3 set values for corresponding Ivar
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:_isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:key]) {
       Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }else if ([mArray containsObject:isKey]) {
       Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
       object_setIvar(self , ivar, value);
       return;
    }
  • First, read all instance variables on the current object, and then match the four cases
- (NSMutableArray *)getIvarListName{
    //Initialize array container
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    //Get the member variable of the current class
    Ivar *ivars = class_copyIvarList([self class], &count);
    //Traverse all member variables
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        //Convert static string pointer to nsstring type  
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }
    //Release member variable pointer array
    free(ivars);
    return mArray;
}

It’s used hereRuntimeThe two one.apiclass_copyIvarListandivar_getName

Ivar  _Nonnull * class_copyIvarList(Class cls, unsigned int *outCount);

Returns an array of pointers to member variables in a class structure, but does not include member variables declared in the parent class. The array contains*outCountPointer, followed by aNULLTerminator. After use, you must usefree()Array of pointers to free member variables. If the class does not declare any instance variables, orclsNil, returnNULLAnd*outCountIt is 0.

const char * ivar_getName(Ivar v);

Returns the name of a member variable

//5. If the previous processes fail, an exception will be thrown
    @throw [NSException exceptionWithName:@"JHUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: setValue:forUndefinedKey:%@.****",self,NSStringFromSelector(_cmd),key] userInfo:nil];
  • Finally throw outsetValue:forUndefinedKeyAnomaly

So far, oursetValue:forKey:The process is over. Of course, the whole content and system are realKVCIt’s far worse than that, including thread safety, variable array and so on, but this is not the point, we just need to draw inferences.

3.2 user defined value

Then what we need to customize isvalueForKey:, we declare the following method:

- (nullable id)jh_valueForKey:(NSString *)key;

And then again, according to what we explored earliervalueForKey:The bottom process, or to judge firstkey:

//1. Judge key
    if (key == nil  || key.length == 0) {
        return nil;
    }
  • IfkeybynilperhapskeyLength is 0, exit directly.

And then it’s about judging whether there’s a correspondinggetterMethod, the search order is as followsgetKey, key, isKey, _key:

//2. Judge whether getKey, key, iskey and Φ key exist. If so, directly call the corresponding method to return the property value
    NSString *Key = key.capitalizedString;
    NSString *getKey = [NSString stringWithFormat:@"get%@:",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@:",Key];
    NSString *_key = [NSString stringWithFormat:@"_%@:",Key];
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    } else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    } else if ([self respondsToSelector:NSSelectorFromString(isKey)]){
        return [self performSelector:NSSelectorFromString(isKey)];
    } else if ([self respondsToSelector:NSSelectorFromString(_key)]){
        return [self performSelector:NSSelectorFromString(_key)];
    }
#pragma clang diagnostic pop

If these fourgetterIf none of the methods are found, you need to read the class method:

//3. Judge whether member variables can be read directly
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"JHUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }

If member variables can be read, then we need to follow the_key_isKey, key, isKeyTo find out:

//4. Query the instance variables in the order of \
    NSMutableArray *mArray = [self getIvarListName];
    _key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }
//5. Throw an exception
    @throw [NSException exceptionWithName:@"JHUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: valueForUndefinedKey:%@.****",self,NSStringFromSelector(_cmd),key] userInfo:nil];
  • Finally throw outvalueForUndefinedKey:Anomaly

The customization of the value taking process is over. In fact, there are some loose points. For example, when getting the attribute value return, you need to judge whether to convert it toNSNumberorNSValueAnd yes.NSArrayandNSSetJudgment of type.

Four, summary

KVCAfter exploring, in fact, most of the content we explore is based on Apple’s official documents. We are exploringiOSAt the bottom, document thinking is very important. Sometimes, there may be an answer hidden in a corner of the document.KVCIt’s not hard to use and understand, but that doesn’t mean we can despise it. stayiOS 13Before, we couldKVCTo get and set the private properties of the system, but from theiOS 13After that, it was disabled. Suggestions onKVCReaders who don’t understand it thoroughly go to the official documents several times. Believe me, you will get new results. Finally, we will briefly summarize the content of this article.

  • KVCIt is a kind ofNSKeyValueCodingThe mechanism provided by implicit protocol.
  • KVCadoptvalueForKey:andvalueForKeyPath:The specific value taking process is as follows, regardless of the set type:

    • withget<Key>, <key>, is<Key>, _<key>Order search method of
    • If the method is not found, it is passed through the class methodaccessInstanceVariablesDirectlyDetermine whether member variables can be read to return property values
    • with_<key>, _is<Key>, <key>, is<Key>Find member variables in order of
  • KVCadoptsetValueForKey:andsetValueForKeyPath:The specific setting process is as follows:

    • withset<Key>, _set<Key>Order search method of
    • If the method is not found, it is passed through the class methodaccessInstanceVariablesDirectlyDetermine whether the set value can be returned through the member variable
    • with_<key>, _is<Key>, <key>, is<Key>Find member variables in order of

Reference material

Apple developer documentation – KVC

The difference between IOS atomic and nonatomic

Objective-C memory management

Summary of IOS underlying principles — in-depth understanding of kvckvo implementation mechanism

Recommended Today

Incomplete delivery order log of SAP SD basic knowledge

Incomplete delivery order log of SAP SD basic knowledge   If we call the incomplete project log, the system checks whether the data in the outbound delivery is complete. From the generated list, we can directly jump to the screen of maintaining incomplete fields.   We can call log of incomplete items from delivery processing, […]