[toc]
參考
https://blog.csdn.net/future_one/article/details/81606895
https://www.cnblogs.com/guohai-stronger/p/10161870.html
http://www.itdecent.cn/p/2b12666b351f
問(wèn)答
weak作用?
weak 是弱引用, 用weak來(lái)修飾、描述所引用對(duì)象的計(jì)數(shù)器并不會(huì)加1, 而且weak會(huì)在引用對(duì)象被釋放的時(shí)候自動(dòng)置為nil, 這也就避免了野指針訪問(wèn)壞內(nèi)存而引起崩潰的情況,
weak也可以解決循環(huán)引用。
為什么修飾代理使用 weak 而不是用assign?
assign 可用來(lái)修飾基本數(shù)據(jù)類型, 也可修飾OC的對(duì)象, 但如果用 assign 修飾對(duì)象類型指向的是一個(gè)強(qiáng)指針, 當(dāng)指向的這個(gè)指針釋放之后, 它仍指向這塊內(nèi)存, 必須要手動(dòng)給置為nil, 否則會(huì)產(chǎn)生野指針, 如果還通過(guò)此指針操作那塊內(nèi)存, 會(huì)導(dǎo)致 EXC_BAD_ACCESS 錯(cuò)誤, 調(diào)用了已經(jīng)被釋放的內(nèi)存空間;
而 weak 只能用來(lái)修飾OC對(duì)象, 而且相比assign比較安全, 如果指向的對(duì)象消失了, 那么它會(huì)自動(dòng)置為nil, 不會(huì)導(dǎo)致野指針。
weak 本質(zhì)?
ARC幫我們做了什么?
ARC 是 LLVM + Runtime 互相協(xié)作的結(jié)果
開(kāi)啟ARC后, LLVM編譯器會(huì)自動(dòng)幫我們?cè)谙鄳?yīng)位置生成 release、retain、autorelease
在作用域 (大括號(hào)) 結(jié)束的位置添加 release
弱引用這種, 是通過(guò)runtime處理的
編譯解析
OC代碼
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
C++代碼
int main(){
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) obj1 = obj;
}
編譯之后的weak, 通過(guò) objc_ownership(weak) 實(shí)現(xiàn) weak 方法, objc_ownership 字面意思是:獲得對(duì)象的所有權(quán), 是對(duì)對(duì)象weak的初始化的一個(gè)操作。
通過(guò) weak 編譯解析, 可以看出 weak 是通過(guò) runtime 初始化并維護(hù)的;
weak 和 strong 都是 OC ARC 的修飾詞, 而 strong 是通過(guò) runtime 維護(hù)的一個(gè)自動(dòng)計(jì)數(shù)表結(jié)構(gòu)。
結(jié)構(gòu)
weak_table_t
- weak_table_t 存儲(chǔ)在
struct SideTable中。(參考《isa指針》) - weak_table_t 是 Runtime 維護(hù)的一個(gè) weak 引用的全局hash表, 用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針?!?
- key: 所指向?qū)ο蟮牡刂?/li>
- value: weak_entry_t 類型結(jié)構(gòu)體數(shù)組(weak指針的數(shù)組) 。 value 是數(shù)組的原因是: 因?yàn)橐粋€(gè)對(duì)象可能被多個(gè)弱引用指針指向 ★
weak 指針的地址值是所指對(duì)象指針的地址
// objc-weak.h
/**
* The global weak references table. 全局的, 內(nèi)部維護(hù)了一個(gè)哈希表 ★
* Stores object ids as keys, and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; // 保存了所有指向指定對(duì)象的weak指針 weak_entries的對(duì)象
size_t num_entries; // 弱引用數(shù)量
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entry_t
weak 全局表 weak_table_t 中的存儲(chǔ) weak 定義的對(duì)象的表結(jié)構(gòu) weak_entry_t,
weak_entry_t 是存儲(chǔ)在弱引用表中的一個(gè)內(nèi)部結(jié)構(gòu)體, 它負(fù)責(zé)維護(hù)和存儲(chǔ)指向一個(gè)對(duì)象的所有弱引用hash表
// 單個(gè)弱引用, 存放在哈希表中
struct weak_entry_t {
// 對(duì)泛型對(duì)象的指針做了一個(gè)封裝, 通過(guò)這個(gè)泛型類來(lái)解決內(nèi)存泄漏的問(wèn)題。
// 相當(dāng)于 objc_object *referent;
DisguisedPtr<objc_object> referent;
union {
struct {
// weak_referrer_t 是 objc_object ** 的別名, 通過(guò)一個(gè)二維指針地址偏移, 用下標(biāo)作為 hash 的 key, 做成了一個(gè)弱引用散列。
weak_referrer_t *referrers; // weak變量地址
uintptr_t out_of_line_ness : 2;
// 引用數(shù)值。這里記錄弱引用表中引用有效數(shù)字, 因?yàn)槿跻帽硎褂玫氖庆o態(tài) hash 結(jié)構(gòu), 所以需要使用變量來(lái)記錄數(shù)目。
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
// hash 元素上限閥值。
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
// 最低有效位, 也是標(biāo)志位。當(dāng)標(biāo)志位 0 時(shí), 增加引用表指針緯度。
// 當(dāng)其為0的時(shí)候, weak_referrer_t 成員將擴(kuò)展為多行靜態(tài) hash table。
// 通常情況下是等于零的, 所以弱引用表總是一個(gè) objc_objective 指針二維數(shù)組。
// 一維 objc_objective 指針可構(gòu)成一張弱引用散列表, 通過(guò)第三緯度實(shí)現(xiàn)了多張散列表, 并且表數(shù)量為 WEAK_INLINE_COUNT 。
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
// objc_object: weak_entry_t對(duì)象中的范型對(duì)象, 用于標(biāo)示weak引用的對(duì)象。
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
#define WEAK_INLINE_COUNT 4
#define REFERRERS_OUT_OF_LINE 2
weak_referrer_t
// The address of a __weak variable.
// These pointers are stored disguised(偽裝的) so memory analysis tools
// don't see lots of interior(內(nèi)部的) pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t;
/* 蘋(píng)果對(duì) DisguisedPtr 的注釋
DisguisedPtr<T> acts like pointer type T*, except the stored value is disguised to hide it from tools like `leaks`.
nil is disguised as itself so zero-filled memory works as expected,
which means 0x80..00 is also disguised as itself but we don't care.
Note that weak_entry_t knows about this encoding.
*/
可以看到 DisguisedPtr<objc_object *> 相當(dāng)于 objc_object **;
即 weak_referrer_t 就是 objc_object ** 的別名
存儲(chǔ)過(guò)程
初始化weak變量時(shí), runtime會(huì)調(diào)用 objc_initWeak 函數(shù), 初始化新的weak指針指向?qū)ο蟮牡刂?
objc_initWeak 函數(shù)內(nèi)部會(huì)調(diào)用 objc_storeWeak() 函數(shù), objc_storeWeak() 函數(shù)的作用是用來(lái)更新指針的指向, 創(chuàng)建弱引用表。
objc_initWeak
id objc_initWeak(id *location, id newObj) {
// 首先判斷指針指向的類對(duì)象是否有效, 若無(wú)效直接釋放指針并返回;
if (!newObj) {
*location = nil;
return nil;
}
// 注冊(cè)為一個(gè)指向value的_weak對(duì)象
// 更新指針的指向, 創(chuàng)建弱引用表。
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
storeWeak()
// 更新指針指向, 創(chuàng)建對(duì)應(yīng)弱引用表
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) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
// 獲取新引用散列
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加鎖操作, 解決選擇競(jìng)爭(zhēng);
// 為了解決死鎖問(wèn)題, 可能會(huì)開(kāi)啟二次嘗試
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
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.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(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.
previouslyInitializedClass = cls;
goto retry;
}
}
// 解除舊對(duì)象與弱引用表關(guān)聯(lián)綁定
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 構(gòu)造新的弱引用表
// 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
// 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;
}
weak_unregister_no_lock()
/**
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
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_register_no_lock()
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
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,
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(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;
}
}
// 構(gòu)造新的弱引用表
// 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(referent, referrer);
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;
}
在最后會(huì)調(diào)用 clearDeallocating 函數(shù)。而 clearDeallocating 函數(shù)首先根據(jù)對(duì)象的地址獲取weak指針地址的數(shù)組, 然后緊接著遍歷這個(gè)數(shù)組, 將其中的數(shù)組開(kāi)始置為nil, 把這個(gè) entry從weak 表中刪除, 最后一步清理對(duì)象的記錄。
釋放過(guò)程
當(dāng) weak 指向的對(duì)象被釋放時(shí), 編譯器如何讓 weak 指針置為nil的呢?
將弱引用存到一個(gè)弱引用表(哈希表)中, 對(duì)象銷毀時(shí), 就從表中取出當(dāng)前對(duì)象的弱引用并清除
調(diào)用 objc_release
因?yàn)閷?duì)象的引用計(jì)數(shù)為0, 所以執(zhí)行 dealloc
在 dealloc 中, 調(diào)用了
_objc_rootDealloc函數(shù)在
_objc_rootDealloc中, 調(diào)用了object_dispose函數(shù)調(diào)用
objc_destructInstance-
最后調(diào)用objc_clear_deallocating,詳細(xì)過(guò)程如下:
a. 從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
b. 將包含在記錄中的所有附有 weak修飾符變量的地址, 賦值為 nil
c. 將weak表中該記錄刪除
d. 從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
釋放過(guò)程 - 實(shí)驗(yàn)代碼
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__strong Person *person1;
__weak Person *person2;
__unsafe_unretained Person *person3;
NSLog(@"111");
{
Person *person = [[Person alloc] init];
person2 = person;
person3 = person;
}
NSLog(@"222 - %@", person2); // 打印 null , 說(shuō)明弱指針 person2 在其指向的對(duì)象銷毀時(shí), 會(huì)自動(dòng)置空
NSLog(@"333 - %@", person3); // 會(huì)報(bào)壞EXC_BAD_ACCESS內(nèi)存訪問(wèn), 在這里person3依然有值, 只不過(guò)是個(gè)野指針, 指向的person對(duì)象已被銷毀
}
@end
釋放過(guò)程 - 源碼
注: 關(guān)于 SideTable、weak_table_t、weak_entry_t 參考 《isa指針》
dealloc
// Replaced by CF (throws an NSException)
+ (void)dealloc {
}
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
rootDealloc()
void _objc_rootDealloc(id obj) {
ASSERT(obj);
obj->rootDealloc();
}
inline void objc_object::rootDealloc() {
if (isTaggedPointer()) return; // fixme necessary?
// 如果該對(duì)象是優(yōu)化的isa(arm64)且 沒(méi)有 弱引用 / 關(guān)聯(lián)對(duì)象 / C++析構(gòu)器 / 引用計(jì)數(shù)表, 直接釋放
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc)) {
assert(!sidetable_present());
free(this);
}
// 普通isa不管有沒(méi)有用過(guò)上述資源, 都走這里
// 需要釋放該對(duì)象相關(guān)的其他資源
else {
object_dispose((id)this);
}
}
object_dispose()
// 有弱引用必然會(huì)走這里
id object_dispose(id obj) {
if (!obj) return nil;
// 釋放 obj 相關(guān)資源
objc_destructInstance(obj);
// 釋放自身
free(obj);
return nil;
}
objc_destructInstance()
// 釋放 obj 相關(guān)資源
void *objc_destructInstance(id obj) {
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj); // 清除c++析構(gòu)函數(shù)相關(guān)成員變量
if (assoc) _object_remove_assocations(obj); // 移除關(guān)聯(lián)對(duì)象
// 根據(jù)當(dāng)前對(duì)象的地址值, 找到對(duì)應(yīng)的
obj->clearDeallocating(); // 將指向當(dāng)前對(duì)象的 weak 弱指針置為nil ★
}
return obj;
}
clearDeallocating()
//
inline void objc_object::clearDeallocating() {
// 普通isa指針, 直接指向 class / metaclass
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
// arm64優(yōu)化過(guò)的isa
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
clearDeallocating_slow()
// arm64優(yōu)化過(guò)的isa 走這里
NEVER_INLINE void objc_object::clearDeallocating_slow() {
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
//
SideTable& table = SideTables()[this];
table.lock();
// 有弱引用 ★
if (isa.weakly_referenced) {
// SideTable 有個(gè)弱引用表 weak_table, 是個(gè)哈希表
// (SideTable 數(shù)據(jù)結(jié)構(gòu)可到《isa》中查看)
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 引用計(jì)數(shù)長(zhǎng)度超出19bit, 存放在 SideTable
if (isa.has_sidetable_rc) {
// 清除引用計(jì)數(shù)
table.refcnts.erase(this);
}
table.unlock();
}
weak_clear_no_lock()
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {
// 要銷毀的對(duì)象
objc_object *referent = (objc_object *)referent_id;
// 根據(jù) 要銷毀的對(duì)象地址值 獲取 弱引用入口
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);
}
weak_entry_for_referent()
/** 根據(jù) 要銷毀的對(duì)象地址值 獲取 弱引用入口
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
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;
// 用 以要銷毀的對(duì)象地址值 referent, 按位與 weak_table的掩碼, 得出哈希表索引
// 可以看出, weak_table 內(nèi)部有一個(gè)哈希表 weak_entries
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
// 從哈希表中取出index對(duì)應(yīng)的弱引用
return &weak_table->weak_entries[index];
}
weak_entry_remove()
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) {
// remove entry
// 釋放weak變量地址, entry->referrers ★★
if (entry->out_of_line()) free(entry->referrers);
// 釋放entry
bzero(entry, sizeof(*entry));
// 弱引用數(shù)量-1
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
// Shrink(收縮) the table if it is mostly empty. // 縮容
static void weak_compact_maybe(weak_table_t *weak_table) {
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
sidetable_clearDeallocating
// arm64之前的 普通isa 使用該方法釋放
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();
}