Cocoa
02 / 02

KVC/KVO, Notifications & Persistence Basics

Cocoa: KVC/KVO, Notifications & Persistence Basics

Key-Value Coding (KVC)

// Access properties indirectly by string name, instead of dot/message access
Person *person = [[Person alloc] init];
[person setValue:@"Alice" forKey:@"name"];
NSString *name = [person valueForKey:@"name"];

// Key paths — traverse nested objects
NSString *friendName = [person valueForKeyPath:@"bestFriend.name"];

// Useful when the property to access is only known at runtime —
// e.g. driven by a config file, form field name, or JSON key
for (NSString *key in dynamicFieldNames) {
    id value = [formModel valueForKey:key];
}

// KVC also powers NSArray/NSDictionary aggregate operators
NSArray<NSNumber *> *ages = [people valueForKeyPath:@"@avg.age"];

Key-Value Observing (KVO)

// KVO notifies observers automatically when a KVC-compliant property
// changes through its standard setter.
[person addObserver:self
         forKeyPath:@"age"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object
                         change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                        context:(void *)context {
    NSNumber *newAge = change[NSKeyValueChangeNewKey];
    NSLog(@"age changed to %@", newAge);
}

// Always remove observers — a common crash source is a dangling
// observer outliving the observed object
- (void)dealloc {
    [person removeObserver:self forKeyPath:@"age"];
}

// Swift equivalent, via Combine's KVO publisher
let cancellable = person.publisher(for: \.age)
    .sink { newAge in print("age is now \(newAge)") }

NotificationCenter

// Decoupled publish-subscribe — poster and observer don't know about each other
NotificationCenter.default.addObserver(
    self,
    selector: #selector(keyboardWillShow),
    name: UIResponder.keyboardWillShowNotification,
    object: nil
)

@objc func keyboardWillShow(_ notification: Notification) {
    guard let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
    // adjust layout for keyboard height
}

// Posting a custom notification
extension Notification.Name {
    static let userDidLogin = Notification.Name("userDidLogin")
}

NotificationCenter.default.post(name: .userDidLogin, object: nil, userInfo: ["userID": userID])

// Always remove observers when done (less critical in Swift with block-based
// observers, which auto-remove on deinit if stored as a token)
deinit {
    NotificationCenter.default.removeObserver(self)
}

UserDefaults, NSCache & NSPredicate

// UserDefaults — small, persistent key-value settings (backed by a plist)
UserDefaults.standard.set(true, forKey: "hasSeenOnboarding")
let seen = UserDefaults.standard.bool(forKey: "hasSeenOnboarding")
// Not for large data or secrets — use the Keychain for sensitive values

// NSCache — auto-evicts under memory pressure, thread-safe (unlike a raw dictionary)
let imageCache = NSCache<NSString, UIImage>()
imageCache.countLimit = 100
imageCache.setObject(image, forKey: url.absoluteString as NSString)
let cached = imageCache.object(forKey: url.absoluteString as NSString)

// NSPredicate — reusable filter condition, for in-memory arrays or Core Data
let predicate = NSPredicate(format: "age > %d AND name CONTAINS[cd] %@", 18, "a")
let adults = people.filter { predicate.evaluate(with: $0) }
// Same predicate object also works directly as fetchRequest.predicate in Core Data

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free