m (→MobileSubstrate APIs and states: Removing filter plist won't do the job.) |
(→Coding practices: A new practice and some additional adjustments.) |
||
Line 3: | Line 3: | ||
== Coding practices == | == Coding practices == | ||
* | * Never use <code>[[UIDevice]]</code> in your constructor if you are hooking SpringBoard! | ||
* Access ivars through their associated properties, where practical. | * Access ivars through their associated properties, where practical. | ||
* Always call the old implementation of a method unless you require suppressing the default | * Always call the old implementation of a method unless you require suppressing the default behavior. Similarly, only call the old implementation of a method from within the method hook. | ||
* Use documented/public SDK features wherever practical. Apple usually tests private APIs internally before making them public; often the public version is the same or similar to the private version that was in previous OS releases. | * Use documented/public SDK features wherever practical. Apple usually tests private APIs internally before making them public; often the public version is the same or similar to the private version that was in previous OS releases. | ||
* Avoid [[UIAlertView]] in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a custom SBAlertItem subclass instead (not necessary for cases where you don't care if the system destroys your alert.) | * Avoid <code>[[UIAlertView]]</code> in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a custom <code>SBAlertItem</code> subclass instead (not necessary for cases where you don't care if the system destroys your alert.) | ||
* Use autoresizingMask and layoutSubviews to make your extension resolution-independent and rotation-aware. | * Use <code>-autoresizingMask</code> and <code>-layoutSubviews</code> to make your extension resolution-independent and rotation-aware. However, if there are any modern APIs from Apple doing the same, consider using that. <code>-layoutSubviews</code> can be invoked too many times and the performance may be reduced. | ||
* Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.) | * Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.) | ||
* UI* methods must be performed on the main thread (this includes [[UIImage]]! use CGImageSource and company if you need to load images from a background thread.) | * <code>UI*</code> methods must be performed on the main thread (this includes <code>[[UIImage]]</code>! use <code>CGImageSource</code> and company if you need to load images from a background thread.) | ||
* Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes '''any''' operation that hits the network.) | * Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes '''any''' operation that hits the network.) | ||
* Access resources through NSBundle so that they may be themed via WinterBoard. | * Access resources through <code>NSBundle</code> so that they may be themed via WinterBoard. | ||
* Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions. | * Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions. | ||
* If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.) | * If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.) | ||
* Participate in the memory warning system if your extension has a significant memory footprint; release everything you can. | * Participate in the memory warning system if your extension has a significant memory footprint; release everything you can. | ||
* Use mmap when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.) | * Use <code>mmap()</code> when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.) | ||
* Compile against the earliest possible firmware you can (compile against 3.0, not 3.0.1, 3.1 or 3.1.2); use feature detection if necessary. | * Compile against the earliest possible firmware you can (compile against 3.0, not 3.0.1, 3.1 or 3.1.2); use feature detection if necessary. | ||
* Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details. | * Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details. | ||
Line 22: | Line 22: | ||
* Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time (use dlopen/dlsym to load libraries and access symbols at runtime after the app has launched) | * Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time (use dlopen/dlsym to load libraries and access symbols at runtime after the app has launched) | ||
** This is less of a concern on 3.1+, due to the [[dyld_shared_cache|dyld shared object cache]]. You still incur the overhead of library initializers, however. | ** This is less of a concern on 3.1+, due to the [[dyld_shared_cache|dyld shared object cache]]. You still incur the overhead of library initializers, however. | ||
* Use a private prefix on classes and category methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a -[NSData base64String] that behave slightly differently.) | * Use a private prefix on classes and category methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a <code>-[NSData base64String]</code> that behave slightly differently.) | ||
* Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, hook the Mail application's sync and integrate the action within that (scheduler/alarm extensions need not heed this warning.) | * Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, hook the Mail application's sync and integrate the action within that (scheduler/alarm extensions need not heed this warning.) | ||
* Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!) | * Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!) | ||
* Don't overuse NSLog | * Don't overuse <code>NSLog(), CFLog(), fprintf(stderr, …);</code> they're slow and synchronous. | ||
* If your extension exposes an icon, include both the 29x29 and 59x60 icon sizes (more for iPad.) | * If your extension exposes an icon, include both the 29x29 and 59x60 icon sizes (more for iPad.) | ||
* Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail. | * Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail. | ||
* Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but seem to be deprecated) | * Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but seem to be deprecated) | ||
* Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well. | * Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well. | ||
* Be aware that <code>-[NSBundle bundleIdentifier]</code> may return <code>nil</code> if it's invoked within an arbitrary process that does not belong to any specific bundle. When it's used with certain other methods that expect the non-null value, it can indefinitely crash the process. In some cases, it will drain the battery because the process will keep restarting. This is some [https://github.com/pr0crustes/SmoothTable/issues/2 bad example]. | |||
* Respect the user's privacy. | * Respect the user's privacy. | ||
** The IMEI, telephone number, contacts list and email inbox are definitely private information. | ** The IMEI, telephone number, contacts list and email inbox are definitely private information. |
Revision as of 07:01, 22 October 2018
Follow the following guidelines to avoid common MobileSubstrate mistakes. See also: Best Practices.
Coding practices
- Never use
UIDevice
in your constructor if you are hooking SpringBoard! - Access ivars through their associated properties, where practical.
- Always call the old implementation of a method unless you require suppressing the default behavior. Similarly, only call the old implementation of a method from within the method hook.
- Use documented/public SDK features wherever practical. Apple usually tests private APIs internally before making them public; often the public version is the same or similar to the private version that was in previous OS releases.
- Avoid
UIAlertView
in SpringBoard--it doesn't participate in the SpringBoard-wide alert system; use a customSBAlertItem
subclass instead (not necessary for cases where you don't care if the system destroys your alert.) - Use
-autoresizingMask
and-layoutSubviews
to make your extension resolution-independent and rotation-aware. However, if there are any modern APIs from Apple doing the same, consider using that.-layoutSubviews
can be invoked too many times and the performance may be reduced. - Avoid doing extra work--the methods your extension hooks may be called from inside a tight loop; disk access is considered a LOT of extra work (related: properties are actually method calls, they can be slow.)
UI*
methods must be performed on the main thread (this includesUIImage
! useCGImageSource
and company if you need to load images from a background thread.)- Never block the main thread! Performing any operation that can take significant time on the main thread is evil (that includes any operation that hits the network.)
- Access resources through
NSBundle
so that they may be themed via WinterBoard. - Don't make assumptions about the ivar layout of a class; these can and will change across firmware versions.
- If accessing low-level graphics hardware, beware that first-generation devices do _not_ have a unified memory model (VRAM and regular RAM are separate regions.)
- Participate in the memory warning system if your extension has a significant memory footprint; release everything you can.
- Use
mmap()
when working with large datasets--the kernel will swap it in and out of memory as necessary (but avoid heavy writes, as that's no better than virtual memory.) - Compile against the earliest possible firmware you can (compile against 3.0, not 3.0.1, 3.1 or 3.1.2); use feature detection if necessary.
- Strip symbols if you have something to hide; use standard C (or C++) for the hidden parts--Objective-C will spill your details.
- Many private frameworks have public headers available for Mac OS X; use them (also, there are header packs floating around--kennytm's is one of the best and is legal to distribute.)
- Only link to the frameworks you actually need--importing unnecessary frameworks will increase app launch time (use dlopen/dlsym to load libraries and access symbols at runtime after the app has launched)
- This is less of a concern on 3.1+, due to the dyld shared object cache. You still incur the overhead of library initializers, however.
- Use a private prefix on classes and category methods: Objective-C has NO namespacing capabilities. (examples: two classes called Preferences that behave differently; two extensions define a
-[NSData base64String]
that behave slightly differently.) - Avoid waking the device or keeping the device from sleeping. If your extension needs to do something periodically, hook the Mail application's sync and integrate the action within that (scheduler/alarm extensions need not heed this warning.)
- Avoid CPU-/GPU-/disk-intensive activity where the user would not expect it (battery life!)
- Don't overuse
NSLog(), CFLog(), fprintf(stderr, …);
they're slow and synchronous. - If your extension exposes an icon, include both the 29x29 and 59x60 icon sizes (more for iPad.)
- Avoid changing the class of objects returned by public APIs--some App Store applications perform unnecessary checks against the class of an object and will fail.
- Prefer binary plists over XML--they're much quicker (OpenSTEP-style text plists are also quick, but seem to be deprecated)
- Manage memory correctly as per the Cocoa memory management guidelines; ensure all hooks comply as well.
- Be aware that
-[NSBundle bundleIdentifier]
may returnnil
if it's invoked within an arbitrary process that does not belong to any specific bundle. When it's used with certain other methods that expect the non-null value, it can indefinitely crash the process. In some cases, it will drain the battery because the process will keep restarting. This is some bad example. - Respect the user's privacy.
- The IMEI, telephone number, contacts list and email inbox are definitely private information.
- The MAC address, Bluetooth address and UDID may be considered private information, depending on the jurisdiction.
Data management
- Store preferences in /var/mobile/Library/Preferences to have iTunes back it up.
- Store data in /var/mobile/Library/AddressBook or /var/mobile/Library/Keyboard to avoid the sandbox restrictions, if necessary.
MobileSubstrate APIs and states
- Always include a MobileLoader filter plist.
- If you require access to only SpringBoard, use "com.apple.springboard"; if you require access to all UIKit apps, use "com.apple.UIKit".
- If you hook a framework like "com.apple.UIKit", test your tweak in non-UIKit processes anyway (e.g. by temporarily changing the filter to "com.apple.Security" or "com.apple.CoreFoundation"). Broken tweaks with incorrect/missing filters will pull your tweak into processes you don't expect. However, there is a way to prevent unwanted code injection to non-UIKit processes. Modify if needed. See this.
- Don't modify OS or Cydia files; use runtime-replacement instead.
- Preferences.framework changed drastically between 3.1 and 3.2. Use PreferenceLoader and standard preferences plists for simple panes, PSViewController + UITableView for more complex panes (PSListController is okay for simple preferences, but is less subject to OS changes; PSViewController is more powerful, but many implementations were broken between 3.1 and 3.2).
- If your extension exposes an API, document it!
- Include headers inside your package so they're easy to find.
- Use an established hooking framework such as substrate.h, CaptainHook.h or Logos; there is little benefit in rolling your own, and too many details have to be just right.
- Never inhibit MobileSubstrate's safe mode.
- If your extension manipulates icon layouts, use IconSupport so you don't stomp all over other extensions' modifications or the stock/safe-mode layout.
- Expect others to hook the same methods you do; expect them not to have read this list and to have done crazy things.
- Prefer hooking methods over hooking functions--new jailbreaks don't always have kernel patches (although this is not as much of a concern, as jailbreakers understand the need for W^X) and MobileSubstrate may not support newer ARM instructions.
- Use MSHookMessageEx instead of MSHookMessage.
- MSHookMessage allows you to specify an optional prefix which gets added to the original method, polluting the class.
Packaging
- Include the proper Depends: line in your Debian control file (that way Cydia will automatically install dependencies and will alert users when they are missing required components like firmware version.)
- Ensure your extension is localizable; use the standard .lproj system where possible. Avoid having images that include text.