Object From runtime

七夕快到了

七夕

Objective-C 對象

每個Objective-C對象都是一個C語言的結構體

在runtime源碼中的runtime.h文件的55行處,有這樣一個聲明:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

### } OBJC2_UNAVAILABLE;
名詞 說明
isa isa指針
super_class 父類
objc_ivar_list 實例變量列表
objc_method_list 方法列表
objc_cache 緩存
objc_protocol_list 協(xié)議列表

注意:方法.屬性.協(xié)議,這些信息都可以在運行時候被改變,這也是 Category的實現(xiàn)遠離,ivar是實例變量,所以不能被改變,因為如果改變的話會影響到已有的類

筆者以前不了解 ,category開發(fā)是什么意思,現(xiàn)在看來可能是不影響原來的類結構的情況下進行編程吧!

說到這里 , 可能就要說一下類的實例成員和屬性了

變量和屬性(property)

@interface MyViewController :UIViewController
{
    //實例變量
    UIButton *Button;
}
// 屬性 (而且默認還會生成 `_myButton` 實例變量)
@property (nonatomic, retain) UIButton *myButton;
@end

實例變量 self->Button _myButton

屬性 myButton

調用屬性變量的setter getter 方法: self.myButton

注:oc語法關于點表達式的說明:"點表達式(.),如果點表達式出現(xiàn)在等號 = 左邊,該屬性名稱的setter方法將被調用。如果點表達式出現(xiàn)在右邊,該屬性名稱的getter方法將被調用。"

變量

在runtime源碼中的runtime.h文件的1646行處,有這樣一個聲明:

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
}  
名詞 說明
objc_ivar_list 成員變量列表
objc_ivar 單個成員變量結構體
ivar_offset 基地址偏移字節(jié)

屬性

在runtime源碼中的 objc-runtime-new.h 文件的244行處,有這樣一個聲明:

struct property_t {
    const char *name;
    const char *attributes;
};
名詞 說明
name 屬性名稱
attributes 屬性特質

如果使用了屬性,編譯器會在編譯期自動合成訪問這些屬性的方法,也就是autosynthesis .同時編譯器還會生成屬性前面加(_)下劃線的實例變量名

方法 說明 eg.
@synthesize 指定實例變量,并且合成setter,getter方法 @synthesize myButton;
@dynamic 不指定實例變量,不合成setter,getter方法 @dynamic myButton;

屬性特質(attribute)

屬性的特質可以分為4類

原子性 內存管理 讀/寫權限 方法名
atomic assign readwriter getter=< name >
nonatomic strong readonly setter=< name >
weak
unsafe_unretained
copy

每種特質說明:

特質 說明
atomic 如果有多個線程同時調用setter的話,不會出現(xiàn)某一個線程執(zhí)行完setter全部語句之前,另一個線程開始執(zhí)行setter情況,相當于函數(shù)頭尾加了鎖一樣,可以保證數(shù)據(jù)的完整性,具備atomic特質的獲取方法會通過鎖定機制來確保其操作的原子性.
nonatmic 禁止多線程,變量保護,提高性能
readwrite 擁有setter 和 getter 方法
readonly 屬性僅具有getter方法,只有當該屬性有@synthesize實現(xiàn)時,編譯器才會為其合成獲取方法.
assign 基本變量,純量類型的簡單賦值
strong 賦新值時,方法會保留新值,釋放舊值,再將新值賦值.
weak 賦新值時,不保留新值,不釋放舊值,當其所指對象銷毀時,屬性也會被清空(nil)
unsafe_unretained 當所指對象銷毀,屬性值不會自動清空
copy 賦值時,設置方法不保留新值,而是拷貝內容.
getter=< name > 指定getter的方法名
setter=< name > 指定setter的方法名(若屬性特征為copy,則在setter方法中也應拷貝對象)
利用runtime 掃描屬性

在runtime源碼中的 objc-runtime-new.h 文件的4098行處,有這樣一個聲明:

objc_property_t *
class_copyPropertyList(Class cls, unsigned int *outCount)
{
    if (!cls) {
        if (outCount) *outCount = 0;
        return nil;
    }

    rwlock_reader_t lock(runtimeLock);

    assert(cls->isRealized());
    auto rw = cls->data();

    property_t **result = nil;
    unsigned int count = rw->properties.count();
    if (count > 0) {
        result = (property_t **)malloc((count + 1) * sizeof(property_t *));

        count = 0;
        for (auto& prop : rw->properties) {
            result[count++] = ∝
        }
        result[count] = nil;
    }

    if (outCount) *outCount = count;
    return (objc_property_t *)result;
}

在runtime源碼中的 objc-runtime-new.h 文件的3061行處,有這樣一個聲明:

const char *property_getName(objc_property_t prop)
{
    return prop->name;
}

const char *property_getAttributes(objc_property_t prop)
{
    return prop->attributes;
}

可以用這些方法獲得屬性列表,和屬性特征

Student.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Student : NSObject
@property (nonatomic, copy) NSString *Id;
@property (nonatomic, strong) UILabel *lab;
@end

導入 <objc/runtime.h> Student.h

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([Student class],&outCount);
for (NSInteger i = 0; i < outCount; i++) {
    NSString *name = @(property_getName(properties[i]));
    NSString *attributes = @(property_getAttributes(properties[i]));
    NSLog(@"(%@)%@",attributes,name);
}
free(properties);

控制臺輸出:

2016-08-06 21:32:33.099 Runtime[3011:300142] (T@"NSString",C,N,V_Id)Id
2016-08-06 21:32:33.099 Runtime[3011:300142] (T@"UILabel",&,N,V_lab)lab
利用runtime動態(tài)添加屬性

在runtime源碼中的 objc-runtime.mm 文件的641行處,有這樣一個聲明:

id 
objc_getAssociatedObject(id object, const void *key) 
{
    return objc_getAssociatedObject_non_gc(object, key);
}

void 
objc_setAssociatedObject(id object, const void *key, id value, 
                         objc_AssociationPolicy policy) 
{
    objc_setAssociatedObject_non_gc(object, key, value, policy);
}

Student.m

// 定義關聯(lián)的key
static const char *key = "name";

@implementation Student

- (NSString *)name
{
    // 根據(jù)關聯(lián)的key,獲取關聯(lián)的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // 添加關聯(lián)對象  關聯(lián)的key  關聯(lián)的value  關聯(lián)屬性特征值
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

Student.h

@interface Student : NSObject
- (NSString *)name;
- (void)setName:(NSString *)name;

調用

Student *stu = [[Student alloc] init];
stu.name = @"學生";
NSLog(@"%@",stu.name);

輸出臺輸出:

2016-08-06 23:15:06.491 Runtime[3256:320704] 學生

方法

在runtime源碼中的runtime.h文件的1665行處,有這樣一個聲明:

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

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;
}  
方法 說明
method_name 方法(選擇器)的名字
method_types 存儲方法的參數(shù)類型和返回值類型
method_imp 一個指向某個函數(shù)的指針

利用runtime動態(tài)掃描方法

unsigned int outCount = 0;
Method *method = class_copyMethodList([Student class], &outCount);
for (NSInteger i = 0; i < outCount; i++) {
    SEL sel = method_getName(method[i]);
    NSString *name = @(sel_getName(sel));
    NSLog(@"%@",name);
        
}
free(method);

輸出臺輸出:

2016-08-07 07:04:36.662 Runtime[4058:381012] Id
2016-08-07 07:04:36.662 Runtime[4058:381012] setId:
2016-08-07 07:04:36.662 Runtime[4058:381012] lab
2016-08-07 07:04:36.662 Runtime[4058:381012] setLab:
2016-08-07 07:04:36.662 Runtime[4058:381012] .cxx_destruct
2016-08-07 07:04:36.662 Runtime[4058:381012] name
2016-08-07 07:04:36.662 Runtime[4058:381012] setName:

.cxx_destruct方法原本是為了C++對象析構的,ARC借用了這個方法插入代碼實現(xiàn)了自動內存釋放的工作

消息機制

objc_msgSend 消息發(fā)送的步驟:

  1. 檢查當前類緩存中是否有方法實現(xiàn),有則直接調用 return 結束步驟
  2. 比較當前類定義中選擇器和請求的的選擇器,有則直接調用 return 結束步驟
  3. 查找父類定義的方法,找到調用方法實現(xiàn),如果未找到,并依次查找父類的父類, 有則直接調用 return 結束步驟
  4. 未找到的類方法調用 +(BOOL)resolveClassMethod:(SEL)sel 未找到的實例方法調用 +(BOOL)resolveInstanceMethod:(SEL)sel 可以在這里添加方法(class_addMethod()) 并且返回YES 重新響應方法 (沒有的話,返回NO 繼續(xù)步驟5)
  5. 如果未找得到方法 則會調用 -(id)forwardingTargetForSelector:(SEL)aSelector (沒有的話,返回nil 繼續(xù)步驟6)
  6. 如果未找得到方法 則會調用 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 返回一個NSMethodSignature 對象,傳遞給 -(void)forwardInvocation:(NSInvocation *)anInvocation (沒有的話,返回nil 繼續(xù)步驟7)
  7. 調用 -(void)doesNotRecognizeSelector:(SEL)aSelector 拋出異常

注: 返回方法不要帶有self ,會陷入死循環(huán)
注: 類緩存方法說明詳見查看

動態(tài)消息

說到消息,就不得不說一個方法

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

這是利用runtime給一個類添加新方法,需要填寫的4個參數(shù)是:

  1. 方法名
  2. 指向方法函數(shù)的指針
  3. 變量類型

動態(tài)添加方法

Student.m

// 設置方法名
static SEL selName(NSString* selname){
    NSString *name = [selname copy];
    return NSSelectorFromString(name);
}
// 設置方法
static id selIMP(id self , SEL _cmd){
    NSString *name = NSStringFromSelector(_cmd);
    NSLog(@"%@",name);
    return name;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    
    if (sel == @selector(addmethod:)) {
        class_addMethod(self, selName(@"addmethod:"), (IMP)selIMP, "@@:");
    }
    return [super resolveInstanceMethod:sel];
}

注: class_addMethod 第四個參數(shù)的聲明 "返回值類型+參數(shù)類型"
上面方法中: static id selIMP(id self , SEL _cmd)

對照下面

Objective-C類型編碼

編碼 含義
c char
i int
s short
l long 在64位程序中,l為32位
q long long
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B C++標準的bool或者C99標準的_Bool
v void
* 字符串(char *)
@ 對象(無論是靜態(tài)指定的還是通過id引用的)
# 類(Class)
: 方法選標(SEL)
[array type] 數(shù)組
{name=type...} 結構體
(name=type...) 聯(lián)合體
bnum num個bit的位域
^type type類型的指針
? 未知類型(其它時候,一般用來指函數(shù)指針)

對應:@(返回值)+@(id self)+ : ( SEL _cmd)

動態(tài)調用方法

方法已經(jīng)在檢測不到的情況下會自動添加了,那么如何調用呢?

Student*st = [[Student alloc]init];
[st performSelector:@selector(addmethod:) withObject:@"addmethod"];

輸出臺輸出:

2016-08-07 12:07:29.940 Runtime[4731:521045] addmethod:

將消息轉出某對象

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));

    Student *st = [[Student alloc] init];
    if ([st respondsToSelector: aSelector]) {
        return st;
    }
    
    return [super forwardingTargetForSelector: aSelector];
}

方法簽名和調用

NSInvocation 里面有目標,選擇器,還有方法簽名

receiver 目標是接收消息的對象

selector 選擇器是一個選擇器或者選擇器的名字

例如剛才的 @selector(addmethod:)

NSMethodSignature 簽名

例如:這是 Student*st = [[Student alloc]init]; 的簽名,返回類型與上文中Objective-C類型編碼相同

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:"];

注:@selector(init) 一個返回對象+傳入對象self+SEL = @@:

定義一個方法

-(int)a:(int)a andb:(int)b{
    return a+b;
}

NSInvocation 用指定對象調用方法

SEL myMethod = @selector(a:andb:);
//從類中請求實例方法簽名
NSMethodSignature * sig  = [[self class] instanceMethodSignatureForSelector:myMethod];
//從類中請求類方法簽名
//NSMethodSignature * sig  = [[self class] methodSignatureForSelector:myMethod];
NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:sig];
int a=11;
int b=22;
int c=00;
ViewController *selff = self;
[invocatin setArgument:&selff atIndex:0];
[invocatin setArgument:&myMethod atIndex:1];
[invocatin setArgument:&a atIndex:2];
[invocatin setArgument:&b atIndex:3];
[invocatin retainArguments];
[invocatin invoke];
//取這個返回值
[invocatin getReturnValue:&c];
NSLog(@"%d",c);

控制臺輸出:

2016-08-07 20:22:40.490 Runtime[6015:691660] 33

注 :如果還記的上文的 自定義的SEl static id selIMP(id self , SEL _cmd) 當時說第一個參數(shù)是id 第二個參數(shù)是方法,也就是IMP的指針原型是:

id (*IMP)(id,SEL,...)

IMP 是一個函數(shù)指針,這個被指向的函數(shù)包含一個接收消息的對象id(self 指針), 調用方法的選標 SEL (方法名),以及不定個數(shù)的方法參數(shù),并返回一個id

所以一般的NSInvocation的實例設置 receiver 和SEL的方式就是:

[invocatin setArgument:&selff atIndex:0];
[invocatin setArgument:&myMethod atIndex:1];

當簽名函數(shù)參數(shù)數(shù)量大于被調函數(shù)時,也是沒有問題.

其實 NSInvocation 和上文中的動態(tài)方法調用 [st performSelector:@selector(addmethod:) withObject:@"addmethod"]; 很像,但是perform相關的這些函數(shù),有一個局限性,其參數(shù)數(shù)量不能超過2個,NSInvocation 可以任意數(shù)量

black magic

還記得這個嗎?

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

第二個參數(shù)和第三個參數(shù)分別是方法名,和指向方法函數(shù)的指針

在runtime源碼中的 objc-runtime-new.mm 文件的2992行處,有這樣一個聲明:

void method_exchangeImplementations(Method m1, Method m2)
{
    if (!m1  ||  !m2) return;

    rwlock_writer_t lock(runtimeLock);

    if (ignoreSelector(m1->name)  ||  ignoreSelector(m2->name)) {
        // Ignored methods stay ignored. Now they're both ignored.
        m1->imp = (IMP)&_objc_ignored_method;
        m2->imp = (IMP)&_objc_ignored_method;
        return;
    }

    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;


    // RR/AWZ updates are slow because class is unknown
    // Cache updates are slow because class is unknown
    // fixme build list of classes whose Methods are known externally?

    flushCaches(nil);

    updateCustomRR_AWZ(nil, m1);
    updateCustomRR_AWZ(nil, m2);
}

那么就很好理解了,他是把方法的IMP交換了

聲明兩個方法

-(int)a:(int)a andb:(int)b{
    return a+b;
}
-(int)b:(int)b anda:(int)a{
    return b-a;
}

然后進行交換

Method Getter1 = class_getInstanceMethod([self class], @selector(a:andb:));
Method Getter2 = class_getInstanceMethod([self class], @selector(b:anda:));
method_exchangeImplementations(Getter1, Getter2); 
NSLog(@"%d", [self a:10 andb:20]);

控制臺輸出:

2016-08-07 21:05:33.675 Runtime[6202:709996] -10

runtime是OC最重要的也是最核心的語法,Objective-C runtime可以有效的幫助我們?yōu)槌绦蛟黾雍芏鄤討B(tài)的行為,這也是OC被稱為動態(tài)語言的原因!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容