標籤:對象屬性equaltostring
用OC等物件導向語言編程時,“對象”(object)就是“基本構造單元”(building block),開發人員可以通過對象來儲存並傳遞資料。在對象之間傳遞資料並執行任務的過程就叫做“訊息傳遞”(Messaing)。
當應用程式運行起來以後,為其提供相關支援的代碼叫做“Objective-C運行期環境”(Objevtive-C runtime),它提供了一些使得對象之間能夠傳遞訊息的重要函數,並且包含建立類執行個體所用的全部邏輯。在理解了運行期環境各個部分協同工作的原理之後,你的開發水平會進一步提升。
第6條:理解“屬性”這一概念
“屬性”(property)是OC的一項特性,用於封裝對象中的資料。OC對象通常會把其所需要的資料儲存為各種執行個體變數。屬性還有一些存取方法setter和getter。這一概念已經定型,開發人員可以令編譯器自動編寫與屬性相關的存取方法。
在描述個人資訊的類中,也許會存放人名、生日、地址等內容。可以在類介面的public區段中聲明一些執行個體變數:
@interface EOCPerson : NSObject{@public NSString* _firstName; NSString* _lastName;@private NSString* _someInternalData;}@end;
原來用過Java和C++的人比較熟悉這種寫法,可以定義變數的範圍。OC很少這麼做。該寫法的變數記憶體布局在編譯期已經固定了。:
詳細見《深度探索C++物件模型》http://blog.csdn.net/hherima/article/details/8888539
如果EOCPerson類中添加了一個屬性NSString* _dateOfBirth,那麼代碼需要重新編譯,否則就會出錯。例如,某個程式碼程式庫中的代碼使用了一份舊的類定義。如果和其相連結的代碼使用了新的類定義,那麼運行時就會出現不相容現象。各種程式設計語言都有應對此問題的方法。Objetive-C的做法是,把執行個體變數當做一種儲存位移量所有的“特殊變數”(special variable),交由“類對象”保管。位移量會在運行期尋找,如果類的定義變了,那麼儲存的位移量也就變了,這樣的話,無論何時訪問執行個體變數,總能使用正確的位移量。甚至可以在運行期向類中新增執行個體變數,這就是穩固的“應用程式二進位介面”(Application Binary Interface,ABI)
@property文法,能夠訪問封裝在對象裡的資料。因此,也可以把屬性當做一種簡稱,其意思是說:編譯器會自動產生一套存取方法,用以訪問給定類型中具有給定名稱的變數。例如:
@interface EOCPerson : NSObject@proterty NSString* firstName;@proterty NSString* lastName;@end
對於類的使用者來說,上述代碼寫出來的類與下面這種寫法等效:
@interface EOCPerson : NSObject-(NSString*)firstName;-(void)setFirstName:(NSString*)firstName;-(NSString*)lastName;-(void)setLastName:(NSString*)lastName;@end
訪問屬性使用點文法,屬性另外一個好處是可以使用自動合成@synthesize,另外@dynamic關鍵字可以告訴編譯器不要自動產生屬性存取方法,這裡不再累贅。
屬性特質
屬性有四種特質:原子性、讀/寫入權限、記憶體管理語義和方法名:
1. 原子性 預設情況下是atomic特質(儘管沒有atomic這個特質),nonatmic告訴編譯器不使用同步鎖。
2. 讀/寫入權限 具備read/write(讀寫)特質的屬性擁有存取方法。若屬性由@synthesize實現,則編譯器會自動產生這兩個方法。具備readonly(唯讀)特質的屬性僅擁有getter方法。
3. 記憶體管理語義
● assign 只會針對純類型簡單賦值操作
● strong 擁有關係,為這種屬性設定新值的時候,保留新值,釋放舊值。
● weak 同assign,但是對象銷毀的時候,屬性值也會清空
● unsafe_unretained 同assign,它適用於“物件類型”這也是跟assign的唯一區別
● copy 跟strong的特質類似。然而設定setter不會保留新值,而是將其“拷貝”(copy)。當屬性類型是NSMutableString*時,需要copy特質,目的是為了防止自己的屬性被無意修改。參考《Effective Objective-C 2.0》—(三)—、介面與API設計、深拷貝、淺拷貝 》最後部分http://blog.csdn.net/hherima/article/details/38403277
4. 方法名
● getter=<name>指定擷取方法名
● setter=<name>指定“設定方法”名
本節要點
● 使用@property 文法來定義對象中封裝的資料
● 通過“特質”來指定儲存資料所需的正確語義。
● 在設定屬性對應的執行個體變數時,一定要遵從該屬性聲明的語義
● 開發iOS程式時候應該使用nonatomic特質,因為atomic會嚴重印象效能。
第7條:在對象內部盡量直接存取執行個體變數
在對象之外訪問執行個體變數時,總是應該通過屬性來做,那麼在對象內部?筆者強烈建議大家在讀取執行個體變數的時候採用直接存取的形式,而在設定執行個體變數的時候通過屬性來做。之所以要通過“設定方法”來寫入執行個體變數,首要原因在於,這樣能夠確保相關屬性的“記憶體管理語義”得以貫徹。
第8條:理解“對象等同性”這一概念
NSObject協議中有兩個判斷等同性的關鍵方法:
-(BOOL) isEqual:(id)object;-(NSUInteger)hash;
NSObject類對這兩個方法的預設實現是:若且唯若“指標值”(pointer value,可理解為記憶體位址)完全相等時,這兩個對象才相等。如果"isEqual:"方法判定兩個對象相等,那麼其hash方法必須返回同一個值。但是,如果兩個對象的hash方法返回同一個值,那麼“isEqual:”方法未必會認為兩者相等。
先看看"isEqual:"介面,如果一個類中有A,B,C三個欄位,那麼isEqual就要對三個地段逐個對比,然後都相同的時候就返回ture。
再看看hash方法,一般的做法是,將A,B,C三個欄位分別hash,最後再異或一下。
-(NSUInteger)hash{NSUInteger firstNameHash = [_firstName hash];NSUInteger lastNameHash = [_lastName hash];NSUInteger ageHash = _age;return firstNameHash ^ lastNameHash ^ ageHash}
這種做法既能保持高效率,又能使產生的雜湊碼至少位於一定範圍之內,而不會過於頻繁的重複。
特定類型所具有的等同性判定方法
除了剛才提到的NSString之外,NSArray與NSDictionary類也有判定方法,前者是“isEqualToArray”後者是“isEqualToDictionary”。
除了剛才
等同性判定的執行深度
建立等同性判斷方法時,需要決定是根據整個對象來判斷,還是僅根據其中幾個欄位來判斷。NSArray的檢測方式為先看兩個數組所含有對象個數是否相等,若相同,則在每個對應位置的兩個對象身上調用其“isEqual”方法,如果對應位置上的對象均相等,那麼這兩個數組就相等,這叫做“深度等同判定”
容器中可變類的等同性
直接舉例NSSet就好。
第一步:在NSSet中添加一個可變數組A[@1,@2];那麼set中就有了一個對象,{[@1,@2]}
第二步:再向NSSet中添加一個可變數組B[@1,@2],由於數組A和B是一樣的,所以set中仍是一個對象,這也負責set的特性。
第三步:向NSSet中添加一個可變數組C[@1],那麼set中就有了兩個對象,{[@1],[@1,@2]}.
第四步:我們改變數組C的值為[@1,@2],那麼set居然包含了兩個相同的對象,{[@1,@2],[@1,@2]}.這不符合set的特性。
第五步:複製一份set。新的set的內容又變成{[@1,@2]}了。看上去set好像是由一個空set開始,通過逐個向其中添加新對象而建立出來的。
這是因為像set這樣的collect,把某個對象放入collect之後,就不應該改變其雜湊碼了。
本節要點:
● 若想檢測對象的等同性,請提供“isEqual:”與hash方法。
● 相同的對象必須具有相同的雜湊碼,但是兩個雜湊碼相同的對象未必相同。
● 不要盲目的逐個檢測每個屬性,而是應該依照具體需求來定製檢測方案
● 編寫hash方法時,應該使用計算速度快而且雜湊碼碰撞幾率低的演算法。
第9條:以“類族模式”隱藏實現細節
“類族”(class cluster)模式是一種很有用的模式(pattern),可以隱藏“抽象基類”(abstract base class)背後的實現細節。Objective-C的系統架構中普遍使用此模式。比如,iOS的使用者介面架構UIKit中就有一個名為UIButton類。
第10條:在既有類中使用關聯對象存放自訂資料
直接列舉開發中常見的例子吧:一個UIAlertView的使用,通常的做法是這樣的:
-(void) askUserAQuestion{ UIAlterView *alert = [[UIAlterView alloc] initWithTitle:@"Question" message:@"what do you want to do?" delegate:self cancelButtonTitle:@"cancel" otherButtonTitle:@"cancel",nil]; [alert show];}//UIAlertViewDelegate protocol method-(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; }}
alertView的建立和處理,不在一個地方。開發過程中,經常來回滑動代碼查看。那麼“關聯對象”就起到作用了,將操作跟UIAlertView關聯起來。例如:
static void* EOCMyAlertKey = "EOCMyAlertViewKey";-(void) askUserAQuestion{ UIAlterView *alert = [[UIAlterView alloc] initWithTitle:@"Question" message:@"what do you want to do?" delegate:self cancelButtonTitle:@"cancel" otherButtonTitle:@"cancel",nil]; void (^block)(NSInteger) = ^(NSInteger buttonIndex){ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; } }; objc_setAssociatedObject(alert,EOCMyAlertViewKey,block,OBJC_ASSOCIATION_COPY); [alert show];}//UIAlertViewDelegate protocol method-(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ if(buttonIndex == 0){ [self doCancel]; } else { [self doContinue]; }}-(void)alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ void (^block)(NSInteger) = objc_getAssociatedObject(alertView,EOCMyAlertViewkey); block(buttonIndex);}
介紹一下改進代碼中的技術
● void objc_setAssociatedObject(id object, void*key ,id value,objc_AssociatedPolicy policy)
此方法以給定的鍵和策略為某對象設定關聯對象值
● id objc_getAssociatedObject(id object,void* key)
此方法根據給定的鍵從某對象中擷取對應的關聯對象值
● void objc_removeAssociatedObjects(id object);
是對象關聯類別型
但是,這個方法應該慎用,在使用block的時候小心循環參考(參考bloc的介紹)。所以一般的做法還是:從中繼承子類,把block儲存為子類中的屬性。