weak的生命周期
weak的生命周期
我們都知道weak表示的是一個弱引用,這個引用不會增加對象的引用計數,并且在所指向的對象被釋放之后,weak指針會被設置的為nil。weak引用通常是用于處理循環引用的問題,如代理及block的使用中,相對會較多的使用到weak。
之前對weak的實現略有了解,知道它的一個基本的生命周期,但具體是怎么實現的,了解得不是太清晰。今天又翻了翻《Objective-C高級編程》關于__weak的講解,在此做個筆記。
我們以下面這行代碼為例:
代碼清單1:示例代碼
- {
- id __weak obj1 = obj;
- }
當我們初始化一個weak變量時,runtime會調用objc_initWeak函數。這個函數在Clang中的聲明如下:
- id objc_initWeak(id *object, id value);
其具體實現如下:
- id objc_initWeak(id *object, id value)
- {
- *object = 0;
- return objc_storeWeak(object, value);
- }
示例代碼輪換成編譯器的模擬代碼如下:
- id obj1;
- objc_initWeak(&obj1, obj);
因此,這里所做的事是先將obj1初始化為0(nil),然后將obj1的地址及obj作為參數傳遞給objc_storeWeak函數。
objc_initWeak函數有一個前提條件:就是object必須是一個沒有被注冊為__weak對象的有效指針。而value則可以是null,或者指向一個有效的對象。
如果value是一個空指針或者其指向的對象已經被釋放了,則object是zero-initialized的。否則,object將被注冊為一個指向value的__weak對象。而這事應該是objc_storeWeak函數干的。objc_storeWeak的函數聲明如下:
- id objc_storeWeak(id *location, id value);
其具體實現如下:
- id objc_storeWeak(id *location, id newObj)
- {
- 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:
- oldObj = *location;
- oldTable = SideTable::tableForPointer(oldObj);
- newTable = SideTable::tableForPointer(newObj);
- ......
- if (*location != oldObj) {
- OSSpinLockUnlock(lock1);
- #if SIDE_TABLE_STRIPE > 1
- if (lock1 != lock2) OSSpinLockUnlock(lock2);
- #endif
- goto retry;
- }
- if (oldObj) {
- weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
- }
- if (newObj) {
- newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
- // weak_register_no_lock returns NULL if weak store should be rejected
- }
- // Do not set *location anywhere else. That would introduce a race.
- *location = newObj;
- ......
- return newObj;
- }
我們撇開源碼中各種鎖操作,來看看這段代碼都做了些什么。在此之前,我們先來了解下weak表和SideTable。
weak表是一個弱引用表,實現為一個weak_table_t結構體,存儲了某個對象相關的的所有的弱引用信息。其定義如下(具體定義在objc-weak.h中):
- struct weak_table_t {
- weak_entry_t *weak_entries;
- size_t num_entries;
- ......
- };
其中weak_entry_t是存儲在弱引用表中的一個內部結構體,它負責維護和存儲指向一個對象的所有弱引用hash表。其定義如下:
- struct weak_entry_t {
- DisguisedPtr<objc_object> referent;
- union {
- struct {
- weak_referrer_t *referrers;
- uintptr_t out_of_line : 1;
- ......
- };
- struct {
- // out_of_line=0 is LSB of one of these (don't care which)
- weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
- };
- };
- };
其中referent是被引用的對象,即示例代碼中的obj對象。下面的union即存儲了所有指向該對象的弱引用。由注釋可以看到,當out_of_line等于0時,hash表被一個數組所代替。另外,所有的弱引用對象的地址都是存儲在weak_referrer_t指針的地址中。其定義如下:
typedef objc_object ** weak_referrer_t;
SideTable是一個用C++實現的類,它的具體定義在NSObject.mm中,我們來看看它的一些成員變量的定義:
- class SideTable {
- private:
- static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
- public:
- RefcountMap refcnts;
- weak_table_t weak_table;
- ......
- }
RefcountMap refcnts,大家應該能猜到這個做什么用的吧?看著像是引用計數什么的。哈哈,貌似就是啊,這東東存儲了一個對象的引用計數的信息。當然,我們在這里不去探究它,我們關注的是weak_table。這個成員變量指向的就是一個對象的weak表。
了解了weak表和SideTable,讓我們再回過頭來看看objc_storeWeak。首先是根據weak指針找到其指向的老的對象:
- oldObj = *location;
然后獲取到與新舊對象相關的SideTable對象:
- oldTable = SideTable::tableForPointer(oldObj);
- newTable = SideTable::tableForPointer(newObj);
- 下面要做的就是在老對象的weak表中移除指向信息,而在新對象的weak表中建立關聯信息:
- if (oldObj) {
- weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
- }
- if (newObj) {
- newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
- // weak_register_no_lock returns NULL if weak store should be rejected
- }
接下來讓弱引用指針指向新的對象:
- *location = newObj;
***會返回這個新對象:
- return newObj;
objc_storeWeak的基本實現就是這樣。當然,在objc_initWeak中調用objc_storeWeak時,老對象是空的,所有不會執行weak_unregister_no_lock操作。
而當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程如下:
調用objc_release
因為對象的引用計數為0,所以執行dealloc
在dealloc中,調用了_objc_rootDealloc函數
在_objc_rootDealloc中,調用了object_dispose函數
調用objc_destructInstance
***調用objc_clear_deallocating
我們重點關注一下***一步,objc_clear_deallocating的具體實現如下:
- void objc_clear_deallocating(id obj)
- {
- ......
- SideTable *table = SideTable::tableForPointer(obj);
- // clear any weak table items
- // clear extra retain count and deallocating bit
- // (fixme warn or abort if extra retain count == 0 ?)
- OSSpinLockLock(&table->slock);
- if (seen_weak_refs) {
- arr_clear_deallocating(&table->weak_table, obj);
- }
- ......
- }
我們可以看到,在這個函數中,首先取出對象對應的SideTable實例,如果這個對象有關聯的弱引用,則調用arr_clear_deallocating來清除對象的弱引用信息。我們來看看arr_clear_deallocating具體實現:
- PRIVATE_EXTERN void arr_clear_deallocating(weak_table_t *weak_table, id referent) {
- {
- weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
- if (entry == NULL) {
- ......
- return;
- }
- // zero out references
- for (int i = 0; i < entry->referrers.num_allocated; ++i) {
- id *referrer = entry->referrers.refs[i].referrer;
- if (referrer) {
- if (*referrer == referent) {
- *referrer = nil;
- }
- else if (*referrer) {
- _objc_inform("__weak variable @ %p holds %p instead of %p\n", referrer, *referrer, referent);
- }
- }
- }
- weak_entry_remove_no_lock(weak_table, entry);
- weak_table->num_weak_refs--;
- }
- }
這個函數首先是找出對象對應的weak_entry_t鏈表,然后挨個將弱引用置為nil。***清理對象的記錄。
通過上面的描述,我們基本能了解一個weak引用從生到死的過程。從這個流程可以看出,一個weak引用的處理涉及各種查表、添加與刪除操作,還是有一定消耗的。所以如果大量使用__weak變量的話,會對性能造成一定的影響。那么,我們應該在什么時候去使用weak呢?《Objective-C高級編程》給我們的建議是只在避免循環引用的時候使用__weak修飾符。
另外,在clang中,還提供了不少關于weak引用的處理函數。如objc_loadWeak, objc_destroyWeak, objc_moveWeak等,我們可以在蘋果的開源代碼中找到相關的實現。等有時間,我再好好研究研究。
參考
《Objective-C高級編程》1.4: __weak修飾符
Clang 3.7 documentation – Objective-C Automatic Reference Counting (ARC)
apple opensource – NSObject.mm
零碎
CAGradientLayer
CAGradientLayer類是用于在其背景色上繪制一個顏色漸變,以填充層的整個形狀,包括圓角。這個類繼承自CALayer類,使用起來還是很方便的。
與Quartz 2D中的漸變處理類似,一個漸變有一個起始位置(startPoint)和一個結束位置(endPoint),在這兩個位置之間,我們可以指定一組顏色值(colors,元素是CGColorRef對象),可以是兩個,也可以是多個,每個顏色值會對應一個位置(locations)。另外,漸變還分為軸向漸變和徑向漸變。
我們寫個實例來看看CAGradientLayer的具體使用:
- CAGradientLayer *layer = [CAGradientLayer layer];
- layer.startPoint = (CGPoint){0.5f, 0.0f};
- layer.endPoint = (CGPoint){0.5f, 1.0f};
- layer.colors = [NSArray arrayWithObjects:(id)[UIColor blueColor].CGColor, (id)[UIColor redColor].CGColor, (id)[UIColor greenColor].CGColor, nil];
- layer.locations = @[@0.0f, @0.6f, @1.0f];
- layer.frame = self.view.layer.bounds;
- [self.view.layer insertSublayer:layer atIndex:0];
參考
CAGradientLayer Class Reference
Xcode中Ineligible Devices的處理
換了臺新電腦,裝了個Xcode 6.3,整了個新證書和profile,然后打開Xcode,連上手機。額,然后發現設備居然被標識為Ineligible Devices,沒認出來。情況類似于下圖:
電腦是受信任的,證書和profile也都是OK的。試了幾次重啟Xcode和重新連接手機,無效。設備就是選不了。***是在Product->Destination里面才選中這個設備的。不過在工具欄還是不能選擇,郁悶,求解。
iOS 7后隱藏UITextField的光標
新項目只支持iOS 7后,很多事情變得簡單多了,就像隱藏UITextField的光標一樣,就簡單的一句話:
textFiled.tintColor = [UIColor clearColor];
通常我們用UIPickerView作為我們的UITextField的inputView時,我們是需要隱藏光標的。當然,如果想換個光標顏色,也是這么處理。
這么處理的有個遺留問題是:通常我們使用UIPickerView作為UITextField的inputView時, 并不希望去執行各種菜單操作(全選、復制、粘帖),但只是去設置UITextField的tintColor時,我們仍然可以執行這邊操作,所以需要加額外的處理。這個問題,我們可以這樣處理:在textFieldShouldBeginEditing:中,我們把UITextField的userInteractionEnabled設置為NO,然后在textFieldShouldEndEditing:,將將這個值設置回來。如下:
- - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
- textField.userInteractionEnabled = NO;
- return YES;
- }
- - (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
- textField.userInteractionEnabled = YES;
- return YES;
- }
這樣就OK了。當然這只是我們當前使用的一種處理方式,還有其它的方法,直接google或者stackoverflow吧。
iOS 7后UIAlertView中文字左對齊問題
在iOS 7之前,如果我們想要讓UIAlertView中的文字居左顯示的話,可以使用以下這段代碼來處理:
- for (UIView *view in alert.subviews) {
- if([[view class] isSubclassOfClass:[UILabel class]]) {
- ((UILabel*)view).textAlignment = NSTextAlignmentLeft;
- }
- }
但很遺憾的是,在iOS 7之后,蘋果不讓我們這么干了。我們去取UIAlertView的subviews時,獲得的只是一個空數組,我們沒有辦法獲取到我們想要的label。怎么辦?三條路:告訴產品經理和UED說這個實現不了(當然,這個是會被鄙視的,人家會說你能力差);自己寫;找第三方開源代碼。嘿嘿,不過由于最近時間緊,所以我決定跟他們說實現不了,哈哈。不過在github上找了一個開源的,Custom iOS AlertView,star的數量也不少,看來不錯,回頭好好研究研究。