About Objective-C runtime
Legacy and Modern Versions
最大区别是“non-fragile”
Legacy Version下修改了一个类中的实例变量,其子类需要重新编译
而Modern Version不需要
The low-level Objective-C runtime API is significantly updated in OS X version 10.5. Many functions and all existing data structures are replaced with new functions. The old functions and structures are deprecated in 32-bit and absent in 64-bit mode. The API constrains several values to 32-bit ints even in 64-bit mode—class count, protocol count, methods per class, ivars per class, arguments per method, sizeof(all arguments) per method, and class version number. In addition, the new Objective-C ABI (not described here) further constrains sizeof(anInstance) to 32 bits, and three other values to 24 bits—methods per class, ivars per class, and sizeof(a single ivar). Finally, the obsolete NXHashTable and NXMapTable are limited to 4 billion items. ——Objective-C Runtime Reference
iOS和OS X v10.5(64bit)系统以后的runtime都是modern version,其他都是legacy version
Interacting with Runtime
1.Objective-C source code
编译器会通过Class及其Extension和实现的protocol的代码,创建这个类的Class Data Structure:
然后运行时的objc_msgSend函数会通过这个Data Structure完成消息机制
2.NSObject Methods
作为rootClass(NSProxy除外),其方法直接由runtime functions来实现。
然而其中有一些方法是需要重写才比较有用的,例如description。默认该方法直接输出类名和指针地址。NSArray重写了成输出其成员的description列表。
NSObject中还有一些方法直接通过查询运行时来返回值,如:
isKindOfClass:, isMenberOfClass:
respondsToSelector:
conformsToProtocol:
MethodForSelector:
3.Runtime Functions
目录在/usr/include/objc
具体查看Objective-C Runtime Reference
Messaging
关于messaging机制的实现这篇写得很详细:Objective-c Messaging by 不会开机的男孩
The objc_msgSend Function
编译时先将
[receiver message]
转成objc_msgSend形式
objc_msgSend(receiver, selector)
有参数的形式
objc_msgSeng(receiver, selector, arg1, arg2, ...)
这部分转换是编译器完成的,你不应该直接使用objc_msgSend。
objc_msgSend的动态绑定方法机制步骤:
- 从哈希表中找到selector指向的方法实现(method implementation),当然查找关键字(key)包含class类型。
- 调用方法并传递参数。
- 传回return值。
messaging机制实现的关键是编译器给每一个class和object创建的结构体(structure),class structure包含如下元素:
- 指向父类的指针(A pointer to the superclass)。
- 类中定义的方法的分发表(A class dispatch table)。这张表记录了selector和对应的方法实现的地址。例如setOrigin::的selector对应setOrigin::方法具体实现代码的地址。
一个类class的对象object可以根据它包含的isa指针找到它所属的class,还有再往上的父类super class。
NSObject和NSProxy的子类都带有isa指针,但是不要依赖于isa指针来找创建时候的class,isa指针是归runtime管理的,很多时候会改变。如果要判断是否属于某个类用
isKindOfClass:
。有篇相关文章有讲isa的,忘了在哪里了wwww。
Messaging Framework
说明:当发送一个message给一个class的时候,objc_msgSend通过isa找到所属class,然后在这个class structure的dispatch table里面查找对应方法,如果没有的话再根据这个class structure里面的isa指针网上找super class里面有没有这个方法。找到之后再调用并发送参数,完成后返回return值。
objc_msgSend这个函数会缓存查找过的selector和对应的address来加速以后的查找。
Using Hidden Arguments
上面说道objc_msgSend根据selector找到实现方法(method implementation)的地址后会调用它并传递参数,它其实还传递了两个额外的参数:
- 消息接收object
- selector
这两个参数让实现方法(method implementation)知道当前消息接收方object和对应selector的信息。
这里说它们是隐形传递的是因为他们在编译的时候才由编译器补上。
代码中对应使用self和_cmd就可以:
- strange {
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
Getting a Method Address
使用Cocoa runtime提供的methodForSelector:
(不是objective-c方法)可以直接获取selector对应的method implementation address。
可以使用它来绕过messaging机制,如果有大量方法需要顺序调用的话。
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
这里setter方法前两个参数分别对应上面说的隐形参数self和_cmd,这里需要显性地写出来因为编译器不会帮单纯的c函数补上它们。
Dynamic Method Resolution //动态方法决议
讲这个:@dynamic
。
比如如下property声明:
@dynamic propertyName
可以使用resolveInstanceMethod:
和resolveClassMethod:
来动态提供method implementation。
objective-c对象的方法其实就是至少包含两个隐形参数的c函数:
void dynamicMethodIMP(id self, SEL _cmd){
//implementation ....
}
你可以使用class_addMethod
(也是Cocoa runtime函数)动态地向一个objective-c对象添加方法,实现resolveInstanceMethod:
就可以:
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
动态方法决议发生在函数respondsToSelector:
或 instancesRespondToSelector:
被调用的时候,也就是messaging机制之前。在实现上面resolveInstanceMethod:
的时候,如果想让某次特定的SEL直接进到messaging机制,直接返回NO。
Dynamic Loading
objective-c程序可以在运行的时候动态地增加classes和categories。Interface Builder就是例子。两个动态加载方法:
- bjc_loadModules, defined in objc/objc-load.h
- NSBundle, by Foundation framework
Message Forwarding
[cat fly];
如果cat object没有fly方法,可以使用forwardInvocation:
方法来forward给其他object。
Forwarding
messaging过程中查找不到的方法,在报错之前,会被封装成NSIvocation发送给forwardInvocation:
(继承于NSObject)方法。NSInvocation object封装了message和message 参数。
forwardInvocation:
默认调用doesNotRecognizeSelector:
。
重写override转发消息给someOtherObject
的栗子,要使用respondsToSelector:
做检查,最后还要调用super方法往上forward:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector: [anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
用处:
- unrecognized messages distribution
- 抑制报错
- 多个不同的 message 合并或同一个message forward给不同的object/method
Forwarding and Multiple Inheritance
消息转发旨在将作为只能单一继承的objective-c中object graphic的补充,辅助细分功能模块到不同object。
多重继承则是为了构建一个庞大的多功能的object。
Surrogate Ojbects
替代对象
messaging不仅仅模仿了多重继承,还有一个作用是用来创建轻量级但是可以接受多种message的object。这个应该是模型设计的问题了。
替代对象仅仅实现一些比较简单的message而把其他都forwarcd给其他object来完成,只要做object检查然后invocate就可以。
The Objective-C Programming Language 中“remote messaging”也有讲这个。
Forwarding and Inheritance
虽然Forwarding机制模仿了继承,但是NSObject把它们区分的非常细。respondsToSelector:
和isKindOfClass:
只查找它们的 inheritance hierarchy(怎么翻译……)而不是forwarding chain。
当然,如果你想这两个方法对forwarding响应的话重写override也是可以的:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO; }
instancesRespondToSelector:
、 conformsToProtocol:
也是一样。
如果你需要使用forwarding转发所有它接受的messagees,需要实现methodSignatureForSelector:
并返回非nil值:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
Note: This is an advanced technique, suitable only for situations where no other solution is possible. It is not intended as a replacement for inheritance. If you must make use of this technique, make sure you fully understand the behavior of the class doing the forwarding and the class you’re forwarding to.
该章节的方法见Foundation framework reference中的NSObject class specification。
invokeWithTarget:
则在oundation framework reference中的NSInvocation class specification。
Type Encodings
@encode()
贴表记下:
Code | Meaning |
---|---|
c | A char |
i | An int |
s | A short |
l | A long (l is treated as 32-bit quantity on 64-bit programs) |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A flost |
d | A double |
B | A C++ bool or C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed ot typed id) |
# | A class object (Class) |
: | A method selector(SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A untion |
bnum | A bit field of num bits |
^type | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
Objective-C不支持 long double 类型。@encode(longdouble)返回d, 和 double 一样.
下面是栗子时间:
一个12个指向float的指针构成的array返回值如下:
[12^f]
structure栗子:
typedef struct example {
id anObject;
char *aString;
int anInt;
} Example;
返回值如下:
{example=@*i}
一个指向上面栗子structure的指针如下:
^{example=@*i}
然而,如果再来一个指向这个指针的指针就会变成这样:
^^{example}
NSObject的栗子:
{NSObject=#}
其中的#是isa
下面是runtime下增加的返回值:
Code | Meaning |
---|---|
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
没有详细解释 ╮(╯_╰)╭
Declared Properties
Objective-C的property结构体定义如下:
typedef struct objc_property *Property;
使用class_copyPropertyList
和protocol_copyPropertyList
可以取得一个class所有property信息的数组(包括category和protocal定义的)。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
例如这个栗子:
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
这样来取:
id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
使用property_getName
可以discover一个property的名字:
const char *property_getName(objc_property_t property)
使用class_getProperty
和protocal_getProperty
来获取指定获取一个class/protocal中定义的property指针。
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
遍历class输出所有property信息:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property),
property_getAttributes(property));
}
Property Type String
你可以使用property_getAttributes
方法来获取property的name、@encode string等信息。
property的@encode结果以T
开头然后跟着一个逗号,
,接着是一个V
跟着后台变量类型名(name of the backing instance variable)
Code | Meaning |
---|---|
R | The property is read-only (readonly). |
C | The property is a copy of the value last assigned (copy). |
& | The property is a reference to the value last assigned (retain). |
N | The property is non-atomic (nonatomic). |
G |
The property defines a custom getter selector name. The name follows the G (for example, GcustomGetter,). |
S |
The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,). |
D | The property is dynamic (@dynamic). |
W | The property is a weak reference (__weak). |
P | The property is eligible for garbage collection. |
t |
Specifies the type using old-style encoding. |
Property Attribute Description Examples
栗子:
enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };
结果如下表:
Property declaration | Property description |
---|---|
@property char charDefault; | Tc,VcharDefault |
@property double doubleDefault; | Td,VdoubleDefault |
@property enum FooManChu enumDefault; | Ti,VenumDefault |
@property float floatDefault; | Tf,VfloatDefault |
@property int intDefault; | Ti,VintDefault |
@property long longDefault; | Tl,VlongDefault |
@property short shortDefault; | Ts,VshortDefault |
@property signed signedDefault; | Ti,VsignedDefault |
@property struct YorkshireTeaStruct structDefault; | T{YorkshireTeaStruct="pot"i"lady"c},VstructDefault |
@property YorkshireTeaStructType typedefDefault; | T{YorkshireTeaStruct="pot"i"lady"c},VtypedefDefault |
@property union MoneyUnion unionDefault; | T(MoneyUnion="alone"f"down"d),VunionDefault |
@property unsigned unsignedDefault; | TI,VunsignedDefault |
@property int (*functionPointerDefault)(char *); | T^?,VfunctionPointerDefault |
@property id idDefault;()Note: the compiler warns: "no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed") | T@,VidDefault |
@property int *intPointer; | T^i,VintPointer |
@property void *voidPointerDefault; | T^v,VvoidPointerDefault |
@property int intSynthEquals; (In the implementation block: @synthesize intSynthEquals=_intSynthEquals;) | Ti,V_intSynthEquals |
@property(getter=intGetFoo,setter=intSetFoo:) int intSetterGetter; | Ti,GintGetFoo,SintSetFoo:,VintSetterGetter |
@property(readonly) int intReadonly; | Ti,R,VintReadonly |
@property(getter=isIntReadOnlyGetter,readonly) int intReadonlyGetter; | Ti,R,GisIntReadOnlyGetter |
@property(readwrite) int intReadwrite; | Ti,VintReadwrite |
@property(assign) int intAssign; | Ti,VintAssign |
@property(retain)ididRetain; | T@,&,VidRetain |
@property(copy)ididCopy; | T@,C,VidCopy |
@property(nonatomic) int intNonatomic; | Ti,VintNonatomic |
@property(nonatomic, readonly, copy) id idReadonlyCopyNonatomic; | T@,R,C,VidReadonlyCopyNonatomic |
@property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; | T@,R,&,VidReadonlyRetainNonatomic |