原文鏈接:Associated Objects
Patterns
-
Adding private variables to facilitate implementation details. When extending the behavior of a built-in class, it may be necessary to keep track of additional state. This is the textbook use case for associated objects. For example, AFNetworking uses associated objects on its
UIImageView
category to store a request operation object, used to asynchronously fetch a remote image at a particular URL. -
Adding public properties to configure category behavior. Sometimes, it makes more sense to make category behavior more flexible with a property, than in a method parameter. In these situations, a public-facing property is an acceptable situation to use associated objects. To go back to the previous example of AFNetworking, its category on
UIImageView
, itsimageResponseSerializer
allows image views to optionally apply a filter, or otherwise change the rendering of a remote image before it is set and cached to disk. - Creating an associated observer for KVO. When using KVO in a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.
Anti-Patterns
- Storing an associated object, when the value is not needed. A common pattern for views is to create a convenience method that populates fields and attributes based on a model object or compound value. If that value does not need to be recalled later, it is acceptable, and indeed preferable, not to associate with that object.
-
Storing an associated object, when the value can be inferred. For example, one might be tempted to store a reference to a custom accessory view's containing
UITableViewCell
, for use intableView:accessoryButtonTappedForRowWithIndexPath:
, when this can retrieved by callingcellForRowAtIndexPath:
. -
Using associated objects instead of X, where X is any one the following:
- Subclassing for when inheritance is a more reasonable fit than composition.
- Target-Action for adding interaction events to responders.
- Gesture Recognizers for any situations when target-action doesn't suffice.
- Delegation when behavior can be delegated to another object.
- NSNotification & NSNotificationCenter for communicating events across a system in a loosely-coupled way.
Associated objects should be seen as a method of last resort, rather than a solution in search of a problem (and really, categories themselves really shouldn't be at the top of the toolchain to begin with).
原文鏈接:Method Swizzling
+load vs. +initialize
Swizzling should always be done in +load
.
There are two methods that are automatically invoked by the Objective-C runtime for each class. +load
is sent when the class is initially loaded, while +initialize
is called just before the application calls its first method on that class or an instance of that class. Both are optional, and are executed only if the method is implemented.
Because method swizzling affects global state, it is important to minimize the possibility of race conditions. +load
is guaranteed to be loaded during class initialization, which provides a modicum of consistency for changing system-wide behavior. By contrast, +initialize
provides no such guarantee of when it will be executed—in fact, it may never be called, if that class is never messaged directly by the app.
dispatch_once
Swizzling should always be done in a dispatch_once
.
Again, because swizzling changes global state, we need to take every precaution available to us in the runtime. Atomicity is one such precaution, as is a guarantee that code will be executed exactly once, even across different threads. Grand Central Dispatch's dispatch_once
provides both of these desirable behaviors, and should be considered as much a standard practice for swizzling as they are for initializing singletons.
Selectors, Methods, & Implementations
In Objective-C, selectors, methods, and implementations refer to particular aspects of the runtime, although in normal conversation, these terms are often used interchangeably to generally refer to the process of message sending.
Here is how each is described in Apple's Objective-C Runtime Reference:
- Selector (
typedef struct objc_selector *SEL
): Selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or "mapped") with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded .- Method (
typedef struct objc_method *Method
): An opaque type that represents a method in a class definition.- Implementation (
typedef id (*IMP)(id, SEL, ...)
): This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
The best way to understand the relationship between these concepts is as follows: a class (Class
) maintains a dispatch table to resolve messages sent at runtime; each entry in the table is a method (Method
), which keys a particular name, the selector (SEL
), to an implementation (IMP
), which is a pointer to an underlying C function.
To swizzle a method is to change a class's dispatch table in order to resolve messages from an existing selector to a different implementation, while aliasing the original method implementation to a new selector.
Considerations
Swizzling is widely considered a voodoo technique, prone to unpredictable behavior and unforeseen consequences. While it is not the safest thing to do, method swizzling is reasonably safe, when the following precautions are taken:
- Always invoke the original implementation of a method (unless you have a good reason not to): APIs provide a contract for input and output, but the implementation in-between is a black box. Swizzling a method and not calling the original implementation may cause underlying assumptions about private state to break, along with the rest of your application.
- Avoid collisions: Prefix category methods, and make damn well sure that nothing else in your code base (or any of your dependencies) are monkeying around with the same piece of functionality as you are.
-
Understand what's going on: Simply copy-pasting swizzling code without understanding how it works is not only dangerous, but is a wasted opportunity to learn a lot about the Objective-C runtime. Read through the Objective-C Runtime Reference and browse
<objc/runtime.h>
to get a good sense of how and why things happen. Always endeavor to replace magical thinking with understanding. -
Proceed with caution: No matter how confident you are about swizzling Foundation, UIKit, or any other built-in framework, know that everything could break in the next release. Be ready for that, and go the extra mile to ensure that in playing with fire, you don't get
NSBurned
.
原文鏈接: Objective-C Runtime
Objective-C
Objective-C 擴展了 C 語言,并加入了面向對象特性和 Smalltalk 式的消息傳遞機制。而這個擴展的核心是一個用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 面向對象和動態機制的基石。
Objective-C 是一個動態語言,這意味著它不僅需要一個編譯器,也需要一個運行時系統來動態得創建類和對象、進行消息傳遞和轉發。理解 Objective-C 的 Runtime 機制可以幫我們更好的了解這個語言,適當的時候還能對語言進行擴展,從系統層面解決項目中的一些設計或技術問題。了解 Runtime ,要先了解它的核心 - 消息傳遞 (Messaging)。
Objective-C 中給一個對象發送消息會經過以下幾個步驟:
- 在對象類的 dispatch table 中嘗試找到該消息。如果找到了,跳到相應的函數IMP去執行實現代碼;
- 如果沒有找到,Runtime 會發送 +resolveInstanceMethod: 或者 +resolveClassMethod: 嘗試去 resolve 這個消息;
- 如果 resolve 方法返回 NO,Runtime 就發送 -forwardingTargetForSelector: 允許你把這個消息轉發給另一個對象;
- 如果沒有新的目標對象返回, Runtime 就會發送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。你可以發送 -invokeWithTarget: 消息來手動轉發消息或者發送 -doesNotRecognizeSelector: 拋出異常。