iOS学习笔记:Runtime
原文链接: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: 抛出异常。