為什么說Objective-C是動態(tài)語言
-
概念
動態(tài)語言 是指程序在運行時可以改變其結(jié)構(gòu):新的函數(shù)可以被引進,已有的函數(shù)可以被刪除等在結(jié)構(gòu)上的變化棒仍,類型的檢查是在運行時做的,優(yōu)點為方便閱讀川蒙,清晰明了庙睡,缺點為不方便調(diào)試。所謂的動態(tài)類型語言揪漩,意思就是類型的檢查是在運行時做的
靜態(tài)類型語言 的類型判斷是在運行前判斷(如編譯階段)旋恼,比如C#、java就是靜態(tài)類型語言,主要優(yōu)點在于其結(jié)構(gòu)非常規(guī)范奄容,便于調(diào)試冰更,方便類型安全
-
Objective-C是動態(tài)語言
Objective-C 可以通過Runtime 這個運行時機制,在運行時動態(tài)的添加變量昂勒、方法蜀细、類等
Objective-C具有相當(dāng)多的動態(tài)特性,基本的戈盈,也是經(jīng)常被提到和用到的有動態(tài)類型(Dynamic typing)审葬,動態(tài)綁定(Dynamic binding)和動態(tài)加載(Dynamic loading)
object-c類的類型和數(shù)據(jù)變量的類型都是在運行是確定的,而不是在編譯時- 確定奕谭。例如:多態(tài)特性涣觉,我們可以使用父類對象來指向子類對象,并且可以用來調(diào)用子類的方法
所以說 Objective-C是動態(tài)語言
Runtime 數(shù)據(jù)結(jié)構(gòu)
Runtime 又叫運行時血柳,是一套底層的 C 語言 API官册,其為 iOS 內(nèi)部的核心之一,我們平時編寫的 OC 代碼难捌,底層都是基于它來實現(xiàn)的
- 在Runtime 之前先來看下 結(jié)構(gòu)體
結(jié)構(gòu)體簡單用法
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 語言", "RUNOOB", "編程語言", 123456};
struct Student {
char *name;
int num;
int age;
char group;
float score;
} stu1 = {"Tom",12,16,'A',1233.9};
int main(int argc, const char * argv[]) {
//*********結(jié)構(gòu)體*********
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
//*********結(jié)構(gòu)體指針*********
//pstu指向結(jié)構(gòu)體變量stu1的地址
struct Student *pstu = &stu1;
//指針引用結(jié)構(gòu)體變量成員的方式是: (*指針變量名).成員名
//注意膝宁,*p 兩邊的括號不可省略鸦难,因為成員運算符“.”的優(yōu)先級高于指針運算符“*”,所以如果 *p 兩邊的括號省略的話员淫,那么 *p.num 就等價于 *(p.num) 了合蔽。
printf("%s的學(xué)號是%d,年齡是%d介返,在%c組拴事,今年的成績是%.1f!\n", (*pstu).name, (*pstu).num, (*pstu).age, (*pstu).group, (*pstu).score);
//->”是“指向結(jié)構(gòu)體成員運算符
printf("%s的學(xué)號是%d圣蝎,年齡是%d刃宵,在%c組,今年的成績是%.1f徘公!\n", pstu->name, pstu->num, pstu->age, pstu->group, pstu->score);
return 0;
}
- Class 的在OC底層中的定義
typedef struct objc_class *Class;
很明顯是一個 結(jié)構(gòu)體類型的指針
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
/// An opaque type that represents an Objective-C class. 不透明的類型牲证,表示一個Objective-C類
typedef struct objc_class *Class;
- id 的在OC底層中的定義
typedef struct objc_object *id;
很明顯也是一個 結(jié)構(gòu)體類型的指針
/// Represents an instance of a class.表示類的實例
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class. 指向類實例的指針
typedef struct objc_object *id;
可以發(fā)現(xiàn):
每個objective-c對象都有一個隱藏的 數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)是Objective-C對象的第一個成員變量关面,它就是isa指針坦袍。
這個指針指向哪呢?
指向Class isa
等太,指向的是這個 對象所對應(yīng)的類(Class,其實類也是一個對象)- NSObject 的在OC底層中的定義
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
不考慮@interface關(guān)鍵字在編譯時的作用捂齐,可以把NSObject更接近C語言結(jié)構(gòu)表示為:
//第一步變形
struct NSObject {
Class isa ;
}
//第二步變形:根據(jù)第1點,Class 在底層的定義澈驼,變形
struct NSObject{
struct objc_class *isa
}
到這一步為止辛燥,所以還得研究 結(jié)構(gòu)體 objc_class
- 分析結(jié)構(gòu)體 objc_class
struct objc_class {
//isa 指針
//對象也有一個isa指針,指向Class isa缝其,指向的是這個 對象所對應(yīng)的類(Class,其實類也是一個對象)
//指向metaclass挎塌,
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//指向其父類,如果這個類是根類内边,則為NULL榴都。
Class _Nullable super_class OBJC2_UNAVAILABLE;
//類名
const char * _Nonnull name OBJC2_UNAVAILABLE;
//類的版本信息,初始化默認為0漠其,可以通過runtime函數(shù)class_setVersion和class_getVersion進行修改嘴高、讀取
long version OBJC2_UNAVAILABLE;
//一些標(biāo)識信息,如CLS_CLASS (0x1L) 表示該類為普通 class ,其中包含對象方法和成員變量;CLS_META (0x2L) 表示該類為 metaclass和屎,其中包含類方法;
long info OBJC2_UNAVAILABLE;
//該類的實例變量大小(包括從父類繼承下來的實例變量);
long instance_size OBJC2_UNAVAILABLE;
//用于存儲每個成員變量的地址
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
// 與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲對象方法拴驮,如CLS_META (0x2L),則存儲類方法;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
//指向最近使用的方法的指針柴信,用于提升效率套啤;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
//存儲該類遵守的協(xié)議
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
/// An opaque type that represents an Objective-C class. 不透明的類型,表示一個Objective-C類
typedef struct objc_class *Class;
附加解釋:
Class isa:指向metaclass随常,也就是靜態(tài)的Class潜沦。對象obj也有一個isa指針萄涯,指向Class isa,指向的是這個 對象所對應(yīng)的類(Class,其實類也是一個對象)
這是解釋類方法也就是實例方法唆鸡,因為類也是對象
metaclass的isa指向根metaclass涝影,如果該metaclass是根metaclass則指向自身;
metaclass 的super_class指向父metaclass
如果該metaclass是根metaclass則指向該metaclass對應(yīng)的類争占;
重點就是isa指針燃逻,Objective-C對類對象和實例對象中的isa所指向的類結(jié)構(gòu)做了不同的命名,類對象中isa指向類結(jié)構(gòu)稱為metaclass燃乍,它存儲類的static類成員變量與static類成員方法(+開頭的方法)唆樊;實例對象中的isa指向類結(jié)構(gòu)稱作class宛琅,它存儲類的普通成員變量與普通成員方法(-開頭的方法)刻蟹。
- SEL 的在OC底層中的定義
也是一個結(jié)構(gòu)體指針
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
Rumtime 消息機制
提到消息機制,不能不提到 SEL嘿辟,SEL又叫選擇器舆瘪,是表示一個方法的selector的指針,他的定義typedef struct objc_selector *SEL;
正因為是結(jié)構(gòu)體指針红伦,所以也有人說SEL是一個對象
比如
創(chuàng)建一Person個類 繼承NSObject英古、
創(chuàng)建一Son個類 繼承Person、
創(chuàng)建一個Student 繼承NSObject昙读,分別實現(xiàn)如下代碼
- (instancetype)init
{
self = [super init];
if (self) {
SEL sel1 = @selector(eat);
NSLog(@"sel : %p", sel1);
}
return self;
}
- (void)eat {
NSLog(@"吃了");
}
輸出如下:
2019-01-19 17:55:05.029309+0800 RuntimeDemo[3106:91635] sel : 0x10552fa4f
2019-01-19 17:55:05.029515+0800 RuntimeDemo[3106:91635] sel : 0x10552fa4f
2019-01-19 17:55:05.029619+0800 RuntimeDemo[3106:91635] sel : 0x10552fa4f
2019-01-19 17:55:05.029757+0800 RuntimeDemo[3106:91635] sel : 0x10552fa4f
Son 繼承Person召调,所以會調(diào)用父類的init 方法,會打印兩次
會發(fā)現(xiàn)有的SEL蛮浑,打印的地址相同
由此證明唠叛,不同類的相同SEL是同一個對象。
所以在 Objective-C同一個類(及類的繼承體系)中沮稚,不能存在2個同名的方法艺沼,即使參數(shù)類型不同也不行。相同的方法只能對應(yīng)一個SEL蕴掏。這也就導(dǎo)致 Objective-C在處理相同方法名且參數(shù)個數(shù)相同但類型不同的方法方面的能力很差
不同類的實例對象執(zhí)行相同的selector時障般,會在各自的方法列表中去根據(jù)selector去尋找自己對應(yīng)的IMP
- 問題一: 不同對象調(diào)用相同的方法怎么找到SEL?
一般的調(diào)用方法如下
- (void) setUpTest01 {
Person *p = [[Person alloc] init];
[p eat];
}
也可以這么些
- (void)setUpTest02 {
Person *p = [Person alloc];
p = [p init];
//[p eat];
[p performSelector:@selector(eat)];
}
Runtime進行方法調(diào)用本質(zhì)上是發(fā)送消息盛杰,發(fā)送消息是怎么發(fā)送的呢挽荡?
通過Runtime 就會發(fā)現(xiàn),在底層使用了 objc_msgSend 函數(shù)
//消息機制
- (void)setUpTest03 {
//Person *p = [Person alloc];
//類方法 其實類也是一個對象即供,oc 表示類 類型定拟,swift 表示元 類型
Person *p = objc_msgSend([Person class], @selector(alloc));
//p = [p init];
p = objc_msgSend(p, @selector(init));
//objc_msgSend(p,@selector(eat)) //編譯報錯
/**
1. target -> build setting ,搜索 msg
2. Enable Strict Cheaking of objc_msgSend Calls 設(shè)置為No
、設(shè)置以后就不會編譯錯誤了募狂,因為oc 不推薦使用底層去實現(xiàn)
*/
objc_msgSend(p,@selector(eat));
//objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>) //... 可擴展參數(shù)
}
- 解讀 objc_msgSend
- self办素,調(diào)用當(dāng)前方法的對象角雷。
- _cmd,當(dāng)前被調(diào)用方法的SEL
objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>) //... 可擴展參數(shù)
繼續(xù)分解性穿,有些函數(shù)不懂沒關(guān)系
- (void)setUpTest04 {
//Person *p = [Person alloc];
//類方法 其實類也是一個對象勺三,oc 表示類 類型,swift 表示元 類型
//Person *p = objc_msgSend(objc_getClass("Person"), @selector(alloc));
Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
//p = objc_msgSend(p, @selector(init));
p = objc_msgSend(p, sel_registerName("init"));
// objc_msgSend(p,@selector(eat));
objc_msgSend(p,sel_registerName("eat"));
}
工程中的所有的SEL組成一個Set集合需曾,Set的特點就是唯一吗坚,因此SEL是唯一的。
因此呆万,如果我們想到這個方法集合中查找某個方法時商源,只需要去 找到這個方法對應(yīng)的SEL就行了,SEL實際上就是根據(jù)方法名hash化了的一個字符串谋减,而對于字符串的比較僅僅需要比較他們的地址就可以了牡彻,可以說速度 上無語倫比!出爹!但是庄吼,有一個問題,就是數(shù)量增多會增大hash沖突而導(dǎo)致的性能下降(或是沒有沖突严就,因為也可能用的是perfect hash)总寻。但是不管使用什么樣的方法加速,如果能夠?qū)⒖偭繙p少(多個方法可能對應(yīng)同一個SEL)梢为,那將是最犀利的方法渐行。那么,我們就不難理解铸董,為什么 SEL僅僅是函數(shù)名了祟印。
-
IMP
IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的首地址
/// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif
解讀參數(shù)
第一個參數(shù)是指向self的指針(如果是實例方法袒炉,則是類實例的內(nèi)存地址旁理;如果是類方法,則是指向元類的指針)我磁,
第二個參數(shù)是方法選擇器(selector)孽文,接下來是方法的實際參數(shù)列表。SEL就是為了查找方法的最終實現(xiàn)IMP的夺艰。由于每個方法對應(yīng)唯一的SEL芋哭,因此我們可以通過SEL方便快速準(zhǔn)確地獲得它所對應(yīng)的 IMP,查找過程將在下面討論郁副。取得IMP后减牺,我們就獲得了執(zhí)行這個方法代碼的入口點,此時,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針 了
Method
struct objc_method {
//方法名
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
//方法的實現(xiàn)
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
/// An opaque type that represents a category.
typedef struct objc_category *Category;
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP拔疚,實際上相當(dāng)于在SEL和IMP之間作了一個映射肥隆。有了SEL
當(dāng)消息發(fā)送給一個對象時,objc_msgSend通過對象的isa指針獲取到類的結(jié)構(gòu)體稚失,然后在方法分發(fā)表里面查找方法的selector栋艳。如果 沒有找到selector,則通過objc_msgSend結(jié)構(gòu)體中的指向父類的指針找到其父類句各,并在父類的分發(fā)表里面查找方法的selector吸占。依 此,會一直沿著類的繼承體系到達NSObject類凿宾。一旦定位到selector矾屯,函數(shù)會就獲取到了實現(xiàn)的入口點,并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實 現(xiàn)初厚。如果最后沒有定位到selector件蚕,則會走消息轉(zhuǎn)發(fā)流程,這個我們在后面討論惧所。
為了加速消息的處理骤坐,運行時系統(tǒng)緩存使用過的selector及對應(yīng)的方法的地址绪杏。這點我們在前面討論過下愈,不再重復(fù)。
KVO的底層實現(xiàn)
- 1.KVO是基于runtime機制實現(xiàn)的
- 2.當(dāng)某個類的屬性對象第一次被觀察時蕾久,系統(tǒng)就會在運行期動態(tài)地創(chuàng)建該類的一個派生類势似,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內(nèi)實現(xiàn)真正的通知機制
- 3.如果原類為Person僧著,那么生成的派生類名為NSKVONotifying_Person
- 4.每個類對象中都有一個isa指針指向當(dāng)前類履因,當(dāng)一個類對象的第一次被觀察,那么系統(tǒng)會偷偷將isa指針指向動態(tài)生成的派生類盹愚,從而在給被監(jiān)控屬性賦值時執(zhí)行的是派生類的setter方法
- 5.鍵值觀察通知依賴于NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey:栅迄;在一個被觀察屬性發(fā)生改變之前, willChangeValueForKey:一定會被調(diào)用皆怕,這就 會記錄舊的值毅舆。而當(dāng)改變發(fā)生后,didChangeValueForKey:會被調(diào)用愈腾,繼而 observeValueForKey:ofObject:change:context: 也會被調(diào)用憋活。
1. command + Q 關(guān)閉工程項目
2. 在次運行項目,在Person 創(chuàng)建對象之后加上一個斷點
3. 在 控制臺可以看出 _p 目錄下 NSObject ——> isa 又個isa 指針虱黄,指向(class)Person
4. 調(diào)用 [_p FF_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil] 之后
5. 在 控制臺可以看出 _p 目錄下 NSObject ——> isa 又個isa 指針悦即,指向(class)FFKVOPerson
(self->_p->isa:NSKVONotifying_Person),
6. 監(jiān)聽屬性的值age 是否被修改,其實是在NSKVONotifying_Person類里面重寫了set 方法辜梳,一旦改變粱甫,就是通知父類做一系列操作
7. 當(dāng)我 用成員變量_name 時候,無法監(jiān)聽作瞄,所以證明只能觀察重寫的 set 方法
代碼如下:
#import "NSObject+KVO.h"
#import <objc/message.h>
@implementation NSObject (KVO)
- (void)FF_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
NSLog(@"%@",self);
//1. 動態(tài)添加一個類
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [@"FFKVO" stringByAppendingString:oldClassName];
const char *newClass = [newClassName UTF8String];
//定義一個類
Class myClass = objc_allocateClassPair([self class], newClass, 0);
//重寫setAge(添加一個set方法)
class_addMethod(myClass, @selector(setAge:), (IMP)setAge, "v@:");
//注冊這個類
objc_registerClassPair(myClass);
//改變isa 指針的指向
//NSKVONotifying_A類剖析:在這個過程魔种,被觀察對象的 isa 指針從指向原來的A類,被KVO機制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類粉洼,來實現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽节预;
object_setClass(self, myClass);
//關(guān)聯(lián)對象
objc_setAssociatedObject(self, (__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//默認參數(shù)!属韧!
void setAge(id self,SEL _cmd ,int age) {
//1保存一下自己
id class = [self class];
//2.讓自己指向父類
object_setClass(self, class_getSuperclass([self class]));
NSLog(@"修改完畢 %d",age);
//3.
objc_msgSend(self,@selector(setAge:),age);
//取出觀察者
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
NSDictionary *dic = @{@"new": [NSNumber numberWithInt:age]};
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"age",self,dic,nil);
//4.改回類型 針對 3
object_setClass(self, class);
}