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

首頁 > 系統 > iOS > 正文

iOS 中weak的實現代碼示例

2019-10-21 18:41:16
字體:
來源:轉載
供稿:網友

只要學過 iOS 的人,都會對 strong、weak、copy等關鍵字應該都會很熟悉。weak 屬性關鍵字就是弱引用,它不會增加引用計數但卻能保證指針的安全訪問,在對象釋放后置為 nil,從而避免錯誤的內存訪問。主要為了解決循環引用的問題。

接下來,我們會從 objc 庫中的 NSObject.mm、 objc-weak.h 以及 objc-weak.mm 文件出發,去具體了解 weak 的實現過程。

weak 的內部結構

Runtime 維護了一個weak表,用于存儲指向某個對象的所有weak指針。weak 表是由單個自旋鎖管理的散列表。
weak表其實是一個hash表,key 是所指對象的指針,value是weak指針的地址(這個地址的值是所指向對象的地址)數組。

在下面涉及的源碼中,我們會看到以下幾個類型:

sideTable、weak_table_t、weak_entry_t 這幾個結構體。

struct SideTable {  // 自旋鎖,用來保證線程安全  spinlock_t slock;  // 引用計數表  RefcountMap refcnts;  // weak 表  weak_table_t weak_table;  ...};

SideTable,它用來管理引用計數表和 weak 表,并使用 spinlock_lock 自旋鎖來防止操作表結構時可能的競態條件。它用一個 64*128 大小的uint8_t 靜態數組作為 buffer 來保存所有的 SideTable 實例。這個結構體里面包含三個變量,第一個spinlock_t,它是一個自旋鎖,用來保證線程安全。第二個RefcountMap,是引用計數表,每個對象的引用計數保存在全局的引用計數表中,一個對象地址對應一個引用計數。第三個就是我們接下來要講的 weak 表,所有的 weak 變量會被加入到全局的weak表中,表的 key 是 weak 修飾的變量指向的對象, value 值就是 weak 修飾的變量。接下來,我們具體看看這個 weak 表

struct weak_table_t {  // 保存了所有指向指定對象的 weak 指針  weak_entry_t *weak_entries;  // 存儲空間,即 entries 的數目  size_t  num_entries;  // 參與判斷引用計數輔助量  uintptr_t mask;  // hash key 最大偏移量  uintptr_t max_hash_displacement;};

這個是全局弱引用的 hash 表。它的作用就是在對象執行 dealloc 的時候將所有指向該對象的 weak 指針的值設為 nil, 避免懸空指針。它使用不定類型對象的地址的 hash 化后的數值作為 key,用 weak_entry_t 類型的結構體對象作為 value。其中 weak_entry_t 是存儲在弱引用表中的一個內部結構體,它負責維護和存儲指向一個對象的所有弱引用 hash 表。其定義如下:

// 存儲在弱引用表中的一個內部結構體#define WEAK_INLINE_COUNT 4struct weak_entry_t {  DisguisedPtr<objc_object> referent;         // 封裝 objc_object 指針,即 weak 修飾的變量指向的對象  union {    struct {      weak_referrer_t *referrers;      uintptr_t    out_of_line : 1;      // LSB 最低有效元 當標志位為0時,增加引用表指針緯度,                            // 當其為0的時候, weak_referrer_t 成員將擴展為靜態數組型的 hash table      uintptr_t    num_refs : PTR_MINUS_1;  // 引用數值,這里記錄弱引用表中引用有效數字,即里面元素的數量      uintptr_t    mask;      uintptr_t    max_hash_displacement;   // hash 元素上限閥值    };    struct {      // out_of_line=0 is LSB of one of these (don't care which)      weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];       };  };};

在 weak_entry_t 的結構中, DisguisedPtr<objc_object> 是對 objc_object * 指針及其一些操作進行的封裝,目的就是為了讓它給人看起來不會有內存泄露的樣子,其內容可以理解為對象的內存地址。out_of-line 成員為最低有效位,當其為 0 的時候,weak_referrer_t 成員將擴展為一個靜態數組型的 hash table。其實 weak_referrer 是objc_objcet 的別名,定義如下:typedef objc_object ** weak_referrer_t;

它通過一個二維指針地址偏移,用下標作為 hash 的 key,做成了一個弱引用散列。

每個對象的 SideTable 中的 weak_table_t 都是全局 weak 表的入口,以引用計數對象為鍵找到其所記錄的 weak 修飾的對象。weak_entry_t 中的 referrers 有兩種形式,當 out_of_line 為 0 的時候,referrers 是一個靜態數組型的表,數組大小默認為 WEAK_INLINE_COUNT 大小,當 out_of_line 不為 0 的時候,referrers 是一個動態數組,內容隨之增加。

weak 實現原理的過程

當我們用 weak 修飾屬性的時候,它是怎么實現當所引用的對象被廢棄的時候,變量置為 nil,我們來探究一下。

{  id obj1 = [[NSObject alloc] init];  id __weak obj2 = obj1;}

經過編譯期轉換之后,以上代碼會變成下面這樣

id obj2;
objc_initWeak(&obj2, obj1);
objc_destroyWeak(&obj2);

我們發現,weak 修飾符變量是通過 objc_initWeak 函數來初始化的,在變量作用域結束的時候通過 objc_destroyWeak 函數來釋放該變量的。接下來,我們看看這兩個函數的源碼。

id objc_initWeak(id *location, id newObj){  // 查看對象實例是否有效  // 無效對象直接導致指針釋放  if (!newObj) {    *location = nil;    return nil;  }  // 這里傳遞了三個 bool 數值  // 使用 template 進行常量參數傳遞是為了優化性能  return storeWeak<false/*old*/, true/*new*/, true/*crash*/>    (location, (objc_object*)newObj);}
void objc_destroyWeak(id *location){  (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>    (location, nil);}

對這兩個方法的分析后,我們發現它們都調用了storeWeak 這個函數,但是兩個方法傳入的參數卻稍有不同。

init 方法中,第一個參數為 weak 修飾的變量,第二個參數為引用計數對象。但在 destoryWeak 函數,第一參數依舊為 weak 修飾的變量,第二個參數為 nil。那這塊傳入不同的參數到底代表什么,我們繼續分析 storeWeak 這個函數。

// 更新一個弱引用變量// 如果 HaveOld 是 true, 變量是個有效值,需要被及時清理。變量可以為 nil。// 如果 HaveNew 是 true, 需要一個新的 value 來替換變量。變量可以為 nil// 如果crashifdeallocation 是 ture ,那么如果 newObj 是 deallocating,或者 newObj 的類不支持弱引用,則該進程就會停止。// 如果crashifdeallocation 是 false,那么 nil 會被存儲。template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>static id storeWeak(id *location, objc_object *newObj){  assert(HaveOld || HaveNew);  if (!HaveNew) assert(newObj == nil);  Class previouslyInitializedClass = nil;  id oldObj;    // 創建新舊散列表  SideTable *oldTable;  SideTable *newTable;  // Acquire locks for old and new values.  // 獲得新值和舊值的鎖存位置 (用地址作為唯一標示)  // Order by lock address to prevent lock ordering problems.  // 通過地址來建立索引標志,防止桶重復  // Retry if the old value changes underneath us.  // 下面指向的操作會改變舊值 retry:  if (HaveOld) {  // 如果 HaveOld 為 true ,更改指針,獲得以 oldObj 為索引所存儲的值地址    oldObj = *location;    oldTable = &SideTables()[oldObj];  } else {    oldTable = nil;  }  if (HaveNew) {  // 獲得以 newObj 為索引所存儲的值對象    newTable = &SideTables()[newObj];  } else {    newTable = nil;  }  // 對兩個 table 進行加鎖操作,防止多線程中競爭沖突  SideTable::lockTwo<HaveOld, HaveNew>(oldTable, newTable);// location 應該與 oldObj 保持一致,如果不同,說明當前的 location 已經處理過 oldObj 可是又被其他線程所修改, 保證線程安全,這個判斷用來避免線程沖突重處理問題  if (HaveOld && *location != oldObj) {    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);    goto retry;  }  // Prevent a deadlock between the weak reference machinery  // and the +initialize machinery by ensuring that no   // weakly-referenced object has an un-+initialized isa.  // 防止弱引用之間發生死鎖,并且通過 +initialize 初始化構造器保證所有弱引用的 isa 非空指向  if (HaveNew && newObj) {    // 獲得新對象的 isa 指針    Class cls = newObj->getIsa();    // 判斷 isa 非空且已經初始化    if (cls != previouslyInitializedClass &&       !((objc_class *)cls)->isInitialized())     {      // 對兩個表解鎖      SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);      _class_initialize(_class_getNonMetaClass(cls, (id)newObj));      // If this class is finished with +initialize then we're good.      // If this class is still running +initialize on this thread       // (i.e. +initialize called storeWeak on an instance of itself)      // then we may proceed but it will appear initializing and       // not yet initialized to the check above.      // Instead set previouslyInitializedClass to recognize it on retry.      // 如果該類已經完成執行 +initialize 方法是最好的,如果該類 + initialize 在線程中,例如 +initialize 正在調用storeWeak 方法,那么則需要手動對其增加保護策略,并設置 previouslyInitializedClass 指針進行標記然后重新嘗試      previouslyInitializedClass = cls;      goto retry;    }  }  // Clean up old value, if any. 清除舊值  if (HaveOld) {    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);  }  // Assign new value, if any. 分配新值  if (HaveNew) {    newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table,                            (id)newObj, location,                            CrashIfDeallocating);    // weak_register_no_lock returns nil if weak store should be rejected    // 如果弱引用被釋放則該方法返回 nil    // Set is-weakly-referenced bit in refcount table.    // 在引用計數表中設置弱引用標記位    if (newObj && !newObj->isTaggedPointer()) {      newObj->setWeaklyReferenced_nolock();    }    // Do not set *location anywhere else. That would introduce a race.    *location = (id)newObj;  }  else {    // No new value. The storage is not changed.  }    SideTable::unlockTwo<HaveOld, HaveNew>(oldTable, newTable);  return (id)newObj;}

以上就是 store_weak 這個函數的實現,它主要做了以下幾件事:

  1. 聲明了新舊散列表指針,因為 weak 修飾的變量如果之前已經指向一個對象,然后其再次改變指向另一個對象,那么按理來說我們需要釋放舊對象中該 weak 變量的記錄,也就是要將舊記錄刪除,然后在新記錄中添加。這里的新舊散列表就是這個作用。
  2. 根據新舊變量的地址獲取相應的 SideTable
  3. 對兩個表進行加鎖操作,防止多線程競爭沖突
  4. 進行線程沖突重處理判斷
  5. 判斷其 isa 是否為空,為空則需要進行初始化
  6. 如果存在舊值,調用 weak_unregister_no_lock 函數清除舊值
  7. 調用 weak_register_no_lock 函數分配新值
  8. 解鎖兩個表,并返回第二參數

初始化弱引用對象流程一覽

弱引用的初始化,從上文的分析可以看出,主要的操作部分就是在弱引用表的取鍵、查詢散列、創建弱引用等操作,可以總結出如下的流程圖:

iOS,weak

舊對象解除注冊操作 weak_unregister_no_lock

void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,             id *referrer_id){  objc_object *referent = (objc_object *)referent_id;  objc_object **referrer = (objc_object **)referrer_id;  weak_entry_t *entry;  if (!referent) return;  if ((entry = weak_entry_for_referent(weak_table, referent))) {    remove_referrer(entry, referrer);    bool empty = true;    if (entry->out_of_line && entry->num_refs != 0) {      empty = false;    }    else {      for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {        if (entry->inline_referrers[i]) {          empty = false;           break;        }      }    }    if (empty) {      weak_entry_remove(weak_table, entry);    }  }  // Do not set *referrer = nil. objc_storeWeak() requires that the   // value not change.}

該方法主要作用是將舊對象在 weak_table 中接觸 weak 指針的對應綁定。根據函數名,稱之為解除注冊操作。

來看看這個函數的邏輯。首先參數是 weak_table_t 表,鍵和值。聲明 weak_entry_t 變量,如果key,也就是引用計數對象為空,直接返回。根據全局入口表和鍵獲取對應的 weak_entry_t 對象,也就是 weak 表記錄。獲取到記錄后,將記錄表以及 weak 對象作為參數傳入 remove_referrer 函數中,這個函數就是解除操作。然后判斷這個 weak 記錄是否為空,如果為空,從全局記錄表中清除相應的引用計數對象的 weak 記錄表。

接下來,我們了解一下,如何獲取這個 weak_entry_t 這個變量。

static weak_entry_t *weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent){  assert(referent);  weak_entry_t *weak_entries = weak_table->weak_entries;  if (!weak_entries) return nil;  size_t index = hash_pointer(referent) & weak_table->mask;  size_t hash_displacement = 0;  while (weak_table->weak_entries[index].referent != referent) {    index = (index+1) & weak_table->mask;    hash_displacement++;    if (hash_displacement > weak_table->max_hash_displacement) {      return nil;    }  }  return &weak_table->weak_entries[index];}

這個函數的邏輯就是先獲取全局 weak 表入口,然后將引用計數對象的地址進行 hash 化后與 weak_table->mask 做與操作,作為下標,在全局 weak 表中查找,若找到,返回這個對象的 weak 記錄表,若沒有,返回nil。

再來了解一下解除對象的函數:

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer){  if (! entry->out_of_line) {    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {      if (entry->inline_referrers[i] == old_referrer) {        entry->inline_referrers[i] = nil;        return;      }    }    _objc_inform("Attempted to unregister unknown __weak variable "           "at %p. This is probably incorrect use of "           "objc_storeWeak() and objc_loadWeak(). "           "Break on objc_weak_error to debug./n",            old_referrer);    objc_weak_error();    return;  }  size_t index = w_hash_pointer(old_referrer) & (entry->mask);  size_t hash_displacement = 0;  while (entry->referrers[index] != old_referrer) {    index = (index+1) & entry->mask;    hash_displacement++;    if (hash_displacement > entry->max_hash_displacement) {      _objc_inform("Attempted to unregister unknown __weak variable "             "at %p. This is probably incorrect use of "             "objc_storeWeak() and objc_loadWeak(). "             "Break on objc_weak_error to debug./n",              old_referrer);      objc_weak_error();      return;    }  }  entry->referrers[index] = nil;  entry->num_refs--;}

這個函數傳入的是 weak 對象,當 out_of_line 為0 時,遍歷數組,找到對應的對象,置nil,如果未找到,報錯并返回。當 out_of_line 不為0時,根據對象的地址 hash 化并和 mask 做與操作作為下標,查找相應的對象,若沒有,報錯并返回,若有,相應的置為 nil,并減少元素數量,即 num_refs 減 1。

新對象添加注冊操作 weak_register_no_lock

id weak_register_no_lock(weak_table_t *weak_table, id referent_id,            id *referrer_id, bool crashIfDeallocating){  objc_object *referent = (objc_object *)referent_id;  objc_object **referrer = (objc_object **)referrer_id;  if (!referent || referent->isTaggedPointer()) return referent_id;  // ensure that the referenced object is viable  bool deallocating;  if (!referent->ISA()->hasCustomRR()) {    deallocating = referent->rootIsDeallocating();  }  else {    BOOL (*allowsWeakReference)(objc_object *, SEL) =       (BOOL(*)(objc_object *, SEL))      object_getMethodImplementation((id)referent,                       SEL_allowsWeakReference);    if ((IMP)allowsWeakReference == _objc_msgForward) {      return nil;    }    deallocating =      ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);  }  if (deallocating) {    if (crashIfDeallocating) {      _objc_fatal("Cannot form weak reference to instance (%p) of "            "class %s. It is possible that this object was "            "over-released, or is in the process of deallocation.",            (void*)referent, object_getClassName((id)referent));    } else {      return nil;    }  }  // now remember it and where it is being stored  weak_entry_t *entry;  if ((entry = weak_entry_for_referent(weak_table, referent))) {    append_referrer(entry, referrer);  }   else {    weak_entry_t new_entry;    new_entry.referent = referent;    new_entry.out_of_line = 0;    new_entry.inline_referrers[0] = referrer;    for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {      new_entry.inline_referrers[i] = nil;    }    weak_grow_maybe(weak_table);    weak_entry_insert(weak_table, &new_entry);  }  // Do not set *referrer. objc_storeWeak() requires that the   // value not change.  return referent_id;}

一大堆 if-else, 主要是為了判斷該對象是不是 taggedPoint 以及是否正在調用 dealloca 等。下面操作開始,同樣是先獲取 weak 表記錄,如果獲取到,則調用 append_referrer 插入對象,若沒有,則新建一個 weak 表記錄,默認為 out_of_line,然后將新對象放到 0 下標位置,其他位置置為 nil 。下面兩個函數 weak_grow_maybe 是用來判斷是否需要重申請內存重 hash,weak_entry_insert 函數是用來將新建的 weak 表記錄插入到全局 weak 表中。插入時同樣是以對象地址的 hash 化和 mask 值相與作為下標來記錄的。

接下來看看 append_referrer 函數,源代碼如下:

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer){  if (! entry->out_of_line) {    // Try to insert inline.    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {      if (entry->inline_referrers[i] == nil) {        entry->inline_referrers[i] = new_referrer;        return;      }    }    // Couldn't insert inline. Allocate out of line.    weak_referrer_t *new_referrers = (weak_referrer_t *)      calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));    // This constructed table is invalid, but grow_refs_and_insert    // will fix it and rehash it.    for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {      new_referrers[i] = entry->inline_referrers[I];    }    entry->referrers = new_referrers;    entry->num_refs = WEAK_INLINE_COUNT;    entry->out_of_line = 1;    entry->mask = WEAK_INLINE_COUNT-1;    entry->max_hash_displacement = 0;  }  assert(entry->out_of_line);  if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {    return grow_refs_and_insert(entry, new_referrer);  }  size_t index = w_hash_pointer(new_referrer) & (entry->mask);  size_t hash_displacement = 0;  while (entry->referrers[index] != NULL) {    index = (index+1) & entry->mask;    hash_displacement++;  }  if (hash_displacement > entry->max_hash_displacement) {    entry->max_hash_displacement = hash_displacement;  }  weak_referrer_t &ref = entry->referrers[index];  ref = new_referrer;  entry->num_refs++;}

當 out_of_line 為 0,并且靜態數組里面還有位置存放,那么直接存放并返回。如果沒有位置存放,則升級為動態數組,并加入。如果 out_of_line 不為 0,先判斷是否需要擴容,然后同樣的,使用對象地址的 hash 化和 mask 做與操作作為下標,找到相應的位置并插入。

對象的銷毀以及 weak 的置 nil 實現

釋放時,調用clearDeallocating函數。clearDeallocating 函數首先根據對象地址獲取所有weak指針地址的數組,然后遍歷這個數組把其中的數據設為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。

當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程如下:

  1. 調用 objc_release
  2. 因為對象的引用計數為0,所以執行dealloc
  3. 在dealloc 中,調用了_objc_rootDealloc 函數
  4. 在 _objc_rootDealloc 中,調用了 objec_dispose 函數
  5. 調用objc_destructInstance
  6. 最后調用 objc_clear_deallocating

objc_clear_deallocating的具體實現如下:

void objc_clear_deallocating(id obj) {  assert(obj);  assert(!UseGC);  if (obj->isTaggedPointer()) return;  obj->clearDeallocating();}

這個函數只是做一些判斷以及更深層次的函數調用,

void objc_object::sidetable_clearDeallocating(){  SideTable& table = SideTables()[this];  // clear any weak table items  // clear extra retain count and deallocating bit  // (fixme warn or abort if extra retain count == 0 ?)  table.lock();  // 迭代器  RefcountMap::iterator it = table.refcnts.find(this);  if (it != table.refcnts.end()) {    if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {      weak_clear_no_lock(&table.weak_table, (id)this);    }    table.refcnts.erase(it);  }  table.unlock();}

我們可以看到,在這個函數中,首先取出對象對應的SideTable實例,如果這個對象有關聯的弱引用,則調用weak_clear_no_lock來清除對象的弱引用信息,我們在來深入一下,

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {  objc_object *referent = (objc_object *)referent_id;  weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);  if (entry == nil) {    /// XXX shouldn't happen, but does with mismatched CF/objc    //printf("XXX no entry for clear deallocating %p/n", referent);    return;  }  // zero out references  weak_referrer_t *referrers;  size_t count;  if (entry->out_of_line) {    referrers = entry->referrers;    count = TABLE_SIZE(entry);  }   else {    referrers = entry->inline_referrers;    count = WEAK_INLINE_COUNT;  }  for (size_t i = 0; i < count; ++i) {    objc_object **referrer = referrers[I];    if (referrer) {      if (*referrer == referent) {        *referrer = nil;      }      else if (*referrer) {        _objc_inform("__weak variable at %p holds %p instead of %p. "               "This is probably incorrect use of "               "objc_storeWeak() and objc_loadWeak(). "               "Break on objc_weak_error to debug./n",                referrer, (void*)*referrer, (void*)referent);        objc_weak_error();      }    }  }  weak_entry_remove(weak_table, entry);}

這個函數根據 out_of_line 的值,取得對應的記錄表,然后根據引用計數對象,將相應的 weak 對象置 nil。最后清除相應的記錄表。

通過上面的描述,我們基本能了解一個weak引用從生到死的過程。從這個流程可以看出,一個weak引用的處理涉及各種查表、添加與刪除操作,還是有一定消耗的。所以如果大量使用__weak變量的話,會對性能造成一定的影響。那么,我們應該在什么時候去使用weak呢?《Objective-C高級編程》給我們的建議是只在避免循環引用的時候使用__weak修飾符。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到IOS開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧美成人免费视频| 欧美黄色小视频| 欧美日韩一区二区精品| 色综合久久久久久中文网| 久久人人爽人人爽人人片av高清| 成人亲热视频网站| 国产国产精品人在线视| 亚洲第一黄色网| 精品视频中文字幕| 欧美一区二三区| 欧美高清自拍一区| 4388成人网| 国产成人综合一区二区三区| 亚洲精品色婷婷福利天堂| 亚洲最大av网| 亚洲视频在线视频| 国产免费久久av| 久久久噜久噜久久综合| 国产成人免费91av在线| 亚洲第一天堂无码专区| 欧美另类第一页| 92国产精品久久久久首页| 亚洲精品欧美日韩| 亚洲国产精品va在线观看黑人| 97国产在线视频| 亚洲午夜女主播在线直播| 亚洲精品xxx| 亚洲精品国产综合区久久久久久久| 51色欧美片视频在线观看| 日韩av电影在线网| 日韩国产在线播放| 国内精品在线一区| 亚洲日韩欧美视频一区| 日韩在线观看免费av| 亚洲一区制服诱惑| 久久91亚洲精品中文字幕| 亚洲欧美日韩视频一区| 国产精品精品视频一区二区三区| 中文字幕精品影院| 欧美中文字幕在线视频| 国产欧美最新羞羞视频在线观看| 亚洲自拍偷拍一区| 亚洲男人的天堂在线| 亚洲精品不卡在线| 国产在线a不卡| 久久国产精品久久久久久| 欧美影院成年免费版| 日韩av免费一区| 国产午夜精品免费一区二区三区| 福利视频一区二区| 久久精品视频一| 欧美日韩免费区域视频在线观看| 91免费在线视频网站| 国语自产偷拍精品视频偷| 91精品啪在线观看麻豆免费| 久久久久久久久久婷婷| 日韩av不卡在线| 91久久在线视频| 日本亚洲欧美三级| 亚洲美女精品久久| 美女性感视频久久久| 午夜精品在线视频| 日韩在线视频免费观看高清中文| 日韩国产激情在线| 68精品国产免费久久久久久婷婷| 欧美丰满少妇xxxxx做受| 欧美日韩精品在线播放| 亚洲iv一区二区三区| 国产999精品久久久| 欧美激情一区二区久久久| 国内精久久久久久久久久人| 亚洲区一区二区| 久热国产精品视频| 欧美日韩裸体免费视频| 欧美激情视频免费观看| 亚洲一区二区少妇| 日本午夜精品理论片a级appf发布| 91在线精品视频| 欧美日韩ab片| 日韩精品在线免费播放| 欧美一性一乱一交一视频| 中文字幕一区日韩电影| 国产精品一区二区3区| 欧美片一区二区三区| 亚洲精品久久久久久久久久久久久| 国产欧美一区二区三区在线看| 国产精品夜间视频香蕉| 日韩女在线观看| 国产日产久久高清欧美一区| 992tv成人免费视频| 青草青草久热精品视频在线观看| 欧洲s码亚洲m码精品一区| 久久亚洲影音av资源网| 国产欧美欧洲在线观看| 国产精品v日韩精品| 热99精品只有里视频精品| 成人免费看片视频| 亚洲第一在线视频| 国模精品视频一区二区| 亚洲精品福利免费在线观看| 久久av红桃一区二区小说| 国产视频999| 国产v综合v亚洲欧美久久| 日韩美女视频免费在线观看| 91精品国产高清久久久久久91| 91精品国产色综合久久不卡98口| 亚洲一区二区在线播放| 日本一本a高清免费不卡| 91精品美女在线| 国产精品久久久久久久久久ktv| 欧美肥臀大乳一区二区免费视频| 欧美大胆在线视频| 国产精自产拍久久久久久蜜| 91精品国产网站| 国产精品精品久久久久久| 亚洲高清免费观看高清完整版| 欧美日韩国产123| 国产免费一区二区三区在线观看| 欧美中文在线观看国产| 91最新在线免费观看| 国产婷婷成人久久av免费高清| 国产精品27p| 亚洲国产成人精品女人久久久| 日韩中文字幕第一页| 日韩精品欧美激情| 综合网中文字幕| 欧美日韩裸体免费视频| 国产日韩欧美另类| 亚洲国产另类 国产精品国产免费| 日韩精品亚洲精品| 国产精品入口尤物| 日韩av在线看| 久久综合伊人77777尤物| 亚洲少妇激情视频| 精品久久久久久| 欧美xxxx做受欧美| 欧美亚洲成人精品| 国产偷亚洲偷欧美偷精品| 欧美午夜精品伦理| 亚洲理论在线a中文字幕| 久久国产精品视频| 国产日韩精品电影| 日韩大片在线观看视频| 亚洲影院污污.| 日韩暖暖在线视频| 亚洲二区在线播放视频| 欧美午夜片在线免费观看| 亚洲天堂影视av| 日韩中文字幕亚洲| 亚洲自拍欧美色图| 欧美大片欧美激情性色a∨久久| 亚洲国产精品va在看黑人| 亚洲美女免费精品视频在线观看| 欧美午夜无遮挡| 久久久噜噜噜久久中文字免| 欧美日韩久久久久| 亚洲国产精彩中文乱码av在线播放| 最好看的2019的中文字幕视频| 亚洲一区二区福利| 91国内产香蕉| 欧美另类极品videosbest最新版本| 欧美精品一区三区| 欧美成年人视频网站欧美| 国产精品女人网站|