Sök…


Det vanligaste exemplet med kodning av verkligt liv

Key Value Coding är integrerad i NSObject med NSKeyValueCoding- protokollet.

Vad betyder detta?

Det betyder att varje ID-objekt kan kalla metoden ValueForKey och dess olika varianter som valueForKeyPath etc..

Det betyder också att alla ID-objekt kan åberopa setValue-metoden och dess olika varianter också.

Exempel:

id obj = [[MyClass alloc] init];
id value = [obj valueForKey:@"myNumber"];

int myNumberAsInt = [value intValue];
myNumberAsInt = 53;
[obj setValue:@(myNumberAsInt) forKey:@"myNumber"];

undantag:

Ovanstående exempel antar att MyClass har en NSNumber-egenskap som heter myNumber. Om myNumber inte visas i MyClass-gränssnittsdefinitionen, kan en NSUndefinedKeyException höjas på kanske båda linjerna 2 och 5 - populärt känd som:

this class is not key value coding-compliant for the key myNumber.

Varför detta är så kraftfullt:

Du kan skriva kod som kan få tillgång till egenskaper i en klass dynamiskt utan att behöva gränssnitt för den klassen. Detta innebär att en tabellvy kan visa värden från alla egenskaper hos ett NSObject-härledt objekt, förutsatt att dess egendomnamn tillhandahålls dynamiskt vid körning.

I exemplet ovan kan koden också fungera utan att MyClass är tillgängligt och id-typ obj är tillgängligt för samtalskod.

Observera viktigt värde

Ställa in observering av nyckelvärde.

I det här fallet vill vi observera contentOffset på ett objekt som vår observatör äger

//
// Class to observe
//
@interface XYZScrollView: NSObject
@property (nonatomic, assign) CGPoint contentOffset;
@end

@implementation XYZScrollView
@end


//
// Class that will observe changes
//
@interface XYZObserver: NSObject
@property (nonatomic, strong) XYZScrollView *scrollView;
@end

@implementation XYZObserver

// simple way to create a KVO context
static void *XYZObserverContext = &XYZObserverContext;


// Helper method to add self as an observer to 
// the scrollView's contentOffset property
- (void)addObserver {

    // NSKeyValueObservingOptions
    //
    // - NSKeyValueObservingOptionNew
    // - NSKeyValueObservingOptionOld
    // - NSKeyValueObservingOptionInitial
    // - NSKeyValueObservingOptionPrior
    //
    // can be combined:
    // (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
    
    NSString *keyPath = NSStringFromSelector(@selector(contentOffset));
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew;    

    [self.scrollView addObserver: self 
                      forKeyPath: keyPath 
                         options: options
                         context: XYZObserverContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
    if (context == XYZObserverContext) { // check the context

        // check the keyPath to see if it's any of the desired keyPath's.
        // You can observe multiple keyPath's
        if ([keyPath isEqualToString: NSStringFromSelector(@selector(contentOffset))]) {

            // change dictionary keys:
            // - NSKeyValueChangeKindKey
            // - NSKeyValueChangeNewKey
            // - NSKeyValueChangeOldKey
            // - NSKeyValueChangeIndexesKey
            // - NSKeyValueChangeNotificationIsPriorKey
            
            // the change dictionary here for a CGPoint observation will
            // return an NSPoint, so we can take the CGPointValue of it.
            CGPoint point = [change[NSKeyValueChangeNewKey] CGPointValue];
            
            // handle point
        }
        
    } else {

        // if the context doesn't match our current object's context
        // we want to pass the observation parameters to super
        [super observeValueForKeyPath: keyPath
                             ofObject: object
                               change: change
                              context: context];
    }
}

// The program can crash if an object is not removed as observer 
// before it is dealloc'd
//
// Helper method to remove self as an observer of the scrollView's
// contentOffset property
- (void)removeObserver {
    NSString *keyPath = NSStringFromSelector(@selector(contentOffset));
    [self.scrollView removeObserver: self forKeyPath: keyPath];
}

@end

Fråga efter KVC-data

if ([[dataObject objectForKey:@"yourVariable"] isEqualToString:"Hello World"]) {
    return YES;
} else {
    return NO;
}

Du kan fråga värden lagrade med KVC snabbt och enkelt utan att behöva hämta eller casta dessa som lokala variabler.

Insamlingsoperatörer

Insamlingsoperatörer kan användas i en KVC-nyckelväg för att utföra en operation på en "samlingstyp" -egenskap (dvs. NSArray , NSSet och liknande). Till exempel är en vanlig åtgärd att utföra att räkna föremål i en samling. För att uppnå detta använder @count operatören för @count collection :

self.array = @[@5, @4, @3, @2, @1];
NSNumber *count = [self.array valueForKeyPath:@"@count"];
NSNumber *countAlt = [self valueForKeyPath:@"array.@count"];
// count == countAlt == 5

Även om detta är helt överflödigt här (vi kunde just ha åtkomst till count ), kan det vara användbart ibland, men det är sällan nödvändigt. Det finns dock vissa samlingsoperatörer som är mycket mer användbara, nämligen @max , @min , @sum , @avg och @unionOf familjen. Det är viktigt att notera att dessa operatörer också kräver en separat nyckelväg som följer operatören för att fungera korrekt. Här är en lista över dem och vilken typ av data de arbetar med:

Operatör Data typ
@count (ingen)
@max NSNumber , NSDate , int (och relaterat), etc.
@min NSNumber , NSDate , int (och relaterat), etc.
@sum NSNumber , int (och relaterat), double (och relaterat), etc.
@avg NSNumber , int (och relaterat), double (och relaterat), etc.
@unionOfObjects NSArray , NSSet , etc.
@distinctUnionOfObjects NSArray , NSSet , etc.
@unionOfArrays NSArray<NSArray*>
@distinctUnionOfArrays NSArray<NSArray*>
@distinctUnionOfSets NSSet<NSSet*>

@max och @min returnerar det högsta respektive lägsta värdet för en egenskap av objekt i samlingen. Titta till exempel på följande kod:

// “Point” class used in our collection
@interface Point : NSObject

@property NSInteger x, y;

+ (instancetype)pointWithX:(NSInteger)x y:(NSInteger)y;

@end

...

self.points = @[[Point pointWithX:0 y:0],
                [Point pointWithX:1 y:-1],
                [Point pointWithX:5 y:-6],
                [Point pointWithX:3 y:0],
                [Point pointWithX:8 y:-4],
];

NSNumber *maxX = [self valueForKeyPath:@"[email protected]"];
NSNumber *minX = [self valueForKeyPath:@"[email protected]"];
NSNumber *maxY = [self valueForKeyPath:@"[email protected]"];
NSNumber *minY = [self valueForKeyPath:@"[email protected]"];

NSArray<NSNumber*> *boundsOfAllPoints = @[maxX, minX, maxY, minY];

...

På bara fyra kodrader och ren Foundation, med kraften från Key-Value Coding-insamlingsoperatörer, kunde vi extrahera en rektangel som omsluter alla punkter i vår grupp.

Det är viktigt att notera att dessa jämförelser görs genom att åberopa metoden compare: på objekten, så om du någonsin vill göra din egen klass kompatibel med dessa operatörer måste du implementera den här metoden.

@sum kommer, som du antagligen kan gissa, lägga till alla värden på en egenskap.

@interface Expense : NSObject

@property NSNumber *price;

+ (instancetype)expenseWithPrice:(NSNumber *)price;

@end

...

self.expenses = @[[Expense expenseWithPrice:@1.50],
                  [Expense expenseWithPrice:@9.99],
                  [Expense expenseWithPrice:@2.78],
                  [Expense expenseWithPrice:@9.99],
                  [Expense expenseWithPrice:@24.95]
];

NSNumber *totalExpenses = [self valueForKeyPath:@"[email protected]"];

Här använde vi @sum att hitta det totala priset för alla utgifter i matrisen. Om vi istället ville hitta det genomsnittliga priset vi betalar för varje kostnad, kan vi använda @avg :

NSNumber *averagePrice = [self valueForKeyPath:@"[email protected]"];

Äntligen finns det @unionOf familjen. Det finns fem olika operatörer i den här familjen, men alla fungerar mestadels på samma sätt, med bara små skillnader mellan var och en. Först finns det @unionOfObjects som kommer att returnera en matris med egenskaper för objekt i en matris:

// See "expenses" array above

NSArray<NSNumber*> *allPrices = [self valueForKeyPath:
    @"[email protected]"];

// Equal to @[ @1.50, @9.99, @2.78, @9.99, @24.95 ]

@distinctUnionOfObjects fungerar på samma sätt som @unionOfObjects , men det tar bort dubbletter:

NSArray<NSNumber*> *differentPrices = [self valueForKeyPath:
    @"[email protected]"];

// Equal to @[ @1.50, @9.99, @2.78, @24.95 ]

Och slutligen kommer de sista 3 operatörerna i @unionOf familjen att gå ett steg djupare och returnera en mängd värden som hittas för en egenskap som finns inne i dually-kapslade matriser:

NSArray<NSArray<Expense*,Expense*>*> *arrayOfArrays =
    @[
        @[ [Expense expenseWithPrice:@19.99],
           [Expense expenseWithPrice:@14.95],
           [Expense expenseWithPrice:@4.50],
           [Expense expenseWithPrice:@19.99]
         ],

        @[ [Expense expenseWithPrice:@3.75],
           [Expense expenseWithPrice:@14.95]
         ]
     ];

// @unionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
    @"@unionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @19.99, @3.75, @14.95 ];

// @distinctUnionOfArrays
NSArray<NSNumber*> allPrices = [arrayOfArrays valueForKeyPath:
    @"@distinctUnionOfArrays.price"];
// Equal to @[ @19.99, @14.95, @4.50, @3.75 ];

Den saknas detta exempel är @distinctUnionOfSets , men detta fungerar exakt på samma sätt som @distinctUnionOfArrays , men fungerar med och returnerar NSSet s istället (det finns ingen icke- distinct version grund i en uppsättning, måste varje objekt vara tydlig i alla fall).

Och det är allt! Insamlingsoperatörer kan vara riktigt kraftfulla om de används på rätt sätt och kan hjälpa till att undvika att behöva slinga igenom saker i onödan.

En sista anmärkning: du kan också använda standarduppsamlingsoperatörerna på matriser av NSNumber s (utan ytterligare tillgång till egendom). För att göra detta får du åtkomst till den self pseudo-egenskapen som just returnerar objektet:

NSArray<NSNumber*> *numbers = @[@0, @1, @5, @27, @1337, @2048];

NSNumber *largest = [numbers valueForKeyPath:@"@max.self"];
NSNumber *smallest = [numbers valueForKeyPath:@"@min.self"];
NSNumber *total = [numbers valueForKeyPath:@"@sum.self"];
NSNumber *average = [numbers valueForKeyPath:@"@avg.self"];


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow