前言
如果你在瀏覽器中搜索Runtime
會出現(xiàn)多如牛毛文章在介紹它眷射,但是大多數(shù)文章都是講的Runtime
有多少用法云云...,也就是說單單是告訴你如何調(diào)用API
,能達到什么效果漾肮。很多大神都會告訴你iOS
進階,Runtime
學(xué)習(xí)是必備的,但是我們進階就是為了知道如何調(diào)用API
嗎?當(dāng)然不是,我們知道OC是一門面向?qū)ο缶幊痰恼Z言灼擂,無時無刻不在和Class(類)
,Object(實例對象)
打交道觉至,然而類中又包含了Ivar(成員變量)
,Method(方法)
,Protocol(協(xié)議)
,所以這遍文章就要來揭一揭Runtime
與它們之間的面紗剔应。
本篇文章目錄
一. Runtime簡介
二. Class/Category/Extension(類/分類/擴展)
2.1、 Class的結(jié)構(gòu)
2.2语御、Runtime中對類Class的操作
三. Object (對象)是如何初始化的峻贮?
3.1 + alloc方法分析
3.2 - init方法分析
3.3 對象操作相關(guān)的Runtime API
四. Ivar/Property(成員變量/屬性)
4.1 property組成
4.2@synthesize和@dynamic的作用
4.3@synthesize的使用場景
4.4@property中有哪些屬性關(guān)鍵字
4.5 Ivar/Property在runtime中的一些應(yīng)用
4.6 Category中使用Property
五. Method/SEL/IMP (方法/方法名稱/方法實現(xiàn))
5.1 Method結(jié)構(gòu)
5.2 消息(Message)的發(fā)送
5.3 消息(Message)的轉(zhuǎn)發(fā)
5.4 Runtime相關(guān)的操作
六. Protocol(協(xié)議)
6.1 協(xié)議是什么?
6.2 如何寫一個協(xié)議
6.3 協(xié)議中的方法由誰來實現(xiàn)
6.4協(xié)議的作用
6.5 Runtime中關(guān)于Protocol的API
一.Runtime簡介
Objective-C 是基于 C 的沃暗,它為 C 添加了面向?qū)ο蟮奶匦栽侣濉K鼘⒑芏囔o態(tài)語言在編譯和鏈接時期做的事放到了 runtime 運行時來處理何恶,可以說 runtime 是我們 Objective-C 幕后工作者孽锥。(Objective-c面向?qū)ο? 在運行時處理其他語言編譯和鏈接時期做的事),OC中對象的類型和對象所執(zhí)行的方法都是在運行時階段進行查找并確認的细层,這種機制被稱為動態(tài)綁定惜辑。
二.Class介紹
本文過長,關(guān)于Category/Extension
的介紹放在深入理解Objective-C:Category
2.1Class
類的結(jié)構(gòu)
我們都知道OC中NSObject是大部分類的基類疫赎,而在NSObject頭文件中的結(jié)構(gòu)如下:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
每一個繼承自NSObject
的類都包含Class isa
的成員變量盛撑,這個isa
的類為Class
的結(jié)構(gòu)如下:
typedef struct objc_class *Class;
Class
是一個objc_class
的結(jié)構(gòu)體:
在Objc2.0
之前objc_class
結(jié)構(gòu)
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;
Objc2.0
之后objc_class
結(jié)構(gòu):
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
Objc2.0
之后objc_class
的結(jié)構(gòu)體繼承自objc_object
,事實上Object(實例對象)
比如:Student *Bob
Bob這個學(xué)生對象就是一個objc_object
結(jié)構(gòu)體,而我們常用的id
對象也是objc_object
結(jié)構(gòu)體
typedef struct objc_object *id;
那么我們可以理解為我們所說的類就是objc_class
結(jié)構(gòu)體捧搞,實例對象就是objc_object
結(jié)構(gòu)體抵卫,而objc_class
繼承自objc_object
,那么類也是一個對象胎撇。
objc_class
包含以下結(jié)構(gòu):
- 1.
isa_t isa
繼承自objc_object
- 2.
Class superclass
- 3.
cache_t cache
- 4.
class_data_bits_t bits
1.isa_t isa
指向元類的指針
2.Class superclass
指向當(dāng)前類的父類
對應(yīng)關(guān)系的圖如下圖介粘,下圖很好的描述了對象,類晚树,元類之間的關(guān)系:
圖中實線是 super_class
指針姻采,虛線是isa
指針。
對象的 isa
指向當(dāng)前類爵憎,類中存儲了對象的所有實例方法慨亲,成員變量等,類的 isa
指向meta-class(元類)
,meta-class
存儲著一個類的所有類方法宝鼓。Meta class
的isa
指針都指向Root class (meta)
即NSObject
的Meta class
元類,Root class (meta)
的 isa
指向了自己刑棵。
對象的實例方法調(diào)用時,通過對象的
isa
找到對應(yīng)的類愚铡,再從類的class_data_bits_t bits
中查找對應(yīng)的方法蛉签。
類對象的類方法調(diào)用時,通過類的isa
找到對應(yīng)的元類,再從元類的class_data_bits_t bits
中查找對應(yīng)的方法正蛙。
Root class (class)
其實就是NSObject
督弓,NSObject
是沒有超類的,所以Root class(class)
的superclass
指向nil
乒验。
Root class(meta)
的superclass
指向Root class(class)
愚隧,也就是NSObject
,形成一個回路锻全。
從 NSObject 的初始化了解 isa可以讓你深入了解isa
3.cache_t cache
緩存經(jīng)常調(diào)用的方法狂塘,當(dāng)調(diào)用方法時,優(yōu)先在Cache查找鳄厌,如果沒有找到荞胡,再到methodLists查找
4.class_data_bits_t bits
存儲類的方法、屬性和遵循的協(xié)議等信息的地方
深入解析 ObjC 中方法的結(jié)構(gòu)深入了解class_data_bits_t
以上就是關(guān)于類Class
的結(jié)構(gòu)介紹了嚎,接下來說一說在Runtime
中對類的一些操作泪漂。
2.2Runtime
中對類Class
的操作
runtime
中類的操作方法大部分是以class_
為前綴,比如
class_getProperty
,class_getClassMethod
等。
類名Class Name
// 獲取類名
const char *class_getName(Class cls)
父類super_class
// 獲取類的父類
Class class_getSuperclass ( Class cls );
元類meta_class
// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );
成員變量Ivar
// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
1.class_copyIvarList
可以獲得成員變量數(shù)組歪泳,數(shù)組中的成員變量信息都是objc_ivar
結(jié)構(gòu)體的指針萝勤。outCount
指針返回數(shù)組的大小。在結(jié)束時須使用free()
來釋放數(shù)組呐伞。
2.class_addIvar
需要注意的是OC不支持向已存在的類中添加Ivar
實例變量敌卓,因此除非通過運行時來創(chuàng)建的類,其他類中都不能添加Ivar
伶氢,在運行時添加Ivar
趟径,class_addIvar
方法只能在objc_allocateClassPair
函數(shù)與objc_registerClassPair
之間調(diào)用。
舉個栗子??:
unsigned int outCount;
Ivar * ivarList = class_copyIvarList([UIView class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar));
NSLog(@"%@",[NSString stringWithUTF8String:ivar_getName(ivar)]);
}
free(ivarList);
2017-07-19 16:07:06.385 Runtime[3016:281958] _constraintsExceptingSubviewAutoresizingConstraints
2017-07-19 16:07:06.385 Runtime[3016:281958] _cachedTraitCollection
2017-07-19 16:07:06.386 Runtime[3016:281958] _layer
2017-07-19 16:07:06.386 Runtime[3016:281958] _layerRetained
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureInfo
2017-07-19 16:07:06.386 Runtime[3016:281958] _gestureRecognizers
2017-07-19 16:07:06.387 Runtime[3016:281958] _window
2017-07-19 16:07:06.387 Runtime[3016:281958] _subviewCache
2017-07-19 16:07:06.387 Runtime[3016:281958] _templateLayoutView
2017-07-19 16:07:06.387 Runtime[3016:281958] _charge
2017-07-19 16:07:06.387 Runtime[3016:281958] _tag
2017-07-19 16:07:06.388 Runtime[3016:281958] _viewDelegate
.........
屬性Property
// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// 為類添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 替換類的屬性
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
方法Method
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
class_addMethod
的實現(xiàn)會覆蓋父類的方法實現(xiàn)癣防,但不會取代本類中已存在的實現(xiàn)蜗巧,如果本類中包含一個同名的實現(xiàn),則函數(shù)會返回NO劣砍。如果要修改已存在實現(xiàn)惧蛹,可以使用class_replaceMethod
或method_setImplementation
。
舉兩個栗子????:
Method *methodList = class_copyMethodList([UIView class], &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methodList[i];
NSLog(@"%@",NSStringFromSelector( method_getName(method)));
}
free(methodList);
2017-07-19 16:07:06.831 Runtime[3016:281958] convertPoint:fromView:
2017-07-19 16:07:06.831 Runtime[3016:281958] subviews
2017-07-19 16:07:06.832 Runtime[3016:281958] setOpaque:
2017-07-19 16:07:06.832 Runtime[3016:281958] addGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] removeGestureRecognizer:
2017-07-19 16:07:06.832 Runtime[3016:281958] addSubview:
2017-07-19 16:07:06.833 Runtime[3016:281958] sizeThatFits:
2017-07-19 16:07:06.833 Runtime[3016:281958] nextResponder
2017-07-19 16:07:06.833 Runtime[3016:281958] becomeFirstResponder
2017-07-19 16:07:06.834 Runtime[3016:281958] convertRect:fromView:
2017-07-19 16:07:06.834 Runtime[3016:281958] convertPoint:toView:
2017-07-19 16:07:06.835 Runtime[3016:281958] drawRect:
2017-07-19 16:07:06.835 Runtime[3016:281958] setFrameOrigin:
2017-07-19 16:07:06.835 Runtime[3016:281958] isHiddenOrHasHiddenAncestor
2017-07-19 16:07:06.836 Runtime[3016:281958] willRemoveSubview:
2017-07-19 16:07:06.836 Runtime[3016:281958] setAutoresizingMask:
2017-07-19 16:07:06.837 Runtime[3016:281958] charge
2017-07-19 16:07:06.837 Runtime[3016:281958] setCharge:
2017-07-19 16:07:06.838 Runtime[3016:281958] origin
2017-07-19 16:07:06.838 Runtime[3016:281958] setOrigin:
......
@interface Bob : NSObject
@end
@implementation Bob
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
// 將要添加method實例方法的SEL
SEL methodSEL = @selector(name);
// 根據(jù)SEL獲取Method
Method methodName = class_getInstanceMethod([self class], methodSEL);
// 根據(jù)SEL獲取方法(Method)的實現(xiàn)
IMP methodIMP = class_getMethodImplementation([self class], methodSEL);
// Method Encoding
const char *methodEncoding = method_getTypeEncoding(methodName);
// 1.當(dāng)類中沒有name方法時添加方法
BOOL isSuccess = class_addMethod([Bob class], methodSEL, methodIMP, methodEncoding);
// 2.當(dāng)類中有name方法時刑枝,使用class_replaceMethod
//IMP replacedMethodIMP = class_replaceMethod([Bob class], methodSEL, methodIMP, methodEncoding);
// 3.當(dāng)類中有name方法時香嗓,使用method_setImplementation
//Method BobMethodName = class_getInstanceMethod([Bob class], methodSEL);
//IMP BobMethodIMP = class_getMethodImplementation([Bob class], methodSEL);
method_setImplementation(BobMethodName, methodIMP);
if (isSuccess) {
[[Bob new] performSelector:@selector(name)];
}
}
- (void)name{
NSLog(@"My name is Bob");
}
@end
// 三種方法都可以實現(xiàn),打印如下
Runtime[2779:265550] My name is Bob
協(xié)議protocol
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
對象Object
創(chuàng)建
id class_createInstance(Class cls, size_t extraBytes)
把對象Object
創(chuàng)建放在最后是因為接下來要說Object
對象装畅,當(dāng)然對象的創(chuàng)建也是必須要說的靠娱。
三. Object 對象
關(guān)于對象在Class
中已經(jīng)基本介紹清楚了,我們常見的對象是一個objc_object
結(jié)構(gòu)體
struct objc_object {
private:
isa_t isa;
}
isa
指向?qū)ο?code>Object所屬的類Class
,實例對象Object
的方法掠兄,成員變量都保存在對象的類Class
中.
那么問題來了像云,對象的創(chuàng)建[[NSObject alloc] init]
是如何完成的呢锌雀?
3.1 + alloc
方法分析
+ alloc
的實現(xiàn)
+ (id)alloc {
return _objc_rootAlloc(self);
}
// alloc 方法的實現(xiàn)真的是非常的簡單, 它直接調(diào)用了另一個私有方法 id _objc_rootAlloc(Class cls)
id _objc_rootAlloc(Class cls) {
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
id obj = class_createInstance(cls, 0);
return obj;
}
id class_createInstance(Class cls, size_t extraBytes) {
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
// 對象初始化中最重要的操作都在 _class_createInstanceFromZone 方法中執(zhí)行:
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
size_t size = cls->instanceSize(extraBytes);
id obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
// 在使用 calloc 為對象分配一塊內(nèi)存空間之前,我們要先獲取對象在內(nèi)存的大醒肝堋:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
//實例大小 instanceSize 會存儲在類的 isa_t 結(jié)構(gòu)體中腋逆,然后經(jīng)過對齊最后返回,在獲取對象大小之后,直接調(diào)用 calloc 函數(shù)就可以為對象分配內(nèi)存空間了
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
isa
的初始化
// 在對象的初始化過程中除了使用 calloc 來分配內(nèi)存之外侈贷,還需要根據(jù)類初始化 isa_t 結(jié)構(gòu)體:
inline void objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) {
if (!indexed) {
isa.cls = cls;
} else {
isa.bits = ISA_MAGIC_VALUE;
isa.has_cxx_dtor = hasCxxDtor;
isa.shiftcls = (uintptr_t)cls >> 3;
}
}
3.2 - init
方法分析
// NSObject 的 - init 方法只是調(diào)用了 _objc_rootInit 并返回了當(dāng)前對象:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj) {
return obj;
}
總的來說惩歉,在 iOS 中一個對象的初始化過程只是分配內(nèi)存空間、然后初始化 isa_t
結(jié)構(gòu)體俏蛮。
3.3 對象操作相關(guān)的Runtime API
/************** object_ *************/
// 獲取對象的類名
const char * object_getClassName ( id obj );
// 拷貝一個對象并返回拷貝好的對象
id object_copy(id obj, size_t size)
// 釋放指定對象占用的內(nèi)存
id object_dispose(id obj)
// 獲取對象的類
Class object_getClass(id obj)
// 設(shè)置對象的類
Class object_setClass(id obj, Class cls)
// 判斷某個對象是不是一個類
BOOL object_isClass(id obj)
// 獲取指定對象撑蚌,指定實例變量的值(對象)
id object_getIvar(id obj, Ivar ivar)
// 設(shè)置對象中實例變量的值
void object_setIvar(id obj, Ivar ivar, id value)
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable(id obj, const char *name, void *value)
// 獲取對象實例變量的值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)
四. Ivar/Property
成員變量/屬性
4.1 property
組成
Ivar
成員變量與Property
屬性的關(guān)系如下:
@property = ivar + getter + setter
即屬性property
是由成員變量ivar
和getter 、setter
存取方法組成的搏屑。
Objective-C
對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量争涌。實例變量一般通過“存取方法”(access method
)來訪問。其中辣恋,“獲取方法” (getter
)用于讀取變量值亮垫,而“設(shè)置方法” (setter
)用于寫入變量值。這個概念已經(jīng)定型抑党,并且經(jīng)由“屬性”這一特性而成為Objective-C 2.0
的一部分包警。 而在正規(guī)的Objective-C
編碼風(fēng)格中,存取方法有著嚴(yán)格的命名規(guī)范底靠。 正因為有了這種嚴(yán)格的命名規(guī)范,所以 Objective-C
這門語言才能根據(jù)名稱自動創(chuàng)建出存取方法特铝。
property
通過自動合成(autosynthesis
)來生成ivar暑中、getter 、setter
并添加到類中鲫剿。
完成屬性定義后鳄逾,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”( autosynthesis
)灵莲。需要強調(diào)的是雕凹,這個過程由編譯 器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method
)的源代碼政冻。除了生成方法代碼 getter枚抵、setter
之外,編譯器還要自動向類中添加適當(dāng)類型的實例變量明场,并且在屬性名前面加下劃線汽摹,以此作為實例變量的名字。也可以在類的實現(xiàn)代碼里通過 @synthesize
語法來指定實例變量的名字苦锨。
@implementation Bob
@synthesize bookName = _BobBookName;
@synthesize age = _BobAge;
@end
4.2@synthesize
和@dynamic
的作用
1)@property
有兩個對應(yīng)的詞逼泣,一個是@synthesize
趴泌,一個是@dynamic
。如果@synthesize
和@dynamic
都沒寫拉庶,那么默認的就是@syntheszie var = _var;
2)@synthesize
的語義是如果你沒有手動實現(xiàn)setter
方法和getter
方法嗜憔,那么編譯器會自動為你加上這兩個方法。
3)@dynamic
告訴編譯器:屬性的setter
與getter
方法由用戶自己實現(xiàn)氏仗,不自動生成痹筛。(當(dāng)然對于readonly
的屬性只需提供getter
即可)。假如一個屬性被聲明為@dynamic var
廓鞠,然后你沒有提供@setter
方法和@getter
方法帚稠,編譯的時候沒問題,但是當(dāng)程序運行到instance.var = someVar
床佳,由于缺setter
方法會導(dǎo)致程序崩潰滋早;或者當(dāng)運行到 someVar = var
時,由于缺getter
方法同樣會導(dǎo)致崩潰砌们。編譯時沒問題杆麸,運行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定浪感。
4.3 有了autosynthesis
(自動合成)昔头,@synthesize
的使用場景
@synthesize
只有在不會自動合成的時候使用,以下情況@property
不會自動合成:
- 1.同時重寫了
setter
和getter
時 - 2.重寫了只讀屬性的
getter
時 - 3.使用了
@dynamic
時 - 4.在
@protocol
中定義的所有屬性 - 5.在
category
中定義的所有屬性 - 6.在子類中重載了父類中的屬性
除了后三條影兽,對其他幾個我們可以總結(jié)出一個規(guī)律:當(dāng)你想手動管理@property
的所有內(nèi)容時揭斧,你就會嘗試通過實現(xiàn)@property
的所有“存取方法”)或者使用@dynamic
來達到這個目的,這時編譯器就會認為你打算手動管理@property
峻堰,于是編譯器就禁用了autosynthesis
(自動合成)讹开。
因為有了autosynthesis
(自動合成),大部分開發(fā)者已經(jīng)習(xí)慣不去手動定義ivar捐名,而是依賴于autosynthesis
(自動合成)旦万,但是一旦你需要使用ivar
,而autosynthesis
(自動合成)又失效了镶蹋,如果不去手動定義ivar
成艘,那么你就得借助@synthesize
來手動合成ivar
。
4.4 @property
中有哪些屬性關(guān)鍵字
1.原子贺归、非原子性(nonatomic
淆两、 atomic
)
2.讀寫、只讀(readwrite
牧氮、readonly
)
3.內(nèi)存管理(assign琼腔、strong、 weak踱葛、unsafe_unretained丹莲、copy
)
4.方法名(@property (nonatomic, getter=isNewVersion) BOOL newVersion;
)
5.是否可以為空 (nonnull,null_resettable,nullable
)
在默認情況下光坝,由編譯器合成的方法會通過鎖定機制確保其原子性(atomic
), nonatomic
則不使用同步鎖,但是需要注意的是atomic
只能保證存數(shù)據(jù)的set
方法加鎖,并不能保證直接使用成員變量取值的安全性甥材。
copy
關(guān)鍵字使用在block盯另、NSString、NSArray洲赵、NSDictionary
等中鸳惯,block
中的copy
將block
由棧中拷貝到堆中,而NSString、NSArray叠萍、NSDictionary
中的copy
可以保證數(shù)據(jù)不被更改芝发,因為你不能保證傳入的是一個可變的數(shù)據(jù)(NSMutable...
)
weak
此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship
)。為這種屬性設(shè)置新值時苛谷,設(shè)置方法既不保留新值辅鲸,也不釋放舊值。此特質(zhì)同assign
類似腹殿, 然而在屬性所指的對象遭到摧毀時独悴,屬性值也會清空(nil out
)。 而 assign 的“設(shè)置方法”只會執(zhí)行針對“純量類型” (scalar type
锣尉,例如 CGFloat
或 NSlnteger
等)的簡單賦值操作刻炒。
assigin
可以用非OC
對象,而weak
必須用于OC
對象
4.5 Ivar/Property
在runtime
中的一些應(yīng)用
Ivar
的一些操作
// 獲取成員變量名
const char *ivar_getName(Ivar v)
// 獲取成員變量類型編碼
const char *ivar_getTypeEncoding(Ivar v)
// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset(Ivar v)
Property
的一些操作
// 獲取屬性名
const char *property_getName(objc_property_t property)
// 獲取屬性特性描述字符串
const char *property_getAttributes(objc_property_t property)
// 獲取屬性中指定的特性
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
// 獲取屬性的特性列表
char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
4.6 Category
中使用Property
我們都知道Category
使用 Property
不會將它添加到類中的,這些將會在深入理解Objective-C:Category中介紹自沧。我們可以通過屬性關(guān)聯(lián)的方式來使Category
可以使用Property
@interface Student (Name)
/* name */
@property (nonatomic, strong) NSString *name;
@end
static char nameKey;
#pragma mark - - 動態(tài)添加屬性
- (void)setName:(NSString *)name{
return objc_setAssociatedObject(self, & nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, & nameKey);
}
五.Method/SEL/IMP
(方法/方法名稱/方法實現(xiàn))
5.1 Method
結(jié)構(gòu)
方法分為2種坟奥,實例方法(Instance Method
)與類方法(Class Method
)
實例方法(Instance Method
)保存在對象(Instance
)的類(Class
)中
類方法(Class Method
)保存在元類(meta-class
)中
Class
結(jié)構(gòu):
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
}
class_data_bits_t bits
中保存了Ivar、Property暂幼、Method筏勒、Protocol
等
同樣Method
也是一個結(jié)構(gòu)體,它的結(jié)構(gòu)如下:
struct method_t {
SEL name;
const char *types;
IMP imp;
};
SEL name
方法名字 旺嬉,@selector()
用于表示運行時方 法的名字。Objective-C在編譯時厨埋,會依據(jù)每一個方法的名字邪媳、參數(shù)序列,生成一個唯一的整型標(biāo)識(Int類型的地址)荡陷,這個標(biāo)識就是SEL雨效。本質(zhì)上,SEL只是一個指向方法的指針(準(zhǔn)確的說废赞,只是一個根據(jù)方法名hash化了的KEY值徽龟,能唯一代表一個方法),它的存在只是為了加快方法的查詢速度唉地。
const char *types
類型編碼据悔,你可以通過官方文檔來了解它
IMP imp
方法的實現(xiàn)传透,IMP
是一個函數(shù)指針,指向方法實現(xiàn)的首地址
5.2 消息(Message
)的發(fā)送
如果你稍微了解一點Runtime
,那么你肯定知道极颓,在 Objective-C
中朱盐,所有的消息傳遞中的“消息“都會被轉(zhuǎn)換成一個 selector
作為objc_msgSend
函數(shù)的參數(shù),
[object speak] -> objc_msgSend(object, @selector(speak))
消息發(fā)送的流程:
1.檢測這個 selector是不是要忽略的。
2.檢查target是不是為nil菠隆。如果這里有相應(yīng)的nil的處理函數(shù)兵琳,就跳轉(zhuǎn)到相應(yīng)的函數(shù)中。如果沒有處理nil的函數(shù)骇径,就自動清理現(xiàn)場并返回躯肌。這一點就是為何在OC中給nil發(fā)送消息不會崩潰的原因。
3.確定不是給nil發(fā)消息之后破衔,在該class的緩存中查找方法對應(yīng)的IMP實現(xiàn)清女。如果找到,就跳轉(zhuǎn)進去執(zhí)行运敢。如果沒有找到校仑,就在方法分發(fā)表里面繼續(xù)查找,一直找到NSObject為止传惠。
4.如果還沒有找到迄沫,那就需要開始消息轉(zhuǎn)發(fā)階段了。至此卦方,發(fā)送消息Messaging階段完成羊瘩。這一階段主要完成的是通過select()快速查找IMP的過程。
5.3 消息(Message
)的轉(zhuǎn)發(fā)
我們都知道調(diào)用一個沒有實現(xiàn)的方法時盼砍,會crash尘吗,在程序crash之前還會執(zhí)行消息轉(zhuǎn)發(fā),那么我們來看看消息轉(zhuǎn)發(fā)的機制:
- 消息轉(zhuǎn)發(fā)第一步:
+(BOOL)resolveInstanceMethod:(SEL)sel
浇坐,當(dāng)調(diào)用一個方法睬捶,但沒有實現(xiàn)時,消息會通過上面方法尋找是否能找到實現(xiàn)近刘。
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
- 如果上一步?jīng)]有實現(xiàn)擒贸,那么進入
-(id)forwardingTargetForSelector:(SEL)aSelector
,這一步是替消息找備援接收者觉渴,如果這一步返回的是nil介劫,那么補救措施就完全的失效了,Runtime系統(tǒng)會向?qū)ο蟀l(fā)送methodSignatureForSelector:
消息.
-(id)forwardingTargetForSelector:(SEL)aSelector{
Class class = NSClassFromString(@"BLView");
UIView *view = class.new;
if (aSelector == NSSelectorFromString(@"calculate")) {
return vc;
}
return nil;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
取到返回的方法簽名用于生成NSInvocation
對象案淋。為接下來的完整的消息轉(zhuǎn)發(fā)生成一個NSMethodSignature
對象座韵。NSMethodSignature
對象會被包裝成NSInvocation
對象,forwardInvocation:
方法里就可以對NSInvocation
進行處理了踢京。-(void)forwardInvocation:(NSInvocation *)anInvocation
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
5.4 Runtime
相關(guān)的操作
Method
// 調(diào)用指定方法的實現(xiàn)
id method_invoke ( id receiver, Method m, ... );
// 調(diào)用返回一個數(shù)據(jù)結(jié)構(gòu)的方法的實現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名
SEL method_getName ( Method m );
// 返回方法的實現(xiàn)
IMP method_getImplementation ( Method m );
// 獲取描述方法參數(shù)和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );
// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );
// 獲取方法的指定位置參數(shù)的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數(shù)的個數(shù)
unsigned int method_getNumberOfArguments ( Method m );
// 通過引用返回方法指定位置參數(shù)的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
// 設(shè)置方法的實現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個方法的實現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
關(guān)于方法交換舉個例子??:
@implementation NSObject (Swizzling)
+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
Class class = [self class];
//原有方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
//替換原有方法的新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//先嘗試給源SEL添加IMP誉碴,這里是為了避免源SEL沒有實現(xiàn)IMP的情況
BOOL didAddMethod = class_addMethod(class,originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {//添加成功:說明源SEL沒有實現(xiàn)IMP宦棺,將源SEL的IMP替換到交換SEL的IMP
class_replaceMethod(class,swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {//添加失敗:說明源SEL已經(jīng)有IMP翔烁,直接將兩個SEL的IMP交換即可
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
需要注意的是方法的交換需要寫在方法所在類的+(void)load;
中
#import "NSArray+safe.h"
#import <objc/message.h>
#import "NSObject+Swizzling.h"
@implementation NSArray (safe)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@autoreleasepool {
[objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)];
[objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];
[objc_getClass("__NSArrayM") swizzleMethod:@selector(integerValue) swizzledSelector:@selector(replace_integerValue)];
}
});
}
- (id)emptyObjectIndex:(NSInteger)index{
return nil;
}
- (id)arrObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
return nil;
}
return [self arrObjectIndex:index];
}
- (id)mutableObjectIndex:(NSInteger)index{
if (index >= self.count || index < 0) {
return nil;
}
return [self mutableObjectIndex:index];
}
- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{
if (object) {
[self mutableInsertObject:object atIndex:index];
}
}
- (NSInteger)replace_integerValue {
return 0;
}
@end
SEL
// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法渺氧,將方法名映射到一個選擇器,并返回這個選擇器
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法
SEL sel_getUid ( const char *str );
// 比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
IMP
// 創(chuàng)建一個指針函數(shù)的指針蹬屹,該函數(shù)調(diào)用時會調(diào)用特定的block
IMP imp_implementationWithBlock ( id block );
// 返回與IMP(使用imp_implementationWithBlock創(chuàng)建的)相關(guān)的block
id imp_getBlock ( IMP anImp );
// 解除block與IMP(使用imp_implementationWithBlock創(chuàng)建的)的關(guān)聯(lián)關(guān)系侣背,并釋放block的拷貝
BOOL imp_removeBlock ( IMP anImp );
@interface MyRuntimeBlock : NSObject
@end
@implementation MyRuntimeBlock
@end
// 測試代碼
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
六、Protocol
6.1 協(xié)議是什么慨默?
協(xié)議聲明了任何類都能夠選擇實現(xiàn)的程序接口贩耐。協(xié)議能夠使兩個不同繼承樹上的類相互交流并完成特定的目的,因此它提供了除繼承外的另一種選擇厦取。任何能夠為其他類提供有用行為的類都能夠聲明接口來匿名的傳達這個行為潮太。任何其他類都能夠選擇遵守這個協(xié)議并實現(xiàn)其中的一個或多個方法,從而利用這個行為虾攻。如果協(xié)議遵守者實現(xiàn)了協(xié)議中的方法铡买,那么聲明協(xié)議的類就能夠通過遵守者調(diào)用協(xié)議中的方法。
6.2 如何寫一個協(xié)議
協(xié)議中能夠聲明方法霎箍,以及屬性奇钞,協(xié)議的繼承。
Protocol
與Category
相同漂坏,屬性是不會添加到類中的景埃,我們需要使用objc_setAssociatedObject 、objc_getAssociatedObject
關(guān)聯(lián)屬性顶别。
@required
表示必須實現(xiàn)谷徙,@optional
代表可選
@protocol UITableViewDataSource<NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; // fixed font style. use custom view (UILabel) if you want something different
- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
@end
6.3 協(xié)議中的方法由誰來實現(xiàn)
實現(xiàn):遵守協(xié)議者及其子類
調(diào)用:遵守協(xié)議者、其子類驯绎、id <協(xié)議名>
6.4協(xié)議的作用
某一個類需要委托其他類處理某些事件完慧,最具代表性性的便是UITableView的那些代理方法。這些方法其實還是代理的方法剩失,只不過定義的地方可能會在委托者類中骗随,通過調(diào)用這些方法,可以:將委托者中的數(shù)據(jù)傳遞給代理赴叹;將代理的數(shù)據(jù)傳遞給委托者;將委托者的事件拋給代理去處理...
給某幾個特定的類添加統(tǒng)一的接口指蚜,這些接口是從這些類中抽象出的共同的行為乞巧,這樣便可以減少重復(fù)的代碼。
6.5 Runtime
中關(guān)于Protocol
的API
// 返回指定的協(xié)議
Protocol * objc_getProtocol ( const char *name );
// 獲取運行時所知道的所有協(xié)議的數(shù)組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 創(chuàng)建新的協(xié)議實例
Protocol * objc_allocateProtocol ( const char *name );
// 在運行時中注冊新創(chuàng)建的協(xié)議
void objc_registerProtocol ( Protocol *proto );
// 為協(xié)議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一個已注冊的協(xié)議到協(xié)議中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 為協(xié)議添加屬性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 返回協(xié)議名
const char * protocol_getName ( Protocol *p );
// 測試兩個協(xié)議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 獲取協(xié)議中指定條件的方法的方法描述數(shù)組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 獲取協(xié)議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 獲取協(xié)議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協(xié)議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 獲取協(xié)議采用的協(xié)議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看協(xié)議是否采用了另一個協(xié)議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
總而言之
Runtime
的所有知識基本都圍繞兩個中心
(1)類的各個方面(Class摊鸡、Object绽媒、Ivar蚕冬、Property、Method是辕、Protocol)的動態(tài)配置囤热。
(2)消息傳遞,Message Send
& Message Forward
.