亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統(tǒng) > iOS > 正文

iOS Runtime詳解(新手也看得懂)

2019-10-21 18:22:00
字體:
供稿:網(wǎng)友

前言

Runtime的特性主要是消息(方法)傳遞,如果消息(方法)在對(duì)象中找不到,就進(jìn)行轉(zhuǎn)發(fā),具體怎么實(shí)現(xiàn)的呢。我們從下面幾個(gè)方面探尋Runtime的實(shí)現(xiàn)機(jī)制。

  • Runtime介紹
  • Runtime消息傳遞
  • Runtime消息轉(zhuǎn)發(fā)
  • Runtime應(yīng)用

Runtime介紹

Objective-C 擴(kuò)展了 C 語言,并加入了面向?qū)ο筇匦院?Smalltalk 式的消息傳遞機(jī)制。而這個(gè)擴(kuò)展的核心是一個(gè)用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 面向?qū)ο蠛蛣?dòng)態(tài)機(jī)制的基石。

Objective-C 是一個(gè)動(dòng)態(tài)語言,這意味著它不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來動(dòng)態(tài)得創(chuàng)建類和對(duì)象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)。理解 Objective-C 的 Runtime 機(jī)制可以幫我們更好的了解這個(gè)語言,適當(dāng)?shù)臅r(shí)候還能對(duì)語言進(jìn)行擴(kuò)展,從系統(tǒng)層面解決項(xiàng)目中的一些設(shè)計(jì)或技術(shù)問題。了解 Runtime ,要先了解它的核心 - 消息傳遞 (Messaging)。

Runtime其實(shí)有兩個(gè)版本: “modern” 和 “legacy”。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行 (Modern) 版的 Runtime 系統(tǒng),只能運(yùn)行在 iOS 和 macOS 10.5 之后的 64 位程序中。而 macOS 較老的32位程序仍采用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系統(tǒng)。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類,而現(xiàn)行版就不需要。

Runtime 基本是用 C 和匯編寫的,可見蘋果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護(hù)的開源代碼。蘋果和GNU各自維護(hù)一個(gè)開源的 runtime 版本,這兩個(gè)版本之間都在努力的保持一致。

平時(shí)的業(yè)務(wù)中主要是使用官方Api,解決我們框架性的需求。

高級(jí)編程語言想要成為可執(zhí)行文件需要先編譯為匯編語言再匯編為機(jī)器語言,機(jī)器語言也是計(jì)算機(jī)能夠識(shí)別的唯一語言,但是OC并不能直接編譯為匯編語言,而是要先轉(zhuǎn)寫為純C語言再進(jìn)行編譯和匯編的操作,從OC到C語言的過渡就是由runtime來實(shí)現(xiàn)的。然而我們使用OC進(jìn)行面向?qū)ο箝_發(fā),而C語言更多的是面向過程開發(fā),這就需要將面向?qū)ο蟮念愞D(zhuǎn)變?yōu)槊嫦蜻^程的結(jié)構(gòu)體。

Runtime消息傳遞

一個(gè)對(duì)象的方法像這樣[obj foo],編譯器轉(zhuǎn)成消息發(fā)送objc_msgSend(obj, foo),Runtime時(shí)執(zhí)行的流程是這樣的:

  • 首先,通過obj的isa指針找到它的 class ;
  • 在 class 的 method list 找 foo ;
  • 如果 class 中沒到 foo,繼續(xù)往它的 superclass 中找 ;
  • 一旦找到 foo 這個(gè)函數(shù),就去執(zhí)行它的實(shí)現(xiàn)IMP 。

但這種實(shí)現(xiàn)有個(gè)問題,效率低。但一個(gè)class 往往只有 20% 的函數(shù)會(huì)被經(jīng)常調(diào)用,可能占總調(diào)用次數(shù)的 80% 。每個(gè)消息都需要遍歷一次objc_method_list 并不合理。如果把經(jīng)常被調(diào)用的函數(shù)緩存下來,那可以大大提高函數(shù)查詢的效率。這也就是objc_class 中另一個(gè)重要成員objc_cache 做的事情 - 再找到foo 之后,把foo 的method_name 作為key ,method_imp作為value 給存起來。當(dāng)再次收到foo 消息的時(shí)候,可以直接在cache 里找到,避免去遍歷objc_method_list。從前面的源代碼可以看到objc_cache是存在objc_class 結(jié)構(gòu)體中的。

objec_msgSend的方法定義如下:

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

那消息傳遞是怎么實(shí)現(xiàn)的呢?我們看看對(duì)象(object),類(class),方法(method)這幾個(gè)的結(jié)構(gòu)體:

//對(duì)象struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};//類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;//方法列表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;}        OBJC2_UNAVAILABLE;//方法struct objc_method { SEL method_name      OBJC2_UNAVAILABLE; char *method_types     OBJC2_UNAVAILABLE; IMP method_imp      OBJC2_UNAVAILABLE;}
  1. 系統(tǒng)首先找到消息的接收對(duì)象,然后通過對(duì)象的isa找到它的類。
  2. 在它的類中查找method_list,是否有selector方法。
  3. 沒有則查找父類的method_list。
  4. 找到對(duì)應(yīng)的method,執(zhí)行它的IMP。
  5. 轉(zhuǎn)發(fā)IMP的return值。

下面講講消息傳遞用到的一些概念:

  • 類對(duì)象(objc_class)
  • 實(shí)例(objc_object)
  • 元類(Meta Class)
  • Method(objc_method)
  • SEL(objc_selector)
  • IMP
  • 類緩存(objc_cache)
  • Category(objc_category)

類對(duì)象(objc_class)

Objective-C類是由Class類型來表示的,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針。

typedef struct objc_class *Class;

查看objc/runtime.h中objc_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;

struct objc_class結(jié)構(gòu)體定義了很多變量,通過命名不難發(fā)現(xiàn),
結(jié)構(gòu)體里保存了指向父類的指針、類的名字、版本、實(shí)例大小、實(shí)例變量列表、方法列表、緩存、遵守的協(xié)議列表等,
一個(gè)類包含的信息也不就正是這些嗎?沒錯(cuò),類對(duì)象就是一個(gè)結(jié)構(gòu)體struct objc_class,這個(gè)結(jié)構(gòu)體存放的數(shù)據(jù)稱為元數(shù)據(jù)(metadata),

該結(jié)構(gòu)體的第一個(gè)成員變量也是isa指針,這就說明了Class本身其實(shí)也是一個(gè)對(duì)象,因此我們稱之為類對(duì)象,類對(duì)象在編譯期產(chǎn)生用于創(chuàng)建實(shí)例對(duì)象,是單例。

實(shí)例(objc_object)

/// 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;

類對(duì)象中的元數(shù)據(jù)存儲(chǔ)的都是如何創(chuàng)建一個(gè)實(shí)例的相關(guān)信息,那么類對(duì)象和類方法應(yīng)該從哪里創(chuàng)建呢?
就是從isa指針指向的結(jié)構(gòu)體創(chuàng)建,類對(duì)象的isa指針指向的我們稱之為元類(metaclass),
元類中保存了創(chuàng)建類對(duì)象以及類方法所需的所有信息,因此整個(gè)結(jié)構(gòu)應(yīng)該如下圖所示:

iOS,Runtime

元類(Meta Class)

通過上圖我們可以看出整個(gè)體系構(gòu)成了一個(gè)自閉環(huán),struct objc_object結(jié)構(gòu)體實(shí)例它的isa指針指向類對(duì)象,
類對(duì)象的isa指針指向了元類,super_class指針指向了父類的類對(duì)象,

而元類的super_class指針指向了父類的元類,那元類的isa指針又指向了自己。

元類(Meta Class)是一個(gè)類對(duì)象的類。

在上面我們提到,所有的類自身也是一個(gè)對(duì)象,我們可以向這個(gè)對(duì)象發(fā)送消息(即調(diào)用類方法)。

為了調(diào)用類方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體。這就引出了meta-class的概念,元類中保存了創(chuàng)建類對(duì)象以及類方法所需的所有信息。

任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。

Method(objc_method)

先看下定義

runtime.h/// An opaque type that represents a method in a class definition.代表類定義中一個(gè)方法的不透明類型typedef struct objc_method *Method;struct objc_method { SEL method_name           OBJC2_UNAVAILABLE; char *method_types          OBJC2_UNAVAILABLE; IMP method_imp           OBJC2_UNAVAILABLE;

Method和我們平時(shí)理解的函數(shù)是一致的,就是表示能夠獨(dú)立完成一個(gè)功能的一段代碼,比如:

- (void)logName{ NSLog(@"name");}

這段代碼,就是一個(gè)函數(shù)。

我們來看下objc_method這個(gè)結(jié)構(gòu)體的內(nèi)容:

  • SEL method_name 方法名
  • char *method_types 方法類型
  • IMP method_imp 方法實(shí)現(xiàn)

在這個(gè)結(jié)構(gòu)體重,我們已經(jīng)看到了SEL和IMP,說明SEL和IMP其實(shí)都是Method的屬性。

我們接著來看SEL。

SEL(objc_selector)

先看下定義

Objc.h/// An opaque type that represents a method selector.代表一個(gè)方法的不透明類型typedef struct objc_selector *SEL;

objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objective-C中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的 ID,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:

@property SEL selector;

可以看到selector是SEL的一個(gè)實(shí)例。

A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.

其實(shí)selector就是個(gè)映射到方法的C字符串,你可以用 Objective-C 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè) SEL 類型的方法選擇器。

selector既然是一個(gè)string,我覺得應(yīng)該是類似className+method的組合,命名規(guī)則有兩條:

  • 同一個(gè)類,selector不能重復(fù)
  • 不同的類,selector可以重復(fù)

這也帶來了一個(gè)弊端,我們?cè)趯慍代碼的時(shí)候,經(jīng)常會(huì)用到函數(shù)重載,就是函數(shù)名相同,參數(shù)不同,但是這在Objective-C中是行不通的,因?yàn)閟elector只記了method的name,沒有參數(shù),所以沒法區(qū)分不同的method。

比如:

- (void)caculate(NSInteger)num;- (void)caculate(CGFloat)num;

是會(huì)報(bào)錯(cuò)的。

我們只能通過命名來區(qū)別:

- (void)caculateWithInt(NSInteger)num;- (void)caculateWithFloat(CGFloat)num;

在不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器。

IMP

看下IMP的定義

/// A pointer to the function of a method implementation. 指向一個(gè)方法實(shí)現(xiàn)的指針typedef id (*IMP)(id, SEL, ...); #endif

就是指向最終實(shí)現(xiàn)程序的內(nèi)存地址的指針。

在iOS的Runtime中,Method通過selector和IMP兩個(gè)屬性,實(shí)現(xiàn)了快速查詢方法及實(shí)現(xiàn),相對(duì)提高了性能,又保持了靈活性。

類緩存(objc_cache)

當(dāng)Objective-C運(yùn)行時(shí)通過跟蹤它的isa指針檢查對(duì)象時(shí),它可以找到一個(gè)實(shí)現(xiàn)許多方法的對(duì)象。然而,你可能只調(diào)用它們的一小部分,并且每次查找時(shí),搜索所有選擇器的類分派表沒有意義。所以類實(shí)現(xiàn)一個(gè)緩存,每當(dāng)你搜索一個(gè)類分派表,并找到相應(yīng)的選擇器,它把它放入它的緩存。所以當(dāng)objc_msgSend查找一個(gè)類的選擇器,它首先搜索類緩存。這是基于這樣的理論:如果你在類上調(diào)用一個(gè)消息,你可能以后再次調(diào)用該消息。

為了加速消息分發(fā), 系統(tǒng)會(huì)對(duì)方法和對(duì)應(yīng)的地址進(jìn)行緩存,就放在上述的objc_cache,所以在實(shí)際運(yùn)行中,大部分常用的方法都是會(huì)被緩存起來的,Runtime系統(tǒng)實(shí)際上非???,接近直接執(zhí)行內(nèi)存地址的程序速度。

Category(objc_category)

Category是表示一個(gè)指向分類的結(jié)構(gòu)體的指針,其定義如下:

struct category_t {  const char *name;  classref_t cls;  struct method_list_t *instanceMethods;  struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties;};

name:是指 class_name 而不是 category_name。
cls:要擴(kuò)展的類對(duì)象,編譯期間是不會(huì)定義的,而是在Runtime階段通過name對(duì) 應(yīng)到對(duì)應(yīng)的類對(duì)象。
instanceMethods:category中所有給類添加的實(shí)例方法的列表。
classMethods:category中所有添加的類方法的列表。
protocols:category實(shí)現(xiàn)的所有協(xié)議的列表。
instanceProperties:表示Category里所有的properties,這就是我們可以通過objc_setAssociatedObject和objc_getAssociatedObject增加實(shí)例變量的原因,不過這個(gè)和一般的實(shí)例變量是不一樣的。

從上面的category_t的結(jié)構(gòu)體中可以看出,分類中可以添加實(shí)例方法,類方法,甚至可以實(shí)現(xiàn)協(xié)議,添加屬性,不可以添加成員變量。

Runtime消息轉(zhuǎn)發(fā)

前文介紹了進(jìn)行一次發(fā)送消息會(huì)在相關(guān)的類對(duì)象中搜索方法列表,如果找不到則會(huì)沿著繼承樹向上一直搜索知道繼承樹根部(通常為NSObject),如果還是找不到并且消息轉(zhuǎn)發(fā)都失敗了就回執(zhí)行doesNotRecognizeSelector:方法報(bào)unrecognized selector錯(cuò)。那么消息轉(zhuǎn)發(fā)到底是什么呢?接下來將會(huì)逐一介紹最后的三次機(jī)會(huì)。

  • 動(dòng)態(tài)方法解析
  • 備用接收者
  • 完整消息轉(zhuǎn)發(fā)

iOS,Runtime

動(dòng)態(tài)方法解析

首先,Objective-C運(yùn)行時(shí)會(huì)調(diào)用 +resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù)并返回YES, 那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程。

實(shí)現(xiàn)一個(gè)動(dòng)態(tài)方法解析的例子如下:

- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //執(zhí)行foo函數(shù) [self performSelector:@selector(foo:)];}+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(foo:)) {//如果是執(zhí)行foo函數(shù),就動(dòng)態(tài)解析,指定新的IMP  class_addMethod([self class], sel, (IMP)fooMethod, "v@:");  return YES; } return [super resolveInstanceMethod:sel];}void fooMethod(id obj, SEL _cmd) { NSLog(@"Doing foo");//新的foo函數(shù)}

打印結(jié)果:

2018-04-01 12:23:35.952670+0800 ocram[87546:23235469] Doing foo

可以看到雖然沒有實(shí)現(xiàn)foo:這個(gè)函數(shù),但是我們通過class_addMethod動(dòng)態(tài)添加fooMethod函數(shù),并執(zhí)行fooMethod這個(gè)函數(shù)的IMP。從打印結(jié)果看,成功實(shí)現(xiàn)了。

如果resolve方法返回 NO ,運(yùn)行時(shí)就會(huì)移到下一步:forwardingTargetForSelector。

備用接收者

如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)。

實(shí)現(xiàn)一個(gè)備用接收者的例子如下:

#import "ViewController.h"#import "objc/runtime.h"@interface Person: NSObject@end@implementation Person- (void)foo { NSLog(@"Doing foo");//Person的foo函數(shù)}@end@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //執(zhí)行foo函數(shù) [self performSelector:@selector(foo)];}+ (BOOL)resolveInstanceMethod:(SEL)sel { return YES;//返回YES,進(jìn)入下一步轉(zhuǎn)發(fā)}- (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(foo)) {  return [Person new];//返回Person對(duì)象,讓Person對(duì)象接收這個(gè)消息 }  return [super forwardingTargetForSelector:aSelector];}@end

打印結(jié)果:

2018-04-01 12:45:04.757929+0800 ocram[88023:23260346] Doing foo

可以看到我們通過forwardingTargetForSelector把當(dāng)前ViewController的方法轉(zhuǎn)發(fā)給了Person去執(zhí)行了。打印結(jié)果也證明我們成功實(shí)現(xiàn)了轉(zhuǎn)發(fā)。

完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。

首先它會(huì)發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil ,Runtime則會(huì)發(fā)出 -doesNotRecognizeSelector: 消息,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation 對(duì)象并發(fā)送 -forwardInvocation:消息給目標(biāo)對(duì)象。

實(shí)現(xiàn)一個(gè)完整轉(zhuǎn)發(fā)的例子如下:

#import "ViewController.h"#import "objc/runtime.h"@interface Person: NSObject@end@implementation Person- (void)foo { NSLog(@"Doing foo");//Person的foo函數(shù)}@end@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //執(zhí)行foo函數(shù) [self performSelector:@selector(foo)];}+ (BOOL)resolveInstanceMethod:(SEL)sel { return YES;//返回YES,進(jìn)入下一步轉(zhuǎn)發(fā)}- (id)forwardingTargetForSelector:(SEL)aSelector { return nil;//返回nil,進(jìn)入下一步轉(zhuǎn)發(fā)}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {  return [NSMethodSignature signatureWithObjCTypes:"v@:"];//簽名,進(jìn)入forwardInvocation }  return [super methodSignatureForSelector:aSelector];}- (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = anInvocation.selector; Person *p = [Person new]; if([p respondsToSelector:sel]) {  [anInvocation invokeWithTarget:p]; } else {  [self doesNotRecognizeSelector:sel]; }}@end

打印結(jié)果:

2018-04-01 13:00:45.423385+0800 ocram[88353:23279961] Doing foo

從打印結(jié)果來看,我們實(shí)現(xiàn)了完整的轉(zhuǎn)發(fā)。通過簽名,Runtime生成了一個(gè)對(duì)象anInvocation,發(fā)送給了forwardInvocation,我們?cè)趂orwardInvocation方法里面讓Person對(duì)象去執(zhí)行了foo函數(shù)。簽名參數(shù)v@:怎么解釋呢,這里蘋果文檔Type Encodings有詳細(xì)的解釋。

以上就是Runtime的三次轉(zhuǎn)發(fā)流程。下面我們講講Runtime的實(shí)際應(yīng)用。

Runtime應(yīng)用

Runtime簡(jiǎn)直就是做大型框架的利器。它的應(yīng)用場(chǎng)景非常多,下面就介紹一些常見的應(yīng)用場(chǎng)景。

  • 關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性
  • 方法魔法(Method Swizzling)方法添加和替換和KVO實(shí)現(xiàn)
  • 消息轉(zhuǎn)發(fā)(熱更新)解決Bug(JSPatch)
  • 實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和自動(dòng)解檔
  • 實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)

關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類增加屬性

我們都是知道分類是不能自定義屬性和變量的。下面通過關(guān)聯(lián)對(duì)象實(shí)現(xiàn)給分類添加屬性。

關(guān)聯(lián)對(duì)象Runtime提供了下面幾個(gè)接口:

//關(guān)聯(lián)對(duì)象void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)//獲取關(guān)聯(lián)的對(duì)象id objc_getAssociatedObject(id object, const void *key)//移除關(guān)聯(lián)的對(duì)象void objc_removeAssociatedObjects(id object)

參數(shù)解釋

id object:被關(guān)聯(lián)的對(duì)象
const void *key:關(guān)聯(lián)的key,要求唯一
id value:關(guān)聯(lián)的對(duì)象
objc_AssociationPolicy policy:內(nèi)存管理的策略

內(nèi)存管理的策略

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. */};

下面實(shí)現(xiàn)一個(gè)UIView的Category添加自定義屬性defaultColor。

#import "ViewController.h"#import "objc/runtime.h"@interface UIView (DefaultColor)@property (nonatomic, strong) UIColor *defaultColor;@end@implementation UIView (DefaultColor)@dynamic defaultColor;static char kDefaultColorKey;- (void)setDefaultColor:(UIColor *)defaultColor { objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (id)defaultColor { return objc_getAssociatedObject(self, &kDefaultColorKey);}@end@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib.  UIView *test = [UIView new]; test.defaultColor = [UIColor blackColor]; NSLog(@"%@", test.defaultColor);}@end

打印結(jié)果:

2018-04-01 15:41:44.977732+0800 ocram[2053:63739] UIExtendedGrayColorSpace 0 1

打印結(jié)果來看,我們成功在分類上添加了一個(gè)屬性,實(shí)現(xiàn)了它的setter和getter方法。

通過關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的屬性的內(nèi)存管理也是有ARC管理的,所以我們只需要給定適當(dāng)?shù)膬?nèi)存策略就行了,不需要操心對(duì)象的釋放。

我們看看內(nèi)存測(cè)量對(duì)于的屬性修飾。

內(nèi)存策略 屬性修飾 描述
OBJC_ASSOCIATION_ASSIGN @property (assign) 或 @property (unsafe_unretained) 指定一個(gè)關(guān)聯(lián)對(duì)象的弱引用。
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) @property (nonatomic, strong) 指定一個(gè)關(guān)聯(lián)對(duì)象的強(qiáng)引用,不能被原子化使用。
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) 指定一個(gè)關(guān)聯(lián)對(duì)象的copy引用,不能被原子化使用。
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) 指定一個(gè)關(guān)聯(lián)對(duì)象的強(qiáng)引用,能被原子化使用。
OBJC_ASSOCIATION_COPY @property (atomic, copy) 指定一個(gè)關(guān)聯(lián)對(duì)象的copy引用,能被原子化使用。

 

方法魔法(Method Swizzling)方法添加和替換和KVO實(shí)現(xiàn)

方法添加

實(shí)際上添加方法剛才在講消息轉(zhuǎn)發(fā)的時(shí)候,動(dòng)態(tài)方法解析的時(shí)候就提到了。

//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
  • cls 被添加方法的類
  • name 添加的方法的名稱的SEL
  • imp 方法的實(shí)現(xiàn)。該函數(shù)必須至少要有兩個(gè)參數(shù),self,_cmd
  • 類型編碼

方法替換

下面實(shí)現(xiàn)一個(gè)替換ViewController的viewDidLoad方法的例子。

@implementation ViewController+ (void)load {  static dispatch_once_t onceToken;  dispatch_once(&onceToken, ^{    Class class = [self class];    SEL originalSelector = @selector(viewDidLoad);    SEL swizzledSelector = @selector(jkviewDidLoad);        Method originalMethod = class_getInstanceMethod(class,originalSelector);    Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);        //judge the method named swizzledMethod is already existed.    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));    // if swizzledMethod is already existed.    if (didAddMethod) {      class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));    }    else {      method_exchangeImplementations(originalMethod, swizzledMethod);    }  });}- (void)jkviewDidLoad {  NSLog(@"替換的方法");    [self jkviewDidLoad];}- (void)viewDidLoad {  NSLog(@"自帶的方法");    [super viewDidLoad];}@end

swizzling應(yīng)該只在+load中完成。 在 Objective-C 的運(yùn)行時(shí)中,每個(gè)類有兩個(gè)方法都會(huì)自動(dòng)調(diào)用。+load 是在一個(gè)類被初始裝載時(shí)調(diào)用,+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用的。兩個(gè)方法都是可選的,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用。

swizzling應(yīng)該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài),所以我們需要確保每個(gè)預(yù)防措施在運(yùn)行時(shí)都是可用的。原子操作就是這樣一個(gè)用于確保代碼只會(huì)被執(zhí)行一次的預(yù)防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once滿足了所需要的需求,并且應(yīng)該被當(dāng)做使用swizzling 的初始化單例方法的標(biāo)準(zhǔn)。

實(shí)現(xiàn)圖解如下圖。

iOS,Runtime

從圖中可以看出,我們通過swizzling特性,將selectorC的方法實(shí)現(xiàn)IMPc與selectorN的方法實(shí)現(xiàn)IMPn交換了,當(dāng)我們調(diào)用selectorC,也就是給對(duì)象發(fā)送selectorC消息時(shí),所查找到的對(duì)應(yīng)的方法實(shí)現(xiàn)就是IMPn而不是IMPc了。

KVO實(shí)現(xiàn)

全稱是Key-value observing,翻譯成鍵值觀察。提供了一種當(dāng)其它對(duì)象屬性被修改的時(shí)候能通知當(dāng)前對(duì)象的機(jī)制。再M(fèi)VC大行其道的Cocoa中,KVO機(jī)制很適合實(shí)現(xiàn)model和controller類之間的通訊。

KVO的實(shí)現(xiàn)依賴于 Objective-C 強(qiáng)大的 Runtime,當(dāng)觀察某對(duì)象 A 時(shí),KVO 機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象A當(dāng)前類的子類,并為這個(gè)新的子類重寫了被觀察屬性 keyPath 的 setter 方法。setter 方法隨后負(fù)責(zé)通知觀察對(duì)象屬性的改變狀況。

Apple 使用了 isa-swizzling 來實(shí)現(xiàn) KVO 。當(dāng)觀察對(duì)象A時(shí),KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:NSKVONotifying_A的新類,該類繼承自對(duì)象A的本類,且 KVO 為 NSKVONotifying_A 重寫觀察屬性的 setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象屬性值的更改情況。

NSKVONotifying_A 類剖析

NSLog(@"self->isa:%@",self->isa); NSLog(@"self class:%@",[self class]); 

在建立KVO監(jiān)聽前,打印結(jié)果為:

self->isa:A
self class:A

在建立KVO監(jiān)聽之后,打印結(jié)果為:

self->isa:NSKVONotifying_A
self class:A

在這個(gè)過程,被觀察對(duì)象的 isa 指針從指向原來的 A 類,被KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類NSKVONotifying_A 類,來實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽;

所以當(dāng)我們從應(yīng)用層面上看來,完全沒有意識(shí)到有新的類出現(xiàn),這是系統(tǒng)“隱瞞”了對(duì) KVO 的底層實(shí)現(xiàn)過程,讓我們誤以為還是原來的類。但是此時(shí)如果我們創(chuàng)建一個(gè)新的名為“NSKVONotifying_A”的類,就會(huì)發(fā)現(xiàn)系統(tǒng)運(yùn)行到注冊(cè) KVO 的那段代碼時(shí)程序就崩潰,因?yàn)橄到y(tǒng)在注冊(cè)監(jiān)聽的時(shí)候動(dòng)態(tài)創(chuàng)建了名為 NSKVONotifying_A 的中間類,并指向這個(gè)中間類了。

子類setter方法剖析

KVO 的鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:willChangeValueForKey:和 didChangeValueForKey: ,在存取數(shù)值的前后分別調(diào)用 2 個(gè)方法:

被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值即將變更;

當(dāng)改變發(fā)生后, didChangeValueForKey: 被調(diào)用,通知系統(tǒng)該keyPath 的屬性值已經(jīng)變更;之后,

observeValueForKey:ofObject:change:context:也會(huì)被調(diào)用。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。

KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:

- (void)setName:(NSString *)newName {    [self willChangeValueForKey:@"name"];  //KVO 在調(diào)用存取方法之前總調(diào)用    [super setValue:newName forKey:@"name"]; //調(diào)用父類的存取方法    [self didChangeValueForKey:@"name"];   //KVO 在調(diào)用存取方法之后總調(diào)用}

消息轉(zhuǎn)發(fā)(熱更新)解決Bug(JSPatch)

JSPatch 是一個(gè) iOS 動(dòng)態(tài)更新框架,只需在項(xiàng)目中引入極小的引擎,就可以使用 JavaScript 調(diào)用任何 Objective-C 原生接口,獲得腳本語言的優(yōu)勢(shì):為項(xiàng)目動(dòng)態(tài)添加模塊,或替換項(xiàng)目原生代碼動(dòng)態(tài)修復(fù) bug。

關(guān)于消息轉(zhuǎn)發(fā),前面已經(jīng)講到過了,消息轉(zhuǎn)發(fā)分為三級(jí),我們可以在每級(jí)實(shí)現(xiàn)替換功能,實(shí)現(xiàn)消息轉(zhuǎn)發(fā),從而不會(huì)造成崩潰。JSPatch不僅能夠?qū)崿F(xiàn)消息轉(zhuǎn)發(fā),還可以實(shí)現(xiàn)方法添加、替換能一系列功能。

實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和自動(dòng)解檔

原理描述:用runtime提供的函數(shù)遍歷Model自身所有屬性,并對(duì)屬性進(jìn)行encode和decode操作。
核心方法:在Model的基類中重寫方法:

- (id)initWithCoder:(NSCoder *)aDecoder {  if (self = [super init]) {    unsigned int outCount;    Ivar * ivars = class_copyIvarList([self class], &outCount);    for (int i = 0; i < outCount; i ++) {      Ivar ivar = ivars[i];      NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];      [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];    }  }  return self;}- (void)encodeWithCoder:(NSCoder *)aCoder {  unsigned int outCount;  Ivar * ivars = class_copyIvarList([self class], &outCount);  for (int i = 0; i < outCount; i ++) {    Ivar ivar = ivars[i];    NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];    [aCoder encodeObject:[self valueForKey:key] forKey:key];  }}

實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)

原理描述:用runtime提供的函數(shù)遍歷Model自身所有屬性,如果屬性在json中有對(duì)應(yīng)的值,則將其賦值。

核心方法:在NSObject的分類中添加方法

- (instancetype)initWithDict:(NSDictionary *)dict {  if (self = [self init]) {    //(1)獲取類的屬性及屬性對(duì)應(yīng)的類型    NSMutableArray * keys = [NSMutableArray array];    NSMutableArray * attributes = [NSMutableArray array];    /*     * 例子     * name = value3 attribute = T@"NSString",C,N,V_value3     * name = value4 attribute = T^i,N,V_value4     */    unsigned int outCount;    objc_property_t * properties = class_copyPropertyList([self class], &outCount);    for (int i = 0; i < outCount; i ++) {      objc_property_t property = properties[i];      //通過property_getName函數(shù)獲得屬性的名字      NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];      [keys addObject:propertyName];      //通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼      NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];      [attributes addObject:propertyAttribute];    }    //立即釋放properties指向的內(nèi)存    free(properties);    //(2)根據(jù)類型給屬性賦值    for (NSString * key in keys) {      if ([dict valueForKey:key] == nil) continue;      [self setValue:[dict valueForKey:key] forKey:key];    }  }  return self;}

以上就是Runtime應(yīng)用的一些場(chǎng)景,本文到此結(jié)束了。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)VEVB武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到IOS開發(fā)頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
99久久一区三区四区免费| 天堂社区 天堂综合网 天堂资源最新版| 无码人妻一区二区三区精品视频| 全网免费在线播放视频入口| 一本久道中文字幕精品亚洲嫩| wwwxxxx国产| 四虎精品一区二区永久在线观看| 成人免费网址在线| 国产av一区二区三区| 亚洲国产乱码最新视频| 日本午夜免费福利视频| 久久综合伊人77777| 久久精品视频9| 久久精品视频va| 菠萝蜜视频在线观看入口| 成人激情视频在线| 特级西西444www大精品视频免费看| 成人女保姆的销魂服务| 中文字幕在线免费| 亚洲一区二区三区四区五区中文| 青青草偷拍视频| 中文字幕日韩综合av| 午夜亚洲性色福利视频| 青青草国产免费自拍| 4444kk在线观看| 91网在线播放| 秋霞午夜理伦电影在线观看| 成人性生交大合| 欧美疯狂party性派对| 日韩av片免费在线观看| 欧美美女视频在线观看| 高清av中文在线字幕观看1| 宅男av一区二区三区| 大杳蕉精品视频在线观看| 黄色三级视频在线观看| 久久久久久婷| 卡一卡二卡三在线观看| 国产精品免费观看| 日本卡一卡2卡3卡4精品卡网站| 亚洲va综合va国产va中文| 成人直播在线观看| 国产精品111| 精品小视频在线观看| zzijzzij亚洲日本成熟少妇| xxxxhd欧美精品| 老熟妇仑乱一区二区av| 激情五月色综合亚洲小说| 九九热爱视频精品视频| 亚洲人成电影在线| 免费视频网站在线观看入口| 欧美一级黑人aaaaaaa做受| 日本一区二区网站| 日韩少妇高潮抽搐| 亚洲日穴在线视频| 丰满人妻一区二区三区大胸| 蜜臀av免费在线观看| 亚洲熟妇国产熟妇肥婆| 水中色av综合| 一区二区三区区四区播放视频在线观看| 亚洲三区在线| 在线播放一区二区精品视频| 久激情内射婷内射蜜桃| 动漫一区二区| 最近中文字幕免费在线观看| av资源免费观看| 国产高清一级毛片在线不卡| 欧美成人激情在线| 久久久之久亚州精品露出| 国产高清免费在线观看| 2022国产精品视频| 色诱女教师一区二区三区| 亚洲午夜高清视频| 岛国av在线网站| 天海翼在线播放| 91国产中文字幕| 性猛交富婆╳xxx乱大交一| 欧美精品一区二区三区中文字幕| 午夜免费日韩视频| 欧美午夜精品在线| а√天堂资源在线| 少妇高潮久久77777| 97在线观看免费视频| 18禁网站免费无遮挡无码中文| 手机福利小视频在线播放| 欧美aaaaa成人免费观看视频| 妺妺窝人体色www聚色窝仙踪| 国产日韩欧美中文字幕| 精品偷拍激情视频在线观看| 2021亚洲天堂| 毛片免费在线观看| 色一情一乱一伦| 成人免费福利片| 日韩欧美国产三级电影视频| 日韩免费福利视频| 亚洲国产免费看| 欧美精品黑人性xxxx| 欧美久久久久久一卡四| 欧美乱妇高清无乱码| 成人亚洲精品久久久久软件| 一区二区三欧美| 免费成人在线观看| 欧美视频免费一区二区三区| av网站免费在线看| 久久免费看av| 日韩毛片无码永久免费看| 国产清纯白嫩初高生在线观看91| 韩国三级hd两男一女| 黄色动漫在线免费观看| 亚洲人在线观看| 中文字幕一区图| 免费不卡欧美自拍视频| 国产黄色三级网站| bt欧美亚洲午夜电影天堂| 性生交大片免费全黄| 成人午夜在线影院| 国产精品国产三级国产aⅴ浪潮| 久久视频免费在线| 色综合天天狠天天透天天伊人| 欧美v国产在线一区二区三区| 91偷拍一区二区三区精品| 国产剧情在线观看一区二区| 日本三级电影网站| 欧美福利在线播放网址导航| 日韩欧美色综合| 亚洲国产福利视频| 欧美成人黄色| 亚洲国产日韩欧美在线观看| 精品久久久久一区二区| 99久久精品免费看国产免费软件| 这里只有精品视频在线观看| 蜜桃免费网站一区二区三区| 99精品网站| 我爱我色成人网| 桃色av一区二区| 欧美日韩在线免费观看| 亚洲美女爱爱视频| 国产三线在线| 精品久久久久久久久久久久久久| 正在播放久久| 美女做爰内谢全过程视频| 国产精品污www一区二区三区| 91在线丨porny丨国产| 少妇影院在线观看| 香蕉视频色在线观看| 成人做爰免费视频免费看| 中文字幕 日韩 欧美| 亚洲美女精品视频| 色操视频在线| 亚洲欧美在线第一页| 成人午夜大片免费观看| 国产美女视频一区二区三区| 国内自拍一区| 国产视频亚洲精品| 亚洲欧美日本一区| 高潮毛片无遮挡| 欧美成人免费视频a| 精品亚洲国产视频| 美国av一区二区三区| 国产精品成人免费精品自在线观看| 国产午夜亚洲精品羞羞网站| 销魂美女一区二区三区视频在线| 日本在线视频不卡| 精油按摩中文字幕久久| 美女视频第一区二区三区免费观看网站| 伦伦影院午夜日韩欧美限制| 国产无一区二区| 在线观看日韩av先锋影音电影院| www.亚洲在线| 日韩视频中文字幕在线观看| 国产盗摄——sm在线视频| 亚洲自拍欧美另类| 九九热免费视频| www黄色在线| 99久久精品国产毛片| 国产精品久久久久久户外露出| 久久午夜av| 国产一区二区影视| 日韩电影大片中文字幕| 懂色av.com| 成人三级毛片| 玖草视频在线观看| 国模冰冰炮一区二区| 国产婷婷一区二区三区久久| 免费高清在线观看免费| 亚洲色图久久久| 国产在线播放一区| 久久久久久久久一区| 中出嫩模无套| 精品人妻无码一区二区三区| 影音先锋亚洲电影| 97中文字幕在线观看| 欧美性xxxx禁忌| 国产欧美激情视频| 久草福利资源站| 日韩不卡高清视频| 国产热re99久久6国产精品| 欧美成人三级视频网站| 久久久精品美女| 99只有精品| 国产精品久久色| 亚洲国产精品无码久久| 调教视频免费在线观看| www.国产.com| 免费在线观看a级片| 欧美精品日韩www.p站| 91精品日本| 最新热久久免费视频| 亚洲欧美网站在线观看| 一本久道高清无码视频| 日韩一区二区三区视频在线观看| 日本一区二区网站| 精品人伦一区二区| 一级淫片免费看| 1024在线看片| 亚洲免费成人av电影| 亚洲欧美激情另类| 欧美理论片在线| 欧美熟乱第一页| 日本10禁啪啪无遮挡免费一区二区| 亚洲www.| av成人在线看| 91亚洲视频在线观看| 亚洲va在线| 国产麻豆精品高清在线播放| 欧美成人aaaaⅴ片在线看| 久久丁香四色| 最新的欧美黄色| 国产精品久久福利| 中文字幕网站在线观看| 99在线热播精品免费99热| 国产精品久久无码| 国产精品中文字幕亚洲欧美| 亚洲视频专区在线| 欧美一区二区三区高清视频| 精品黑人一区二区三区| 六月丁香综合在线视频| 51蜜桃传媒精品一区二区| 在线看黄网址| 97欧美精品一区二区三区| 99国产精品欲| 成人免费视频视频在| 国产精彩精品视频| a视频在线播放| 久久精品久久久久久| 亚洲香蕉av在线一区二区三区| 给我看免费高清在线观看| 亚洲天堂男人的天堂| 日韩欧美中文字幕电影| 婷婷丁香久久五月婷婷| av一区二区三区免费观看| 欧美日本国产视频| 国产欧美一区二区精品性色超碰| 久久久久久一区| 国产午夜亚洲精品羞羞网站| xxxx影院| 香蕉在线观看视频| av亚洲在线观看| 夜夜春成人影院| 亚洲精品网站在线| 日韩精品在线不卡| 久草在线资源福利站| av高清资源| 成年人视频免费在线播放| 亚洲精品永久www嫩草| ●精品国产综合乱码久久久久| 高清电影一区| 91精品视频播放| 亚洲国产精品久久久久婷婷软件| 日韩电影在线一区二区| 欧美久久一级| 啊啊啊啊啊啊啊视频在线播放| 欧美精品aⅴ在线视频| xxxx.国产| 亚洲精品v欧美精品v日韩精品| 久久99视频精品| 一级二级三级欧美| 欧美一级视频免费看| 一区三区二区视频| 欧美一级久久久久久久久大| 翔田千里一区二在线观看| 午夜成年女人毛片免费观看| 99riav一区二区三区| 国产成人精品电影| 日本少妇色视频| 欧美 日韩 国产 成人 在线观看| 外国成人直播| 久久国产精品一区| 国产成人av网址| 欧美少妇性生活视频| 中文字幕日本最新乱码视频| 欧美激情精品久久久久| 综合干狼人综合首页| 国产精品亚洲综合天堂夜夜| 国模精品视频一区二区三区| wwww在线观看免费视频| 女人黄色免费在线观看| xxxx一级片| 国产成人精品网站| 久久99国产精品99久久| 国产精品色婷婷视频| 日韩美女一区二区三区四区| 日本人体一区二区| 欧美成人黑人xx视频免费观看| 18成人在线观看| 99久久这里只有精品| 国产女人在线观看| 中文字幕av一区 二区| 日韩久久中文字幕| 国产亚洲aⅴaaaaaa毛片| 日本福利视频| 国产毛片毛片| 狠狠操第一页| 国产欧美日韩激情| 欧美性感美女一区二区| 成人久久网站| 亚洲第一导航| 欧美视频一区在线观看| 九九热精品视频在线| 精品国产伦一区二区三| 在线免费毛片| 欧美日韩在线精品一区二区三区| 色爱av美腿丝袜综合粉嫩av| 在线观看日韩国产| 精品欧美一区二区三区精品久久| 成片免费观看视频| 亚洲字幕久久| 亚洲熟妇无码av| 国产成人综合一区|