七夕快到了

Objective-C 對(duì)象
每個(gè)Objective-C對(duì)象都是一個(gè)C語言的結(jié)構(gòu)體
在runtime源碼中的runtime.h文件的55行處,有這樣一個(gè)聲明:
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 | 實(shí)例變量列表 |
| objc_method_list | 方法列表 |
| objc_cache | 緩存 |
| objc_protocol_list | 協(xié)議列表 |
注意:方法.屬性.協(xié)議,這些信息都可以在運(yùn)行時(shí)候被改變,這也是 Category的實(shí)現(xiàn)遠(yuǎn)離,ivar是實(shí)例變量,所以不能被改變,因?yàn)槿绻淖兊脑挄?huì)影響到已有的類
筆者以前不了解 ,category開發(fā)是什么意思,現(xiàn)在看來可能是不影響原來的類結(jié)構(gòu)的情況下進(jìn)行編程吧!
說到這里 , 可能就要說一下類的實(shí)例成員和屬性了
變量和屬性(property)
@interface MyViewController :UIViewController
{
//實(shí)例變量
UIButton *Button;
}
// 屬性 (而且默認(rèn)還會(huì)生成 `_myButton` 實(shí)例變量)
@property (nonatomic, retain) UIButton *myButton;
@end
實(shí)例變量 self->Button _myButton
屬性 myButton
調(diào)用屬性變量的setter getter 方法: self.myButton
注:oc語法關(guān)于點(diǎn)表達(dá)式的說明:"點(diǎn)表達(dá)式(.),如果點(diǎn)表達(dá)式出現(xiàn)在等號(hào) = 左邊,該屬性名稱的setter方法將被調(diào)用。如果點(diǎn)表達(dá)式出現(xiàn)在右邊,該屬性名稱的getter方法將被調(diào)用。"
變量
在runtime源碼中的runtime.h文件的1646行處,有這樣一個(gè)聲明:
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 | 單個(gè)成員變量結(jié)構(gòu)體 |
| ivar_offset | 基地址偏移字節(jié) |
屬性
在runtime源碼中的 objc-runtime-new.h 文件的244行處,有這樣一個(gè)聲明:
struct property_t {
const char *name;
const char *attributes;
};
| 名詞 | 說明 |
|---|---|
| name | 屬性名稱 |
| attributes | 屬性特質(zhì) |
如果使用了屬性,編譯器會(huì)在編譯期自動(dòng)合成訪問這些屬性的方法,也就是autosynthesis .同時(shí)編譯器還會(huì)生成屬性前面加(_)下劃線的實(shí)例變量名
| 方法 | 說明 | eg. |
|---|---|---|
| @synthesize | 指定實(shí)例變量,并且合成setter,getter方法 | @synthesize myButton; |
| @dynamic | 不指定實(shí)例變量,不合成setter,getter方法 | @dynamic myButton; |
屬性特質(zhì)(attribute)
屬性的特質(zhì)可以分為4類
| 原子性 | 內(nèi)存管理 | 讀/寫權(quán)限 | 方法名 |
|---|---|---|---|
| atomic | assign | readwriter | getter=< name > |
| nonatomic | strong | readonly | setter=< name > |
| weak | |||
| unsafe_unretained | |||
| copy |
每種特質(zhì)說明:
| 特質(zhì) | 說明 |
|---|---|
| atomic | 如果有多個(gè)線程同時(shí)調(diào)用setter的話,不會(huì)出現(xiàn)某一個(gè)線程執(zhí)行完setter全部語句之前,另一個(gè)線程開始執(zhí)行setter情況,相當(dāng)于函數(shù)頭尾加了鎖一樣,可以保證數(shù)據(jù)的完整性,具備atomic特質(zhì)的獲取方法會(huì)通過鎖定機(jī)制來確保其操作的原子性. |
| nonatmic | 禁止多線程,變量保護(hù),提高性能 |
| readwrite | 擁有setter 和 getter 方法 |
| readonly | 屬性僅具有g(shù)etter方法,只有當(dāng)該屬性有@synthesize實(shí)現(xiàn)時(shí),編譯器才會(huì)為其合成獲取方法. |
| assign | 基本變量,純量類型的簡(jiǎn)單賦值 |
| strong | 賦新值時(shí),方法會(huì)保留新值,釋放舊值,再將新值賦值. |
| weak | 賦新值時(shí),不保留新值,不釋放舊值,當(dāng)其所指對(duì)象銷毀時(shí),屬性也會(huì)被清空(nil) |
| unsafe_unretained | 當(dāng)所指對(duì)象銷毀,屬性值不會(huì)自動(dòng)清空 |
| copy | 賦值時(shí),設(shè)置方法不保留新值,而是拷貝內(nèi)容. |
| getter=< name > | 指定getter的方法名 |
| setter=< name > | 指定setter的方法名(若屬性特征為copy,則在setter方法中也應(yīng)拷貝對(duì)象) |
利用runtime 掃描屬性
在runtime源碼中的 objc-runtime-new.h 文件的4098行處,有這樣一個(gè)聲明:
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行處,有這樣一個(gè)聲明:
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
導(dǎo)入 <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);
控制臺(tái)輸出:
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動(dòng)態(tài)添加屬性
在runtime源碼中的 objc-runtime.mm 文件的641行處,有這樣一個(gè)聲明:
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
// 定義關(guān)聯(lián)的key
static const char *key = "name";
@implementation Student
- (NSString *)name
{
// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 添加關(guān)聯(lián)對(duì)象 關(guān)聯(lián)的key 關(guān)聯(lián)的value 關(guān)聯(lián)屬性特征值
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
Student.h
@interface Student : NSObject
- (NSString *)name;
- (void)setName:(NSString *)name;
調(diào)用
Student *stu = [[Student alloc] init];
stu.name = @"學(xué)生";
NSLog(@"%@",stu.name);
輸出臺(tái)輸出:
2016-08-06 23:15:06.491 Runtime[3256:320704] 學(xué)生
方法
在runtime源碼中的runtime.h文件的1665行處,有這樣一個(gè)聲明:
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 | 存儲(chǔ)方法的參數(shù)類型和返回值類型 |
| method_imp | 一個(gè)指向某個(gè)函數(shù)的指針 |
利用runtime動(dòng)態(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);
輸出臺(tái)輸出:
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++對(duì)象析構(gòu)的,ARC借用了這個(gè)方法插入代碼實(shí)現(xiàn)了自動(dòng)內(nèi)存釋放的工作
消息機(jī)制
objc_msgSend 消息發(fā)送的步驟:
- 檢查當(dāng)前類緩存中是否有方法實(shí)現(xiàn),有則直接調(diào)用 return 結(jié)束步驟
- 比較當(dāng)前類定義中選擇器和請(qǐng)求的的選擇器,有則直接調(diào)用 return 結(jié)束步驟
- 查找父類定義的方法,找到調(diào)用方法實(shí)現(xiàn),如果未找到,并依次查找父類的父類, 有則直接調(diào)用 return 結(jié)束步驟
- 未找到的類方法調(diào)用
+(BOOL)resolveClassMethod:(SEL)sel未找到的實(shí)例方法調(diào)用+(BOOL)resolveInstanceMethod:(SEL)sel可以在這里添加方法(class_addMethod()) 并且返回YES 重新響應(yīng)方法 (沒有的話,返回NO 繼續(xù)步驟5) - 如果未找得到方法 則會(huì)調(diào)用
-(id)forwardingTargetForSelector:(SEL)aSelector(沒有的話,返回nil 繼續(xù)步驟6) - 如果未找得到方法 則會(huì)調(diào)用
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回一個(gè)NSMethodSignature對(duì)象,傳遞給-(void)forwardInvocation:(NSInvocation *)anInvocation(沒有的話,返回nil 繼續(xù)步驟7) - 調(diào)用
-(void)doesNotRecognizeSelector:(SEL)aSelector拋出異常
注: 返回方法不要帶有self ,會(huì)陷入死循環(huán)
注: 類緩存方法說明詳見查看
動(dòng)態(tài)消息
說到消息,就不得不說一個(gè)方法
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
這是利用runtime給一個(gè)類添加新方法,需要填寫的4個(gè)參數(shù)是:
- 類
- 方法名
- 指向方法函數(shù)的指針
- 變量類型
動(dòng)態(tài)添加方法
Student.m
// 設(shè)置方法名
static SEL selName(NSString* selname){
NSString *name = [selname copy];
return NSSelectorFromString(name);
}
// 設(shè)置方法
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 第四個(gè)參數(shù)的聲明 "返回值類型+參數(shù)類型"
上面方法中: static id selIMP(id self , SEL _cmd)
對(duì)照下面
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++標(biāo)準(zhǔn)的bool或者C99標(biāo)準(zhǔn)的_Bool |
| v | void |
| * | 字符串(char *) |
| @ | 對(duì)象(無論是靜態(tài)指定的還是通過id引用的) |
| # | 類(Class) |
| : | 方法選標(biāo)(SEL) |
| [array type] | 數(shù)組 |
| {name=type...} | 結(jié)構(gòu)體 |
| (name=type...) | 聯(lián)合體 |
| bnum | num個(gè)bit的位域 |
| ^type | type類型的指針 |
| ? | 未知類型(其它時(shí)候,一般用來指函數(shù)指針) |
對(duì)應(yīng):@(返回值)+@(id self)+ : ( SEL _cmd)
動(dòng)態(tài)調(diào)用方法
方法已經(jīng)在檢測(cè)不到的情況下會(huì)自動(dòng)添加了,那么如何調(diào)用呢?
Student*st = [[Student alloc]init];
[st performSelector:@selector(addmethod:) withObject:@"addmethod"];
輸出臺(tái)輸出:
2016-08-07 12:07:29.940 Runtime[4731:521045] addmethod:
將消息轉(zhuǎn)出某對(duì)象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));
Student *st = [[Student alloc] init];
if ([st respondsToSelector: aSelector]) {
return st;
}
return [super forwardingTargetForSelector: aSelector];
}
方法簽名和調(diào)用
NSInvocation 里面有目標(biāo),選擇器,還有方法簽名
receiver 目標(biāo)是接收消息的對(duì)象
selector 選擇器是一個(gè)選擇器或者選擇器的名字
例如剛才的 @selector(addmethod:)
NSMethodSignature 簽名
例如:這是 Student*st = [[Student alloc]init]; 的簽名,返回類型與上文中Objective-C類型編碼相同
NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:"];
注:@selector(init) 一個(gè)返回對(duì)象+傳入對(duì)象self+SEL = @@:
定義一個(gè)方法
-(int)a:(int)a andb:(int)b{
return a+b;
}
NSInvocation 用指定對(duì)象調(diào)用方法
SEL myMethod = @selector(a:andb:);
//從類中請(qǐng)求實(shí)例方法簽名
NSMethodSignature * sig = [[self class] instanceMethodSignatureForSelector:myMethod];
//從類中請(qǐng)求類方法簽名
//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];
//取這個(gè)返回值
[invocatin getReturnValue:&c];
NSLog(@"%d",c);
控制臺(tái)輸出:
2016-08-07 20:22:40.490 Runtime[6015:691660] 33
注 :如果還記的上文的 自定義的SEl static id selIMP(id self , SEL _cmd) 當(dāng)時(shí)說第一個(gè)參數(shù)是id 第二個(gè)參數(shù)是方法,也就是IMP的指針原型是:
id (*IMP)(id,SEL,...)
IMP 是一個(gè)函數(shù)指針,這個(gè)被指向的函數(shù)包含一個(gè)接收消息的對(duì)象id(self 指針), 調(diào)用方法的選標(biāo) SEL (方法名),以及不定個(gè)數(shù)的方法參數(shù),并返回一個(gè)id
所以一般的NSInvocation的實(shí)例設(shè)置 receiver 和SEL的方式就是:
[invocatin setArgument:&selff atIndex:0];
[invocatin setArgument:&myMethod atIndex:1];
當(dāng)簽名函數(shù)參數(shù)數(shù)量大于被調(diào)函數(shù)時(shí),也是沒有問題.
其實(shí) NSInvocation 和上文中的動(dòng)態(tài)方法調(diào)用 [st performSelector:@selector(addmethod:) withObject:@"addmethod"]; 很像,但是perform相關(guān)的這些函數(shù),有一個(gè)局限性,其參數(shù)數(shù)量不能超過2個(gè),NSInvocation 可以任意數(shù)量
black magic
還記得這個(gè)嗎?
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
第二個(gè)參數(shù)和第三個(gè)參數(shù)分別是方法名,和指向方法函數(shù)的指針
在runtime源碼中的 objc-runtime-new.mm 文件的2992行處,有這樣一個(gè)聲明:
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交換了
聲明兩個(gè)方法
-(int)a:(int)a andb:(int)b{
return a+b;
}
-(int)b:(int)b anda:(int)a{
return b-a;
}
然后進(jìn)行交換
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]);
控制臺(tái)輸出:
2016-08-07 21:05:33.675 Runtime[6202:709996] -10
runtime是OC最重要的也是最核心的語法,Objective-C runtime可以有效的幫助我們?yōu)槌绦蛟黾雍芏鄤?dòng)態(tài)的行為,這也是OC被稱為動(dòng)態(tài)語言的原因!!