Objective-C 是面相運行時的語言(runtime oriented language),就是說它會儘可能的把編譯和鏈接時要執行的邏輯延遲到運行時。這就給了你很大的靈活性,你可以按需要把消息重定向給合適的對象,你甚 至可以交換方法的實現,等等(譯註:在 Objective-C 中調用一個對象的方法可以看成向一個對象發送消息, Method Swizzling 具體實現可以參看 jrswizzle )。這就需要使用 runtime。Runtime系統來動態創建類和對象,進行消息發送和轉發。
OC 類處理
參考 :
http://blog.csdn.net/lpstudy/article/details/21954711?utm_source=tuicool&utm_medium=referral
runtime是開源的。可以去down:http://opensource.apple.com/tarballs/objc4/objc4-437.1.tar.gz
我們可以Object.h從看對象的定義的實現 其實是一個結構體
/*
Object.h
Copyright 1988-1996 NeXT Software, Inc.
DEFINED AS: A common class
HEADER FILES: <objc/Object.h>
#if __OBJC2__
@interface Object
{
Class isa; /* A pointer to the instance's class structure */
}
+class;
-(BOOL) isEqual:anObject;
@end
#else
@interface Object
{
Class isa; /* A pointer to the instance's class structure */
}
/* Initializing classes and instances */
*/
isa 是一個指向實例的類的指針,
然後接下來使一些方法,包括
/* Creating, copying, and freeing instances */
/* Identifying classes */
/* Identifying and comparing instances */
/* Testing inheritance relationships */
等還有指向父類的指針
其實Class 中的 *isa 本身就是一個指針類型
在 Objective-C 中的對象的一個重要的特性是,你可以向它們發送消息:當你向一個 Objective-C 的對象發送消息的時候,runtime 沿着對象的 isa 指針找到了這個對象的 Class結構體。 Class 結構體中包含了一個這個類的方法列表和一個指向父類的指針,用於查找繼承的方法。
Class本身就是一個指針類型,是一個指向objc_class結構體指針。
lobjc_class是什麼?就是類的定義
typedef struct objc_class *Class;
struct objc_class
{
struct objc_class* isa;
struct objc_class*super_class;
const char*name;
long version;
long info;
long instance_size;
struct objc_ivar_list* ivars;
struct objc_method_list** methodLists;
struct objc_cache* cache;
struct objc_protocol_list* protocols;
};
同樣的, struct objc_class 中讓我們在 Class 上調用一個方法,Class 的 isa 指針必須指向一個 Class 結構體,並且那個 Class 結構體必須包含我們可以在那個 Class 上調用的方法的列表
這就引出了 meta-class 的定義:meta-class 是 Class 對象的類(the meta-class is the class for a Class object)。
cache用來緩存經常訪問的方法,它指向objc_cache結構體,後面會重點講到。
protocols表示類遵循哪些協議。
ivars表示多個成員變量,它指向objc_ivar_list結構體。在runtime.h可以看到它的定義
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
methodLists表示方法列表,它指向objc_method_list結構體的二級指針,可以動態修改*methodLists的值來添加成員方法,也是Category實現原理,同樣也解釋Category不能添加屬性的原因。在runtime.h可以看到它的定義:
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
同理,objc_method_list也是一個鏈表,存儲多個objc_method,而objc_method結構體存儲類的某個方法的信息。
Ivar
Ivar其實就是一個指向objc_ivar結構體指針,它包含了變量名(ivar_name)、變量類型(ivar_type)等信息。
Ivar表示類中的實例變量,在runtime.h文件中找到它的定義:
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
Cache
顧名思義,Cache主要用來緩存,那它緩存什麼呢?我們先在runtime.h文件看看它的定義:
typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
ache其實就是一個存儲Method的鏈表,主要是爲了優化方法調用的性能。當對象receiver調用方法message時,首先根據對象receiver的isa指針查找到它對應的類,然後在類的methodLists中搜索方法,如果沒有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調用方法。如果沒有找到,有可能消息轉發,也可能忽略它。但這樣查找方式效率太低,因爲往往一個類大概只有20%的方法經常被調用,佔總調用次數的80%。所以使用Cache來緩存經常調用的方法,當調用方法時,優先在Cache查找,如果沒有找到,再到methodLists查找。
meta-class 是 Class 對象的類。每個 Class 都有個不同的自己的 meta-class(因此每個 Class 都可以有一個自己不同的方法列表)。也就是說每個類的 Class 不完全相同。
meta-class 總是會保證 Class 對象會有從基類繼承的所有的的實例和類方法,加上之後繼承的類方法。如從 NSObject 繼承的類,就意味着在所有的 Class(和 meta-class)對象中定義了所有從 NSObject 繼承的實例和協議方法。
所有的 meta-class 使用基類的 meta-class(NSObject 的 meta-class 用於繼承自 NSObject 的類)作爲他們自己的類,包括在運行時自己定義的基礎的 meta-class。
簡單來說:
- 當你向一個對象發送消息,就在那個對象的方法列表中查找那個消息( 就是 -(typename) methodname 。。。 聲明的實力方法
- 當你想一個類發送消息,就再那個類的 meta-class 中查找那個消息。( + (typename) methodname 。。。聲明的類方法。
- 並且meta-class 是必須的,因爲它爲一個 Class 存儲類方法。每個類都必須有一個唯一的 meta-class,因爲每個 Class 都有一個可能不一樣的類方法。
和 Class 以 super_class 指針指向它的父類的方法一樣,meta-class 以 super_class 指針指向 Class 的 super_class 的 meta-class。(譯註:這句話有點繞,就是 super-class 一個指向 Class 的父類,一個指向 meta-class 的父類。Class 是一般對象的類型,meta-class 是 Class 的類型。)
消息機制
參考:
http://www.cocoachina.com/bbs/read.php?tid=189505
在runtime中Message也是一個結構體
struct objc_method {
SEL method_name OBJC2 _UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
/*OBJC2_UNAVAILABLE是一個Apple對Objc系統運行版本進行約束的宏定義,主要爲了兼容非Objective-C 2.0的遺留版本,但我們仍能從中獲取一些有用信息。 */
IMP
在上面講Method時就說過,IMP本質上就是一個函數指針,指向方法的實現,在objc.h找到它的定義:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
id objc_msgSend ( id self, SEL op, ... );
首先 SEL 類裏面的方法都是被轉換成SEL變量進行存儲的。SEL其本身是一個Int類型的一個地址,地址中存放着方法的名字。對於一個類中。每一個方法對應着一個SEL。所以iOS類中不能存在2個名稱相同 的方法,即使參數類型不同,因爲SEL是根據方法名字生成的,相同的方法名稱只能對應一個SEL。SEL 可以用@selector()取類方法的編號,他的行爲基本可以等同C語言的中函數指針,只不過C語言中,可以把函數名直接賦給一個函數指針,而Object-C的類不能直接應用函數指針,這樣只能做一個@selector語法來取.
id其實就是一個指向objc_object結構體指針,它包含一個Class isa成員,根據isa指針就可以順藤摸瓜找到對象所屬的類。
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
C/C++ 函數指針
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int (* funcp)(int, int);
int func(int a, int b)
{
return a + b;
}
int main()
{
funcp = &func;
cout<<funcp(1, 2)<<endl;
return 0;
}
object-c的選擇器
SEL afun = @selector(someMethodName:::::);
在讓我們看一下objc_msgSend它具體是如何發送消息:
首先根據receiver對象的isa指針獲取它對應的class;
優先在class的cache查找message方法,如果找不到,再到methodLists查找;
如果沒有在class找到,再到super_class查找;
一旦找到message這個方法,就執行它實現的IMP。
消息轉發機制
receiver message]調用方法時,如果在message方法在receiver對象的類繼承體系中沒有找到方法,那怎麼辦?一般情況下,程序在運行時就會Crash掉,拋出unrecognized selector sent to…類似這樣的異常信息。但在拋出異常之前,還有三次機會按以下順序讓你拯救程序。
Method Resolution
首先Objective-C在運行時調用+ resolveInstanceMethod:或+ resolveClassMethod:方法,讓你添加方法的實現。如果你添加方法並返回YES,那系統在運行時就會重新啓動一次消息發送的過程。
舉一個簡單例子,定義一個類Message,它主要定義一個方法sendMessage,下面就是它的設計與實現:
@interface Message : NSObject
- (void)sendMessage:(NSString *)word;
@end
[cpp] view plaincopy
@implementation Message
- (void)sendMessage:(NSString *)word
{
NSLog(@"normal way : send message = %@", word);
}
@end
如果我在viewDidLoad方法中創建Message對象並調用sendMessage方法:
- (void)viewDidLoad {
[super viewDidLoad];
Message *message = [Message new];
[message sendMessage:@"Sam Lau"];
}
控制檯會打印以下信息:
normal way : send message = Sam Lau
但現在我將原來sendMessage方法實現給註釋掉,覆蓋resolveInstanceMethod方法:
#pragma mark - Method Resolution
/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(sendMessage:)) {
class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {
NSLog(@"method resolution way : send message = %@", word);
}), "v@*");
}
return YES;
}
控制檯就會打印以下信息:
method resolution way : send message = Sam Lau
注意到上面代碼有這樣一個字符串”v@*,它表示方法的參數和返回值,詳情請參考Type Encodings。
如果resolveInstanceMethod方法返回NO,運行時就跳轉到下一步:消息轉發(Message Forwarding)。
Fast Forwarding
如果目標對象實現- forwardingTargetForSelector:方法,系統就會在運行時調用這個方法,只要這個方法返回的不是nil或self,也會重啓消息發送的過程,把這消息轉發給其他對象來處理。否則,就會繼續Normal Fowarding。
繼續上面Message類的例子,將sendMessage和resolveInstanceMethod方法註釋掉,然後添加forwardingTargetForSelector方法的實現:
#pragma mark - Fast Forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(sendMessage:)) {
return [MessageForwarding new];
}
return nil;
}
此時還缺一個轉發消息的類MessageForwarding,這個類的設計與實現如下:
@interface MessageForwarding : NSObject
- (void)sendMessage:(NSString *)word;
@end
[cpp] view plaincopy
@implementation MessageForwarding
- (void)sendMessage:(NSString *)word
{
NSLog(@"fast forwarding way : send message = %@", word);
}
@end
此時,控制檯會打印以下信息:
fast forwarding way : send message = Sam Lau
這裏叫Fast,是因爲這一步不會創建NSInvocation對象,但Normal Forwarding會創建它,所以相對於更快點。
Normal Forwarding
如果沒有使用Fast Forwarding來消息轉發,最後只有使用Normal Forwarding來進行消息轉發。它首先調用methodSignatureForSelector:方法來獲取函數的參數和返回值,如果返回爲nil,程序會Crash掉,並拋出unrecognized selector sent to instance異常信息。如果返回一個函數簽名,系統就會創建一個NSInvocation對象並調用-forwardInvocation:方法。
繼續前面的例子,將forwardingTargetForSelector方法註釋掉,添加methodSignatureForSelector和forwardInvocation方法的實現:
#pragma mark - Normal Forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding = [MessageForwarding new];
if ([messageForwarding respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding];
}
}
三種方法的選擇
Runtime提供三種方式來將原來的方法實現代替掉,那該怎樣選擇它們呢?
Method Resolution:由於Method Resolution不能像消息轉發那樣可以交給其他對象來處理,所以只適用於在原來的類中代替掉。
Fast Forwarding:它可以將消息處理轉發給其他對象,使用範圍更廣,不只是限於原來的對象。
Normal Forwarding:它跟Fast Forwarding一樣可以消息轉發,但它能通過NSInvocation對象獲取更多消息發送的信息,例如:target、selector、arguments和返回值等信息。
Associated Objects
當使用Category對某個類進行擴展時,有時需要存儲屬性,Category是不支持的,這時需要使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個API來向對象添加、獲取和刪除關聯值:
void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
id objc_getAssociatedObject (id object, const void *key )
void objc_removeAssociatedObjects (id object )
其中objc_AssociationPolicy是個枚舉類型,它可以指定Objc內存管理的引用計數機制。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
個關於NSObject+AssociatedObject Category添加屬性associatedObject的示例代碼:
NSObject+AssociatedObject@interface NSObject (AssociatedObject)
@property (strong, nonatomic) id associatedObject;
@end
NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject
{
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject
{
return objc_getAssociatedObject(self, _cmd);
}
@end
Associated Objects的key要求是唯一併且是常量,而SEL是滿足這個要求的,所以上面的採用隱藏參數_cmd作爲key。