Latest 1.0.0b1
Homepage https://github.com/jpmhouston/Panopticon
License MIT
Platforms ios 5.0, osx 10.7
Authors

Version
Carthage compatible
License
Platform

A simplified Objective-C API for using NSNotifications and KVO with consistent terminology and useful convenience features.

Uses blocks exclusively, but unlike NSNotification’s blocks API, allows removal using matching parameters instead of requiring storage of an observation object. Supports automatic removal when either observer or observee is deallocated. Can optionally omit the “pan_” prefix on all the category methods.

Extensible to other styles of observers, included are a wrapper for UIControl event actions, and another for darwin notifications across app groups.

Panopticon’s feature set is heavily influenced by MAKVONotificationCenter, and also adapts its rock-solid solution for automatic removal. Inspiration for app groups notifications comes from MMWormhole.

Written in Objective-C but tested to verify it’s usable from Swift.

Most every external classes, properties, methods are documented, but could very well do with some proofreading and improvements. Attempts to build documentation using jazzy are currently blocked by its issue #158, sadly.

Pull requests welcome.

Usage

Import using either #import <Panopticon/Panopticon.h> or @import Panopticon;.

There are 2 general styles of observing methods, one where you pass in an observer (often self), and another where you omit the observer for brevity when its unnecessary. In both cases, the final block parameter is called when the observation is triggered.

You can do nothing and observation is removed automatically when either your observer or the observed object is deallocated. No need to add remove code to dealloc, no need to keep the PANObservation result from the observe method.

If you want to explicitly remove an observation, you can keep the observe method’s result after all and call remove on it. But you can also use a stopObserving class method, which (like -[NSNotificationCenter removeObserver:name:object:]) you call with a repeat of that the same parameters as you the passed to observe, and the correct observation will be found and removed.

PANObservation *o1 = [self pan_observeForChanges:object toKeyPath:@"name" withBlock:^(id obj, PANKeyValueObservation *obs) {
    NSLog(@"observed change to object's name parameter!");
}];

PANObservation *o2 = [object pan_observeChangesToKeyPath:@"flag" withBlock:^(PANKeyValueObservation *obs) {
    NSLog(@"observed change to object's flag parameter!");
}];

PANObservation *o3 = [self pan_observeForNotifications:object named:@"Banana" withBlock:^(id obj, PANNotificationObservation *obs) {
    NSLog(@"observed object posting Banana notification");
}];

PANObservation *o4 = [object pan_observeNotificationsNamed:@"Seaweed" withBlock:^(PANNotificationObservation *obs) {
    NSLog(@"observed object posting Seaweed notification");
}];

...

[self pan_stopObservingForChanges:object toKeyPath:@"name"];
[o4 remove];

When you’ve provided an observer object, that object is passed back as the first parameter to your block. You can use this to avoid having to make your own weak self pointer. You specialize the type of that parameter in your block definition to the expected type and avoid unnecessary casting.

[self pan_observeForChanges:object toKeyPath:@"name" withBlock:^(ViewController *obj, PANKeyValueObservation *obs) {
    [obj handleNameChange];
}];

The second parameter the block is an observation object, the same as the observe method’s result. Not ony can you call remove on this object, but it has accessors for all observation details, such as the notification object or the change dictionary:

[self pan_observeForChanges:object toKeyPath:@"name" withBlock:^(id obj, PANKeyValueObservation *obs) {
    NSLog(@"%@ %@ %d %@ %@", obs.changeDict, obs.keyPath, (int)obs.kind, obs.oldValue, obs.changedValue);
}];

[self pan_observeForNotifications:object named:@"Cheesecake" withBlock:^(id obj, PANNotificationObservation *obs) {
    NSLog(@"%@ %@ %@", obs.notification, obs.postedObject, obs.userInfo);
}];

There are additional methods which:

  • accept KVO option parameters
  • observe multiple KVO keypaths at once
  • observe a NSNotification posted by any object
  • observe an object’s own KVO changes or notifications
  • call the observation block on a specific NSOperationQueue or GCD queue

There’s a mechanism for pausing an observation such that when unpaused, the data and a timestamp from every missed call is available.

Also

Panopticon can be easily extended to other flavor of observations. For example, I’ve added these capabilities:

UIControl Events

[self pan_observeControlForPress:self.button withBlock:^(id obj, PANUIControlObservation *obs) {
}];

[self.field pan_observeEvents:UIControlEventEditingDidBegin withBlock:^(PANUIControlObservation *obs) {
}];

Cross-App Group Darwin Notifications

[PANAppGroupObservation registerAppGroup:groupId];

[self pan_observeAppGroupNotificationsNamed:@"name" withBlock:^(id obj, PANAppGroupObservation *obs) {
  // called with latest post
  NSLog(@"%@", obs.payload);
}];

[self pan_observeReliablyAppGroupNotificationsNamed:@"updates" withBlock:^(id obj, NSArray *observations) {
  // called with all posts made between last observation
  // even if app was inactive or quit in the meantime
}];

[self.data pan_postWithinAppGroupNotificationNamed:@"x"];

Installation

Panopticon is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod "Panopticon"

Alternately, you can enable shorthand methods that leave the pan_ prefix off the catgory method names (inspired by MagicalRecord) by instead adding to your Podfile:

pod "Panopticon/Shorthand"

and also, somewhere in your app’s launching process, such as a +load method or your app delegate’s application:didFinishLaunching... method, add this class method call:

[PANObservation setupShorthandMethods];

Read the comments in "PanopticonShorthand.h" for more details.

Running Example

If using pod try or manually cloning the source to try out the Example app, to build the CocoaPods target you must first cd into the Example directory and execute pod install, then as directed open Panopticon.xcworkspace and not the .xcodeproj file. Note that the example project also has a second target that builds using static frameworks.

Author

Pierre Houston, [email protected]

License

Panopticon is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "Panopticon",
    "version": "1.0.0b1",
    "summary": "Consistent blocks API for disparate Cocoa observers: KVO, NSNotification, cross-app Darwin Notifications, and more.",
    "description": "A simplified Objective-C API for using NSNotifications and KVO with consistent terminology and useful convenience features.nUses blocks exclusively, but unlike NSNotification's blocks API, allows manual removal without requiring storage of an observation object.nSupports automatic removal when either observer or observee is deallocated, and an easy shorthand header which lets you omit method prefixes.nExtensible to other styles of observers, included are a wrapper for UIControl event actions, and another for darwin notifications across app groups.",
    "homepage": "https://github.com/jpmhouston/Panopticon",
    "license": "MIT",
    "authors": {
        "Pierre Houston": "[email protected]"
    },
    "source": {
        "git": "https://github.com/jpmhouston/Panopticon.git",
        "tag": "1.0.0b1"
    },
    "platforms": {
        "ios": "5.0",
        "osx": "10.7"
    },
    "ios": {
        "frameworks": [
            "Foundation",
            "UIKit"
        ]
    },
    "osx": {
        "frameworks": "Foundation"
    },
    "default_subspecs": "Core",
    "subspecs": [
        {
            "name": "Core",
            "source_files": "Source/**/*.{h,m}",
            "public_header_files": "Source/**/*.h",
            "private_header_files": [
                "Source/**/*+Private.h",
                "Source/AppGroups/PANAppGroupNotificationManager.h"
            ],
            "ios": {
                "exclude_files": [
                    "Source/ShorthandAutosetup.h",
                    "Source/**/*Shorthand.{h,m}"
                ]
            },
            "osx": {
                "exclude_files": [
                    "Source/ShorthandAutosetup.h",
                    "Source/**/*Shorthand.{h,m}",
                    "Source/UIControl/*"
                ]
            }
        },
        {
            "name": "Shorthand",
            "dependencies": {
                "Panopticon/Core": []
            },
            "source_files": [
                "Source/ShorthandAutosetup.h",
                "Source/**/*Shorthand.h"
            ],
            "public_header_files": "Source/**/*Shorthand.h",
            "private_header_files": "Source/ShorthandAutosetup.h",
            "osx": {
                "exclude_files": "Source/UIControl/*Shorthand.h"
            }
        }
    ]
}

Pin It on Pinterest

Share This