User:Uroboro: Difference between revisions

641 editsJoined 4 March 2013
(→‎Reduced style: Forgot key)
m (Mail filter.)
 
(47 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Find me on saurik's [[IRC]] server, [https://twitter.com/uroboro845 Twitter], [http://www.reddit.com/user/uroboro Reddit], [https://github.com/uroboro Github], [http://stackoverflow.com/users/1429562/uroboro StackOverflow].
Find me on saurik's [[IRC]] server, [https://twitter.com/uroboro845 Twitter], [http://www.reddit.com/user/uroboro Reddit], [https://github.com/uroboro Github], [http://stackoverflow.com/users/1429562/uroboro StackOverflow] or send me an [mailto:[email protected] email].


Made [https://github.com/uroboro/UnlockEvents UnlockEvents] and [https://github.com/uroboro/FlipNC FlipNC].
Made [https://github.com/uroboro/UnlockEvents UnlockEvents] and [https://github.com/uroboro/FlipNC FlipNC].
Line 7: Line 7:
Some [[NIC]] templates [https://github.com/uroboro/nicTemplates here].
Some [[NIC]] templates [https://github.com/uroboro/nicTemplates here].


== How 2 prefs ==
And a repository there: [https://cydia.saurik.com/api/share#?source=https://uroboro.github.io/repo/ uroboro.github.io/repo].


As opposed to using NSDictionaries and <code>[NSHomeDirectory() stringByAppendingFormat:@"/Library/Preferences/%s.plist", "com.your.tweak"]</code>, here's another alternative to handling preferences on tweaks.
'''DISCLAIMER: All code written in this page is to be considered experimental and untested. The author shall not be held responsible or liable for any undesired consequences, including, but not limited to, data loss, property damage, time travel, the singularity, suffered by you as a result of any error in such code. You also agree that you will not use these code snippets for any purposes prohibited by United States law, including, without limitation, the development, design, manufacture, or production of nuclear, missile, or chemical or biological weapons.'''


=== Tweak.xm ===
== "Interesting" links ==


<source lang="objc">
* [https://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html Object ownership].
@interface NSUserDefaults (Tweak_Category)
* [https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html Memory management].
- (id)objectForKey:(NSString *)key inDomain:(NSString *)domain;
* [https://www.reddit.com/r/jailbreak/comments/3bcwdk/tutorial_lets_secure_ssh_a_bit_more/ Securing SSH] - [https://bitbucket.org/lordscotland/sshconnect/overview SSHConnect].
- (void)setObject:(id)value forKey:(NSString *)key inDomain:(NSString *)domain;
@end
 
static NSString *nsDomainString = @"com.your.tweak";
static NSString *nsNotificationString = @"com.your.tweak/preferences.changed";
static BOOL enabled;
 
static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSNumber *n = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"enabled" inDomain:nsDomainString];
enabled = (n)? [n boolValue]:YES;
}
 
%ctor {
// Set variables on start up
notificationCallback(NULL, NULL, NULL, NULL, NULL);


// Register for 'PostNotification' notifications
== Hooking everything ==
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, notificationCallback, (CFStringRef)nsNotificationString, NULL, CFNotificationSuspensionBehaviorCoalesce);


// Add any personal initializations
While previously it was possible to provide no filter or an empty one, this is no longer the case, both by [[Cydia Substrate]] requiring one and not it being empty. So the solution is based on another requirement that is not very documented: Cydia Substrate will only hook binaries that are linked with [[Security.framework]]. The following filter will suffice for this purpose but please reduce the hooking target of your tweak as much as possible.
}


/*
{|
* From here onward, write your tweak.
|+ com.your.tweak.plist
* To make your tweak actually do stuff when enabled:
|- valign="top"
if (!enabled) {
|
    // Do the original algorithm, either by calling:
{{Collapse|1=<source lang="javascript">{
    // %orig(); //if using logos for methods
Filter = {
    // _functionName(args); //if using MSHook() for functions
Bundles = (
} else {
"com.apple.Security"
    ...
)
    // Optionally, do the original algorithm
}
}
}
*/
</source>|2=NeXTSTEP style}}
||
{{Collapse|1=<source lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Filter</key>
<dict>
<key>Bundles</key>
<array>
<string>com.apple.Security</string>
</array>
</dict>
</dict>
</plist>
</source>|2=XML style}}
|}


</source>
== Late hooking ==


=== Preference plist ===
=== UI usage ===


Choose any of the following:
If you need to use UI elements, you have to wait until the application is ready.


==== Reduced style ====
==== Using callbacks ====


Provides a switch on the root section of the preferences (like Airplane Mode). Recommended for configuration-less tweaks.
<source lang="objc">
static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
// UI is available, use UIKit here
#if BEFORE_IOS_8
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert"
message:@"This is an alert."
delegate:nil
cancelButtonTitle:@"I'm ok with this"
otherButtonTitles:nil];
[alert show];
#else
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Alert"
message:@"This is an alert."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"I'm ok with this"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}]];
[[UIWindow keyWindow].rootViewController presentViewController:alert animated:YES completion:^{}];
#endif
}
static void * observer = NULL;


Saved in your tweak's folder as <code>layout/Library/PreferenceLoader/Preferences/com.your.tweak.plist</code>
%ctor {
CFNotificationCenterAddObserver(
CFNotificationCenterGetLocalCenter(),
&observer,
notificationCallback,
(CFStringRef)UIApplicationDidFinishLaunchingNotification,
NULL,
CFNotificationSuspensionBehaviorCoalesce
);
}


<pre>{
// Remove observer upon unloading the dylib
    entry = {
%dtor {
        cell = PSSwitchCell;
CFNotificationCenterRemoveObserver(
        defaults = "com.your.tweak";
CFNotificationCenterGetLocalCenter(),
        label = "Your Tweak";
&observer,
        key = enabled;
(CFStringRef)UIApplicationDidFinishLaunchingNotification,
        default = 1;
NULL
        icon = "/Applications/Preferences.app/[email protected]";
);
        PostNotification = "com.your.tweak/preferences.changed";
    };
}
}
</pre>


==== Extended Style ====


Provides a pane where other cells can appear (like Wi-Fi). Recommended for configuration-friendly tweaks.
</source>


Saved in your tweak's folder as <code>layout/Library/PreferenceLoader/Preferences/com.your.tweak.plist</code>
==== Using blocks ====


<pre>{
<source lang="objc">
    title = "Your Tweak";
static id observer;
    entry = {
%ctor {
        cell = PSLinkCell;
observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
        label = "Your Tweak";
object:nil queue:[NSOperationQueue mainQueue]
        icon = "/Applications/Preferences.app/icon-table@2x.png";
usingBlock:^(NSNotification *notification) {
    };
// UI is available, use UIKit here
    items = (
#if BEFORE_IOS_8
        {
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert"  
            cell = PSSwitchCell;
message:@"This is an alert."
            defaults = "com.your.tweak";
delegate:nil
            label = Enabled;
cancelButtonTitle:@"I'm ok with this"
            key = enabled;
otherButtonTitles:nil];
            default = 1;
[alert show];
            PostNotification = "com.your.tweak/preferences.changed";
#else
        }
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Alert"
        // add more cells (dictionaries) here
message:@"This is an alert."
    );
preferredStyle:UIAlertControllerStyleAlert];
}</pre>
[alert addAction:[UIAlertAction actionWithTitle:@"I'm ok with this"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}]];
[[UIWindow keyWindow].rootViewController presentViewController:alert animated:YES completion:^{}];
#endif
}
];
}


==== PreferenceLoader Style ====
// Remove observer upon unloading the dylib
%dtor {
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}
</source>


Provides a static list of cells. Recommended for Preference Bundles of tweaks.
=== Dynamic bundle loading ===


Saved in your tweak's Preference Bundle subproject folder as <code>Resources/com.your.tweak.plist</code>
Same applies if a class you want to hook is dynamically loaded by a bundle.


<pre>{
''To do: read [https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/dyld.3.html this] and make this procedure more robust.''
    items = (
        {
            cell = PSSwitchCell;
            defaults = "com.your.tweak";
            label = Enabled;
            key = enabled;
            default = 1;
            PostNotification = "com.your.tweak/preferences.changed";
        }
        // add more cells (dictionaries) here
    );
}</pre>


=== Flipswitches ===
==== Using callbacks ====


After using the Flipswitch NIC template, modify accordingly
<source lang="objc">
static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
if ([((NSDictionary *)userInfo)[NSLoadedClasses] containsObject:@"targetClass"]) {
// Target class has been loaded
}
}
%ctor {
CFNotificationCenterAddObserver(
CFNotificationCenterGetLocalCenter(), NULL,
notificationCallback,
(CFStringRef)NSBundleDidLoadNotification,
NULL, CFNotificationSuspensionBehaviorCoalesce);
}
</source>


==== Switch.x ====
==== Using blocks ====


<source lang="objc">
<source lang="objc">
#import "FSSwitchDataSource.h"
static id observer;
#import "FSSwitchPanel.h"
%ctor {
 
observer = [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
@interface NSUserDefaults (Tweak_Category)
object:nil queue:[NSOperationQueue mainQueue]
- (id)objectForKey:(NSString *)key inDomain:(NSString *)domain;
usingBlock:^(NSNotification *notification) {
- (void)setObject:(id)value forKey:(NSString *)key inDomain:(NSString *)domain;
if ([notification.userInfo[NSLoadedClasses] containsObject:@"targetClass"]) {
@end
// Target class has been loaded
 
}
static NSString *nsDomainString = @"com.your.tweak";
}
static NSString *nsNotificationString = @"com.your.tweak/preferences.changed";
];
 
@interface YourTweakFlipswitchSwitch : NSObject <FSSwitchDataSource>
@end
 
@implementation YourTweakFlipswitchSwitch
 
- (NSString *)titleForSwitchIdentifier:(NSString *)switchIdentifier {
    return @"Your Tweak";
}
 
- (FSSwitchState)stateForSwitchIdentifier:(NSString *)switchIdentifier {
    NSNumber *n = (NSNumber *)[[NSUserDefaults standardUserDefaults] objectForKey:@"enabled" inDomain:nsDomainString];
    BOOL enabled = (n)? [n boolValue]:YES;
    return (enabled) ? FSSwitchStateOn : FSSwitchStateOff;
}
}


- (void)applyState:(FSSwitchState)newState forSwitchIdentifier:(NSString *)switchIdentifier {
//if a destructor existed or you don't need to track the notification anymore
    switch (newState) {
%dtor {
    case FSSwitchStateIndeterminate:
[[NSNotificationCenter defaultCenter] removeObserver:observer];
        break;
    case FSSwitchStateOn:
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"enabled" inDomain:nsDomainString];
        CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)nsNotificationString, NULL, NULL, YES);
        break;
    case FSSwitchStateOff:
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"enabled" inDomain:nsDomainString];
        CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), (CFStringRef)nsNotificationString, NULL, NULL, YES);
        break;
    }
    return;
}
}
@end
</source>
</source>


Line 184: Line 195:
| [[ApplicationScripting.framework/Inheritance hierarchy‏‎]] || [[ApplicationScripting.framework/MIG subsystem‏]]
| [[ApplicationScripting.framework/Inheritance hierarchy‏‎]] || [[ApplicationScripting.framework/MIG subsystem‏]]
|-
|-
| || [[AppSupport.framework/MIG subsystem]]
| [[AppSupport.framework/Inheritance hierarchy‏‎]] || [[AppSupport.framework/MIG subsystem]]
|-
|-
| [[AudioToolbox.framework/Inheritance hierarchy]] || [[‏AudioToolbox.framework/MIG subsystem]]
| [[AudioToolbox.framework/Inheritance hierarchy]] || [[‏AudioToolbox.framework/MIG subsystem]]
Line 194: Line 205:
| [[‏BulletinBoard.framework/Inheritance hierarchy‏]] || [[‏BulletinBoard.framework/MIG subsystem]]
| [[‏BulletinBoard.framework/Inheritance hierarchy‏]] || [[‏BulletinBoard.framework/MIG subsystem]]
|-
|-
| || [[ChatKit.framework/MIG subsystem‏]]
| [[ChatKit.framework/Inheritance hierarchy‏‎]] || [[ChatKit.framework/MIG subsystem‏]]
|-
|-
| [[CoreTelephony.framework/Inheritance hierarchy‏‎]] || [[CoreFoundation.framework/MIG subsystem]]
| [[CoreFoundation.framework/Inheritance hierarchy‏‎]] || [[CoreFoundation.framework/MIG subsystem]]
|-
|-
| || [[CoreTelephony.framework/MIG subsystem]]
| [[CoreTelephony.framework/Inheritance hierarchy‏‎]] || [[CoreTelephony.framework/MIG subsystem]]
|-
|-
| || [[‏Foundation.framework/MIG subsystem]]
| [[‏Foundation.frameworkInheritance hierarchy‏‎]] || [[‏Foundation.framework/MIG subsystem]]
|-
|-
| [[GraphicsServices.framework/Inheritance hierarchy‏‎]] || [[GraphicsServices.framework/MIG subsystem]]
| [[GraphicsServices.framework/Inheritance hierarchy‏‎]] || [[GraphicsServices.framework/MIG subsystem]]
Line 212: Line 223:
| [[MapKit.framework/Inheritance hierarchy]] || [[MapKit.framework/MIG subsystem]]
| [[MapKit.framework/Inheritance hierarchy]] || [[MapKit.framework/MIG subsystem]]
|-
|-
| || [[‏Message.framework/MIG subsystem]]
| [[Message.framework/Inheritance hierarchy]] || [[‏Message.framework/MIG subsystem]]
|-
| [[QuartzCore.framework/Inheritance hierarchy]] || [[QuartzCore.framework/MIG subsystem]]
|-
|-
| [[Preferences.app/Inheritance hierarchy‏]] || [[‏Preferences.app/MIG subsystem]]
| [[Preferences.app/Inheritance hierarchy‏]] || [[‏Preferences.app/MIG subsystem]]
|-
|-
| || [[Preferences.framework/MIG subsystem]]
| [[Preferences.framework/Inheritance hierarchy]] || [[Preferences.framework/MIG subsystem]]
|-
|-
| [[SpringBoardServices.framework/Inheritance hierarchy‏]] ||  
| [[SpringBoard.app/Inheritance hierarchy]] || [[SpringBoard.app/MIG subsystem]]
|-
| [[SpringBoardServices.framework/Inheritance hierarchy‏]] || [[SpringBoardServices.framework/MIG subsystem]]
|-
|-
| [[TelephonyUI.framework/Inheritance hierarchy]] || [[TelephonyUI.framework/MIG subsystem]]
| [[TelephonyUI.framework/Inheritance hierarchy]] || [[TelephonyUI.framework/MIG subsystem]]
|-
| [[UIKit.framework/Inheritance hierarchy]] || [[UIKit.framework/MIG subsystem]]
|-
|-
| [[WiFiPicker.servicebundle/Inheritance hierarchy]] || [[‏WiFiPicker.servicebundle/MIG subsystem‏]]
| [[WiFiPicker.servicebundle/Inheritance hierarchy]] || [[‏WiFiPicker.servicebundle/MIG subsystem‏]]
|-
|-
|}
== Test Zone ==
Wondering what looks better:
To "wikitable"...
{| class="wikitable"
|+ com.your.tweak.plist
! NeXTSTEP style !! XML style
|- valign="top"
|
<source lang="javascript">{
entry = {
cell = PSSwitchCell;
defaults = "com.your.tweak";
label = "Your Tweak";
key = enabled;
default = 1;
icon = "/Applications/Preferences.app/[email protected]";
PostNotification = "com.your.tweak/preferences.changed";
};
}
</source>
||
<source lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>entry</key>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>defaults</key>
<string>com.your.tweak</string>
<key>label</key>
<string>Your Tweak</string>
<key>key</key>
<string>enabled</string>
<key>default</key>
<true/>
<key>icon</key>
<string>/Applications/Preferences.app/[email protected]</string>
<key>PostNotification</key>
<string>com.your.tweak/preferences.changed</string>
</dict>
</dict>
</plist>
</source>
|}
or not to "wikitable"...
{|
|+ com.your.tweak.plist
! NeXTSTEP style !! XML style
|- valign="top"
|
<source lang="javascript">{
entry = {
cell = PSSwitchCell;
defaults = "com.your.tweak";
label = "Your Tweak";
key = enabled;
default = 1;
icon = "/Applications/Preferences.app/[email protected]";
PostNotification = "com.your.tweak/preferences.changed";
};
}
</source>
||
<source lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>entry</key>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>defaults</key>
<string>com.your.tweak</string>
<key>label</key>
<string>Your Tweak</string>
<key>key</key>
<string>enabled</string>
<key>default</key>
<true/>
<key>icon</key>
<string>/Applications/Preferences.app/[email protected]</string>
<key>PostNotification</key>
<string>com.your.tweak/preferences.changed</string>
</dict>
</dict>
</plist>
</source>
|}
Maybe collapsing?
{|
|+ com.your.tweak.plist
|- valign="top"
|
{{Collapse|1=<source lang="javascript">{
entry = {
cell = PSSwitchCell;
defaults = "com.your.tweak";
label = "Your Tweak";
key = enabled;
default = 1;
icon = "/Applications/Preferences.app/[email protected]";
PostNotification = "com.your.tweak/preferences.changed";
};
}
</source>|2=NeXTSTEP style}}
||
{{Collapse|1=<source lang="xml">
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>entry</key>
<dict>
<key>cell</key>
<string>PSSwitchCell</string>
<key>defaults</key>
<string>com.your.tweak</string>
<key>label</key>
<string>Your Tweak</string>
<key>key</key>
<string>enabled</string>
<key>default</key>
<true/>
<key>icon</key>
<string>/Applications/Preferences.app/[email protected]</string>
<key>PostNotification</key>
<string>com.your.tweak/preferences.changed</string>
</dict>
</dict>
</plist>
</source>|2=XML style}}
|}
|}

Latest revision as of 05:06, 30 January 2018

Find me on saurik's IRC server, Twitter, Reddit, Github, StackOverflow or send me an email.

Made UnlockEvents and FlipNC.

If you are looking for notifications within a process, give NotificationExplorer a look.

Some NIC templates here.

And a repository there: uroboro.github.io/repo.

DISCLAIMER: All code written in this page is to be considered experimental and untested. The author shall not be held responsible or liable for any undesired consequences, including, but not limited to, data loss, property damage, time travel, the singularity, suffered by you as a result of any error in such code. You also agree that you will not use these code snippets for any purposes prohibited by United States law, including, without limitation, the development, design, manufacture, or production of nuclear, missile, or chemical or biological weapons.

"Interesting" links

Hooking everything

While previously it was possible to provide no filter or an empty one, this is no longer the case, both by Cydia Substrate requiring one and not it being empty. So the solution is based on another requirement that is not very documented: Cydia Substrate will only hook binaries that are linked with Security.framework. The following filter will suffice for this purpose but please reduce the hooking target of your tweak as much as possible.

com.your.tweak.plist

Late hooking

UI usage

If you need to use UI elements, you have to wait until the application is ready.

Using callbacks

static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
	// UI is available, use UIKit here
#if BEFORE_IOS_8
	UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert" 
		message:@"This is an alert." 
		delegate:nil 
		cancelButtonTitle:@"I'm ok with this" 
		otherButtonTitles:nil];
	[alert show];			
#else
	UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Alert"
		message:@"This is an alert."
		preferredStyle:UIAlertControllerStyleAlert];
	[alert addAction:[UIAlertAction actionWithTitle:@"I'm ok with this" 
		style:UIAlertActionStyleDefault
		handler:^(UIAlertAction * action) {}]];
	[[UIWindow keyWindow].rootViewController presentViewController:alert animated:YES completion:^{}];
#endif
}
 
static void * observer = NULL;

%ctor {
	CFNotificationCenterAddObserver(
		CFNotificationCenterGetLocalCenter(),
		&observer,
		notificationCallback,
		(CFStringRef)UIApplicationDidFinishLaunchingNotification,
		NULL,
		CFNotificationSuspensionBehaviorCoalesce
	);
}

// Remove observer upon unloading the dylib
%dtor {
	CFNotificationCenterRemoveObserver(
		CFNotificationCenterGetLocalCenter(),
		&observer,
		(CFStringRef)UIApplicationDidFinishLaunchingNotification,
		NULL
	);
}

Using blocks

static id observer;
%ctor {
	observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification
		object:nil queue:[NSOperationQueue mainQueue]
		usingBlock:^(NSNotification *notification) {
			// UI is available, use UIKit here
#if BEFORE_IOS_8
			UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert" 
				message:@"This is an alert." 
				delegate:nil 
				cancelButtonTitle:@"I'm ok with this" 
				otherButtonTitles:nil];
			[alert show];			
#else
			UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Alert"
				message:@"This is an alert."
				preferredStyle:UIAlertControllerStyleAlert];
			[alert addAction:[UIAlertAction actionWithTitle:@"I'm ok with this" 
				style:UIAlertActionStyleDefault
				handler:^(UIAlertAction * action) {}]];
			[[UIWindow keyWindow].rootViewController presentViewController:alert animated:YES completion:^{}];
#endif
		}
	];
}

// Remove observer upon unloading the dylib
%dtor {
	[[NSNotificationCenter defaultCenter] removeObserver:observer];
}

Dynamic bundle loading

Same applies if a class you want to hook is dynamically loaded by a bundle.

To do: read this and make this procedure more robust.

Using callbacks

static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
	if ([((NSDictionary *)userInfo)[NSLoadedClasses] containsObject:@"targetClass"]) {
		// Target class has been loaded
	}
}
 
%ctor {
	CFNotificationCenterAddObserver(
		CFNotificationCenterGetLocalCenter(), NULL,
		notificationCallback,
		(CFStringRef)NSBundleDidLoadNotification,
		NULL, CFNotificationSuspensionBehaviorCoalesce);
}

Using blocks

static id observer;
%ctor {
	observer = [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification
		object:nil queue:[NSOperationQueue mainQueue]
		usingBlock:^(NSNotification *notification) {
			if ([notification.userInfo[NSLoadedClasses] containsObject:@"targetClass"]) {
				// Target class has been loaded
			}
		}
	];
}

//if a destructor existed or you don't need to track the notification anymore
%dtor {
	[[NSNotificationCenter defaultCenter] removeObserver:observer];
}

Other Stuff

Similar Wanted Pages:

Inheritance hierarchy‏‎ MIG Subsystems
ApplicationScripting.framework/Inheritance hierarchy‏‎ ApplicationScripting.framework/MIG subsystem‏
AppSupport.framework/Inheritance hierarchy‏‎ AppSupport.framework/MIG subsystem
AudioToolbox.framework/Inheritance hierarchy ‏AudioToolbox.framework/MIG subsystem
AVFoundation.framework/Inheritance hierarchy‏ AVFoundation.framework/MIG subsystem
BackBoardServices.framework/Inheritance hierarchy‏ BackBoardServices.framework/MIG subsystem‏‎
‏BulletinBoard.framework/Inheritance hierarchy‏ ‏BulletinBoard.framework/MIG subsystem
ChatKit.framework/Inheritance hierarchy‏‎ ChatKit.framework/MIG subsystem‏
CoreFoundation.framework/Inheritance hierarchy‏‎ CoreFoundation.framework/MIG subsystem
CoreTelephony.framework/Inheritance hierarchy‏‎ CoreTelephony.framework/MIG subsystem
‏Foundation.frameworkInheritance hierarchy‏‎ ‏Foundation.framework/MIG subsystem
GraphicsServices.framework/Inheritance hierarchy‏‎ GraphicsServices.framework/MIG subsystem
IH boilerplate/Inheritance hierarchy‏‎ IH boilerplate/MIG subsystem‏‎
IOKit.framework/Inheritance hierarchy‏‎ IOKit.framework/MIG subsystem
IOSurface.framework/Inheritance hierarchy ‏IOSurface.framework/MIG subsystem
MapKit.framework/Inheritance hierarchy MapKit.framework/MIG subsystem
Message.framework/Inheritance hierarchy ‏Message.framework/MIG subsystem
QuartzCore.framework/Inheritance hierarchy QuartzCore.framework/MIG subsystem
Preferences.app/Inheritance hierarchy‏ ‏Preferences.app/MIG subsystem
Preferences.framework/Inheritance hierarchy Preferences.framework/MIG subsystem
SpringBoard.app/Inheritance hierarchy SpringBoard.app/MIG subsystem
SpringBoardServices.framework/Inheritance hierarchy‏ SpringBoardServices.framework/MIG subsystem
TelephonyUI.framework/Inheritance hierarchy TelephonyUI.framework/MIG subsystem
UIKit.framework/Inheritance hierarchy UIKit.framework/MIG subsystem
WiFiPicker.servicebundle/Inheritance hierarchy ‏WiFiPicker.servicebundle/MIG subsystem‏

Test Zone

Wondering what looks better:

To "wikitable"...

com.your.tweak.plist
NeXTSTEP style XML style
{
	entry = {
		cell = PSSwitchCell;
		defaults = "com.your.tweak";
		label = "Your Tweak";
		key = enabled;
		default = 1;
		icon = "/Applications/Preferences.app/[email protected]";
		PostNotification = "com.your.tweak/preferences.changed";
	};
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>entry</key>
	<dict>
		<key>cell</key>
		<string>PSSwitchCell</string>
		<key>defaults</key>
		<string>com.your.tweak</string>
		<key>label</key>
		<string>Your Tweak</string>
		<key>key</key>
		<string>enabled</string>
		<key>default</key>
		<true/>
		<key>icon</key>
		<string>/Applications/Preferences.app/[email protected]</string>
		<key>PostNotification</key>
		<string>com.your.tweak/preferences.changed</string>
	</dict>
</dict>
</plist>

or not to "wikitable"...

com.your.tweak.plist
NeXTSTEP style XML style
{
	entry = {
		cell = PSSwitchCell;
		defaults = "com.your.tweak";
		label = "Your Tweak";
		key = enabled;
		default = 1;
		icon = "/Applications/Preferences.app/[email protected]";
		PostNotification = "com.your.tweak/preferences.changed";
	};
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>entry</key>
	<dict>
		<key>cell</key>
		<string>PSSwitchCell</string>
		<key>defaults</key>
		<string>com.your.tweak</string>
		<key>label</key>
		<string>Your Tweak</string>
		<key>key</key>
		<string>enabled</string>
		<key>default</key>
		<true/>
		<key>icon</key>
		<string>/Applications/Preferences.app/[email protected]</string>
		<key>PostNotification</key>
		<string>com.your.tweak/preferences.changed</string>
	</dict>
</dict>
</plist>

Maybe collapsing?

com.your.tweak.plist