面試題(重要面試看一看)
說明:面試題來源是微博@我就叫Sunny怎麼了的這篇博文:《招聘一個靠譜的 iOS》,其中共55題,除第一題為糾錯題外,其他54道均為簡答題。
博文中給出了高質量的面試題,但是未給出答案,我嘗試著總結了下答案,分兩篇發:這是上篇 ,下一篇文章將釋出在這裡,會把剩餘問題總結下,並且進行勘誤,歡迎各位指正文中的錯誤。請持續關注微博@iOS程式犭袁。(答案未經出題者校對,如有紕漏,請向微博@iOS程式犭袁指正。)
出題者簡介: 孫源(sunnyxx),目前就職於百度,負責百度知道 iOS 客戶端的開發工作,對技術喜歡刨根問底和總結最佳實踐,熱愛分享和開源,維護一個叫 forkingdog 的開源小組。
1. 風格糾錯題
修改方法有很多種,現給出一種做示例:
下面對具體修改的地方,分兩部分做下介紹:硬傷部分和優化部分 。因為硬傷部分沒什麼技術含量,為了節省大家時間,放在後面講,大神請直接看優化部分。
優化部分
1)enum建議使用 NS_ENUM 和 NS_OPTIONS 巨集來定義列舉型別,參見官方的 Adopting Modern Objective-C 一文:
//定義一個列舉 typedef NS_ENUM(NSInteger, CYLSex) { CYLSexMan, CYLSexWoman };
2)age屬性的型別:應避免使用基本型別,建議使Foundation資料型別,對應關係如下:
int -> NSInteger unsigned -> NSUInteger float -> CGFloat 動畫時間 -> NSTimeInterval
同時考慮到age的特點,應使用NSUInteger,而非int。 這樣做的是基於64-bit 適配考慮,詳情可參考出題者的博文《64-bit Tips》。
3)如果工程專案非常龐大,需要拆分成不同的模組,可以在類、typedef巨集命名的時候使用字首。
4)doLogIn方法不應寫在該類中:雖然LogIn的命名不太清晰,但筆者猜測是login的意思,而登入操作屬於業務邏輯,觀察類名UserModel,以及屬性的命名方式,應該使用的是MVC模式,並非MVVM,在MVC中業務邏輯不應當寫在Model中。(如果是MVVM,拋開命名規範,UserModel這個類可能對應的是使用者註冊頁面,如果有特殊的業務需求,比如:login對應的應當是註冊並登入的一個Button,出現login方法也可能是合理的。)
5)doLogIn方法命名不規範:添加了多餘的動詞字首。 請牢記:
如果方法表示讓物件執行一個動作,使用動詞打頭來命名,注意不要使用do,does這種多餘的關鍵字,動詞本身的暗示就足夠了。
6)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中不要用with來連線兩個引數:withAge:應當換為age:,age:已經足以清晰說明引數的作用,也不建議用andAge::通常情況下,即使有類似withA:withB:的命名需求,也通常是使用withA:andB:這種命名,用來表示方法執行了兩個相對獨立的操作(從設計上來說,這時候也可以拆分成兩個獨立的方法),它不應該用作闡明有多個引數,比如下面的:
//錯誤,不要使用"and"來連線引數 - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; //錯誤,不要使用"and"來闡明有多個引數 - (instancetype)initWithName:(CGFloat)width andAge:(CGFloat)height; //正確,使用"and"來表示兩個相對獨立的操作 - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
7)由於字串值可能會改變,所以要把相關屬性的“記憶體管理語義”宣告為copy。(原因在下文有詳細論述:用@property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?)
8)“性別”(sex)屬性的:該類中只給出了一種“初始化方法” (initializer)用於設定“姓名”(Name)和“年齡”(Age)的初始值,那如何對“性別”(Sex)初始化?
Objective-C 有 designated 和 secondary 初始化方法的觀念。 designated 初始化方法是提供所有的引數,secondary 初始化方法是一個或多個,並且提供一個或者更多的預設引數來呼叫 designated 初始化方法的初始化方法。舉例說明:
// .m檔案 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong // @implementation CYLUser - (instancetype)initWithName:(NSString *)name age:(int)age sex:(CYLSex)sex { if(self = [super init]) { _name = [name copy]; _age = age; _sex = sex; } return self; } - (instancetype)initWithName:(NSString *)name age:(int)age { return [self initWithName:name age:age sex:nil]; } @end
上面的程式碼中initWithName:age:sex: 就是 designated 初始化方法,另外的是 secondary 初始化方法。因為僅僅是呼叫類實現的 designated 初始化方法。
因為出題者沒有給出.m檔案,所以有兩種猜測:1:本來打算只設計一個designated 初始化方法,但漏掉了“性別”(sex)屬性。那麼最終的修改程式碼就是上文給出的第一種修改方法。2:不打算初始時初始化“性別”(sex)屬性,打算後期再修改,如果是這種情況,那麼應該把“性別”(sex)屬性設為readwrite屬性,最終給出的修改程式碼應該是:
9)按照介面設計的慣例,如果設計了“初始化方法” (initializer),也應當搭配一個快捷構造方法。而快捷構造方法的返回值,建議為instancetype,為保持一致性,init方法和快捷構造方法的返回型別最好都用instancetype。
10)如果基於第一種修改方法:既然該類中已經有一個“初始化方法” (initializer),用於設定“姓名”(Name)、“年齡”(Age)和“性別”(Sex)的初始值: 那麼在設計對應@property時就應該儘量使用不可變的物件:其三個屬性都應該設為“只讀”。用初始化方法設定好屬性值之後,就不能再改變了。在本例中,仍需宣告屬性的“記憶體管理語義”。於是可以把屬性的定義改成這樣
@property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, assign, readonly) NSUInter age; @property (nonatomic, assign, readonly) CYLSex sex;
由於是隻讀屬性,所以編譯器不會為其建立對應的“設定方法”,即便如此,我們還是要寫上這些屬性的語義,以此表明初始化方法在設定這些屬性值時所用的方式。要是不寫明語義的話,該類的呼叫者就不知道初始化方法裡會拷貝這些屬性,他們有可能會在呼叫初始化方法之前自行拷貝屬性值。這種操作多餘而且低效。
11)initUserModelWithUserName如果改為initWithName會更加簡潔,而且足夠清晰。
12)UserModel如果改為User會更加簡潔,而且足夠清晰。
13)UserSex如果改為Sex會更加簡潔,而且足夠清晰。
硬傷部分
1)在-和(void)之間應該有一個空格
2)enum中駝峰命名法和下劃線命名法混用錯誤:列舉型別的命名規則和函式的命名規則相同:命名時使用駝峰命名法,勿使用下劃線命名法。
3)enum左括號前加一個空格,或者將左括號換到下一行
4)enum右括號後加一個空格
5)UserModel :NSObject 應為UserModel : NSObject,也就是:右側少了一個空格。
6)@interface與@property屬性宣告中間應當間隔一行。
7)兩個方法定義之間不需要換行,有時為了區分方法的功能也可間隔一行,但示例程式碼中間隔了兩行。
8)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名與引數之間多了空格。而且- 與(id)之間少了空格。
9)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名與引數之間多了空格:(NSString*)name前多了空格。
10)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中(NSString*)name,應為(NSString *)name,少了空格。
11)doLogIn方法命名不清晰:筆者猜測是login的意思,應該是粗心手誤造成的。
12)第二個@property中assign和nonatomic調換位置。
2. 什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?
什麼情況使用 weak 關鍵字?
1)在ARC中,在有可能出現迴圈引用的時候,往往要通過讓其中一端使用weak來解決,比如:delegate代理屬性
2)自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用weak,自定義IBOutlet控制元件屬性一般也使用weak;當然,也可以使用strong。在下文也有論述:《IBOutlet連出來的檢視屬性為什麼可以被設定成weak?》
不同點:
1)weak 此特質表明該屬性定義了一種“非擁有關係” (nonowning relationship)。為這種屬性設定新值時,設定方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的物件遭到摧毀時,屬性值也會清空(nil out)。 而 assign 的“設定方法”只會執行鍼對“純量型別” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。
2)assigin 可以用非OC物件,而weak必須用於OC物件
3. 怎麼用 copy 關鍵字?
用途:
1)NSString、NSArray、NSDictionary 等等經常使用copy關鍵字,是因為他們有對應的可變型別:NSMutableString、NSMutableArray、NSMutableDictionary;
block使用copy是從MRC遺留下來的“傳統”,在MRC中,方法內部的block是在棧區的,使用copy可以把它放到堆區.在ARC中寫不寫都行:對於block使用copy還是strong效果是一樣的,但寫上copy也無傷大雅,還能時刻提醒我們:編譯器自動對block進行了copy操作。
下面做下解釋: copy此特質所表達的所屬關係與strong類似。然而設定方法並不保留新值,而是將其“拷貝” (copy)。 當屬性型別為NSString時,經常用此特質來保護其封裝性,因為傳遞給設定方法的新值有可能指向一個NSMutableString類的例項。這個類是NSString的子類,表示一種可修改其值的字串,此時若是不拷貝字串,那麼設定完屬性之後,字串的值就可能會在物件不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字串,確保物件中的字串值不會無意間變動。只要實現屬性所用的物件是“可變的” (mutable),就應該在設定新屬性值時拷貝一份。
用@property宣告 NSString、NSArray、NSDictionary 經常使用copy關鍵字,是因為他們有對應的可變型別:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保物件中的字串值不會無意間變動,應該在設定新屬性值時拷貝一份。
該問題在下文中也有論述:用@property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?
4. 這個寫法會出什麼問題: @property (copy) NSMutableArray *array;
兩個問題:
1、新增,刪除,修改陣列內的元素的時候,程式會因為找不到對應的方法而崩潰.因為copy就是複製一個不可變NSArray的物件;
2、使用了atomic屬性會嚴重影響效能。
第1條的相關原因在下文中有論述《用@property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?》 以及上文《怎麼用 copy 關鍵字?》也有論述。
第2條原因,如下:
該屬性使用了同步鎖,會在建立時生成一些額外的程式碼用於幫助編寫多執行緒程式,這會帶來效能問題,通過宣告nonatomic可以節省這些雖然很小但是不必要額外開銷。
在預設情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備nonatomic特質,則不使用同步鎖。請注意,儘管沒有名為“atomic”的特質(如果某屬性不具備nonatomic特質,那它就是“原子的”(atomic))。
在iOS開發中,你會發現,幾乎所有屬性都宣告為nonatomic。
一般情況下並不要求屬性必須是“原子的”,因為這並不能保證“執行緒安全” ( thread safety),若要實現“執行緒安全”的操作,還需採用更為深層的鎖定機制才行。例如,一個執行緒在連續多次讀取某屬性值的過程中有別的執行緒在同時改寫該值,那麼即便將屬性宣告為atomic,也還是會讀到不同的屬性值。
因此,開發iOS程式時一般都會使用nonatomic屬性。但是在開發Mac OS X程式時,使用 atomic屬性通常都不會有效能瓶頸。
5. 如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
若想令自己所寫的物件具有拷貝功能,則需實現NSCopying協議。如果自定義的物件分為可變版本與不可變版本,那麼就要同時實現NSCopyiog與NSMutableCopying協議。
具體步驟:
1)需宣告該類遵從NSCopying協議
2)實現NSCopying協議。該協議只有一個方法:
- (id)copyWithZone: (NSZone*) zone
注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實真正需要實現的卻是“copyWithZone”方法。
以第一題的程式碼為例:
然後實現協議中規定的方法:
但在實際的專案中,不可能這麼簡單,遇到更復雜一點,比如類物件中的資料結構可能並未在初始化方法中設定好,需要另行設定。舉個例子,假如CYLUser中含有一個數組,與其他CYLUser物件建立或解除朋友關係的那些方法都需要操作這個陣列。那麼在這種情況下,你得把這個包含朋友物件的陣列也一併拷貝過來。下面列出了實現此功能所需的全部程式碼:
// .m檔案
以上做法能滿足基本的需求,但是也有缺陷:如果你所寫的物件需要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法。
【注:深淺拷貝的概念,在下文中有介紹,詳見下文的:用@property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?】
在例子中,存放朋友物件的set是用“copyWithZooe:”方法來拷貝的,這種淺拷貝方式不會逐個複製set中的元素。若需要深拷貝的話,則可像下面這樣,編寫一個專供深拷貝所用的方法:
- (id)deepCopy { CYLUser *copy = [[[self copy] allocWithZone:zone] initWithName:_name age:_age sex:sex]; copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES]; return copy; }
至於如何重寫帶 copy 關鍵字的 setter這個問題,
如果拋開本例來回答的話,如下:
- (void)setName:(NSString *)name { _name = [name copy]; }
如果單單就上文的程式碼而言,我們不需要也不能重寫name的 setter :由於是name是隻讀屬性,所以編譯器不會為其建立對應的“設定方法”,用初始化方法設定好屬性值之後,就不能再改變了。( 在本例中,之所以還要宣告屬性的“記憶體管理語義”--copy,是因為:如果不寫copy,該類的呼叫者就不知道初始化方法裡會拷貝這些屬性,他們有可能會在呼叫初始化方法之前自行拷貝屬性值。這種操作多餘而低效。)。
那如何確保name被copy?在初始化方法(initializer)中做:
- (instancetype)initWithName:(NSString *)name age:(int)age sex:(CYLSex)sex { if(self = [super init]) { _name = [name copy]; _age = age; _sex = sex; _friends = [[NSMutableSet alloc] init]; } return self; }
6. @property 的本質是什麼?ivar、getter、setter 是如何生成並新增到這個類中的。
@property 的本質是什麼?
@property = ivar + getter + setter;
下面解釋下:
“屬性” (property)有兩大概念:ivar(例項變數)、存取方法(access method = getter + setter)。
“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在於封裝物件中的資料。 Objective-C 物件通常會把其所需要的資料儲存為各種例項變數。例項變數一般通過“存取方法”(access method)來訪問。其中,“獲取方法” (getter)用於讀取變數值,而“設定方法” (setter)用於寫入變數值。這個概念已經定型,並且經由“屬性”這一特性而成為Objective-C 2.0的一部分。 而在正規的 Objective-C 編碼風格中,存取方法有著嚴格的命名規範。 正因為有了這種嚴格的命名規範,所以 Objective-C 這門語言才能根據名稱自動創建出存取方法。其實也可以把屬性當做一種關鍵字,其表示:
編譯器會自動寫出一套存取方法,用以訪問給定型別中具有給定名稱的變數。 所以你也可以這麼說:
@property = getter + setter;
例如下面這個類:
@interface Person : NSObject @property NSString *firstName; @property NSString *lastName; @end
上述程式碼寫出來的類與下面這種寫法等效:
@interface Person : NSObject - (NSString *)firstName; - (void)setFirstName:(NSString *)firstName; - (NSString *)lastName; - (void)setLastName:(NSString *)lastName; @end
ivar、getter、setter 是如何生成並新增到這個類中的?
“自動合成”( autosynthesis)
完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”( autosynthesis)。需要強調的是,這個過程由編譯 器在編譯期執行,所以編輯器裡看不到這些“合成方法”(synthesized method)的原始碼。除了生成方法程式碼 getter、setter 之外,編譯器還要自動向類中新增適當型別的例項變數,並且在屬性名前面加下劃線,以此作為例項變數的名字。在前例中,會生成兩個例項變數,其名稱分別為 _firstName與_lastName。也可以在類的實現程式碼裡通過 @synthesize語法來指定例項變數的名字.
@implementation Person @synthesize firstName = _myFirstName; @synthesize lastName = myLastName; @end
我為了搞清屬性是怎麼實現的,曾經反編譯過相關的程式碼,大致生成了五個東西:
1)OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變數距離存放物件的記憶體區域的起始地址有多遠。
2)setter與getter方法對應的實現函式
3)ivar_list :成員變數列表
4)method_list :方法列表
5)prop_list :屬性列表
也就是說我們每次在增加一個屬性,系統都會在ivar_list中新增一個成員變數的描述,在method_list中增加setter與getter方法的描述,在屬性列表中增加一個屬性的描述,然後計算該屬性在物件中的偏移量,然後給出setter與getter方法對應的實現,在setter方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,為了能夠讀取正確位元組數,系統物件偏移量的指標型別進行了型別強轉.
7. @protocol 和 category 中如何使用 @property
1)在protocol中使用property只會生成setter和getter方法宣告,我們使用屬性的目的,是希望遵守我協議的物件能實現該屬性
2)category 使用 @property 也是隻會生成setter和getter方法的宣告,如果我們真的需要給category增加屬性的實現,需要藉助於執行時的兩個函式:
①objc_setAssociatedObject
②objc_getAssociatedObject
8. runtime 如何實現 weak 屬性
要實現weak屬性,首先要搞清楚weak屬性的特點:
weak 此特質表明該屬性定義了一種“非擁有關係” (nonowning relationship)。為這種屬性設定新值時,設定方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的物件遭到摧毀時,屬性值也會清空(nil out)。
那麼runtime如何實現weak變數的自動置nil?
runtime 對註冊的類, 會進行佈局,對於 weak 物件會放入一個 hash 表中。 用 weak 指向的物件記憶體地址作為 key,當此物件的引用計數為0的時候會 dealloc,假如 weak 指向的物件記憶體地址是a,那麼就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 物件,從而設定為 nil。
我們可以設計一個函式(虛擬碼)來表示上述機制:
objc_storeWeak(&a, b)函式:
objc_storeWeak函式把第二個引數--賦值物件(b)的記憶體地址作為鍵值key,將第一個引數--weak修飾的屬性變數(a)的記憶體地址(&a)作為value,註冊到 weak 表中。如果第二個引數(b)為0(nil),那麼把變數(a)的記憶體地址(&a)從weak表中刪除,
你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),並且當key變nil,將value置nil。
在b非nil時,a和b指向同一個記憶體地址,在b變nil時,a變nil。此時向a傳送訊息不會崩潰:在Objective-C中向nil傳送訊息是安全的。
而如果a是由assign修飾的,則: 在b非nil時,a和b指向同一個記憶體地址,在b變nil時,a還是指向該記憶體地址,變野指標。此時向a傳送訊息極易崩潰。
下面我們將基於objc_storeWeak(&a, b)函式,使用虛擬碼模擬“runtime如何實現weak屬性”:
// 使用虛擬碼模擬:runtime如何實現weak屬性 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong id obj1; objc_initWeak(&obj1, obj); /*obj引用計數變為0,變數作用域結束*/ objc_destroyWeak(&obj1);
下面對用到的兩個方法objc_initWeak和objc_destroyWeak做下解釋:
總體說來,作用是: 通過objc_initWeak函式初始化“附有weak修飾符的變數(obj1)”,在變數作用域結束時通過objc_destoryWeak函式釋放該變數(obj1)。
下面分別介紹下方法的內部實現:
objc_initWeak函式的實現是這樣的:在將“附有weak修飾符的變數(obj1)”初始化為0(nil)後,會將“賦值物件”(obj)作為引數,呼叫objc_storeWeak函式。
obj1 = 0; obj_storeWeak(&obj1, obj);
也就是說:
weak 修飾的指標預設值是 nil (在Objective-C中向nil傳送訊息是安全的)
然後obj_destroyWeak函式將0(nil)作為引數,呼叫objc_storeWeak函式。
objc_storeWeak(&obj1, 0);
前面的原始碼與下列原始碼相同。
// 使用虛擬碼模擬:runtime如何實現weak屬性 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong id obj1; obj1 = 0; objc_storeWeak(&obj1, obj); /* ... obj的引用計數變為0,被置nil ... */ objc_storeWeak(&obj1, 0);
objc_storeWeak函式把第二個引數--賦值物件(obj)的記憶體地址作為鍵值,將第一個引數--weak修飾的屬性變數(obj1)的記憶體地址註冊到 weak 表中。如果第二個引數(obj)為0(nil),那麼把變數(obj1)的地址從weak表中刪除,在後面的相關一題會詳解。
使用虛擬碼是為了方便理解,下面我們“真槍實彈”地實現下:
如何讓不使用weak修飾的@property,擁有weak的效果。
我們從setter方法入手:
- (void)setObject:(NSObject *)object { objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }]; }
也就是有兩個步驟:
1)在setter方法中做如下設定:
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
2)在屬性所指的物件遭到摧毀時,屬性值也會清空(nil out)。做到這點,同樣要藉助runtime:
//要銷燬的目標物件 id objectToBeDeallocated; //可以理解為一個“事件”:當上面的目標物件銷燬時,同時要發生的“事件”。 id objectWeWantToBeReleasedWhenThatHappens; objc_setAssociatedObject(objectToBeDeallocted, someUniqueKey, objectWeWantToBeReleasedWhenThatHappens, OBJC_ASSOCIATION_RETAIN);
知道了思路,我們就開始實現cyl_runAtDealloc方法,實現過程分兩部分:
第一部分:建立一個類,可以理解為一個“事件”:當目標物件銷燬時,同時要發生的“事件”。藉助block執行“事件”。
// .h檔案 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong // 這個類,可以理解為一個“事件”:當目標物件銷燬時,同時要發生的“事件”。藉助block執行“事件”。 typedef void (^voidBlock)(void); @interface CYLBlockExecutor : NSObject - (id)initWithBlock:(voidBlock)block; @end // .m檔案 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong // 這個類,可以理解為一個“事件”:當目標物件銷燬時,同時要發生的“事件”。藉助block執行“事件”。 #import "CYLBlockExecutor.h" @interface CYLBlockExecutor() { voidBlock _block; } @implementation CYLBlockExecutor - (id)initWithBlock:(voidBlock)aBlock { self = [super init]; if (self) { _block = [aBlock copy]; } return self; } - (void)dealloc { _block ? _block() : nil; } @end
第二部分:核心程式碼:利用runtime實現cyl_runAtDealloc方法
// CYLNSObject+RunAtDealloc.h檔案 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong // 利用runtime實現cyl_runAtDealloc方法 #import "CYLBlockExecutor.h" const void *runAtDeallocBlockKey = &runAtDeallocBlockKey; @interface NSObject (CYLRunAtDealloc) - (void)cyl_runAtDealloc:(voidBlock)block; @end // CYLNSObject+RunAtDealloc.m檔案 // http://weibo.com/luohanchenyilong/ // https://github.com/ChenYilong // 利用runtime實現cyl_runAtDealloc方法 #import "CYLNSObject+RunAtDealloc.h" #import "CYLBlockExecutor.h" @implementation NSObject (CYLRunAtDealloc) - (void)cyl_runAtDealloc:(voidBlock)block { if (block) { CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block]; objc_setAssociatedObject(self, runAtDeallocBlockKey, executor, OBJC_ASSOCIATION_RETAIN); } } @end
使用方法: 匯入
#import "CYLNSObject+RunAtDealloc.h"
然後就可以使用了:
NSObject *foo = [[NSObject alloc] init]; [foo cyl_runAtDealloc:^{ NSLog(@"正在釋放foo!"); }];
9. @property中有哪些屬性關鍵字?/ @property 後面可以有哪些修飾符?
屬性可以擁有的特質分為四類:
-
原子性---nonatomic特質
在預設情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備nonatomic特質,則不使用同步鎖。請注意,儘管沒有名為“atomic”的特質(如果某屬性不具備nonatomic特質,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那麼就應該遵從與屬性特質相符的原子性。
-
讀/寫許可權---readwrite(讀寫)、readooly (只讀)
-
記憶體管理語義---assign、strong、 weak、unsafe_unretained、copy
-
方法名---getter=、setter=
getter=的樣式:
@property (nonatomic, getter=isOn) BOOL on;
( setter=這種不常用,也不推薦使用。故不在這裡給出寫法。)
-
不常用的:nonnull,null_resettable,nullable
10. weak屬性需要在dealloc中置nil麼?
不需要。
在ARC環境無論是強指標還是弱指標都無需在deallco設定為nil,ARC會自動幫我們處理。
即便是編譯器不幫我們做這些,weak也不需要在dealloc中置nil:
正如上文的:runtime 如何實現 weak 屬性 中提到的:
我們模擬下weak的setter方法,應該如下:
- (void)setObject:(NSObject *)object { objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }]; }
相關推薦
面試題(重要面試看一看)
說明:面試題來源是微博@我就叫Sunny怎麼了的這篇博文:《招聘一個靠譜的 iOS》,其中共55題,除第一題為糾錯題外,其他54道均為簡答題。 博文中給出了高質量的面試題,但是未給出答案,我嘗試著總結了下答案,分兩篇發:這是上篇 ,下一篇文章將釋出在這裡,會把剩餘問題總結下,並且進行勘誤
C#面試題(曾經面試過三次)
1:求輸出結果 static void Main(string[] args) { int x = 20; int y = 40; GetResult(ref x, y); Console.W
最新阿裏巴巴面試題(附帶面試標準答案)
python Pythonweb 爬蟲 Python面試題 Python學習 在上一節中,我們已經介紹了關於阿裏巴巴的任職要求,這節我就詳細的介紹一下關於阿裏巴巴的面試題(涉及到標準代碼部分不予以出現,如果想要可以加群:725479218,裏面可以進行技術分享、技術交流、Python學習(
2018最新java面試題(技術面試)
想是 res 指定位置 普通 數據庫連接 結果 not 要求 pub 1、servlet執行流程 客戶端發出http請求,web服務器將請求轉發到servlet容器,servlet容器解析url並根據web.xml找到相對應的servlet,並將request、respon
出一套高階大資料開發面試題(刷起來!!!)
一千個讀者眼中有一千個哈姆雷特,一千名 大資料 程式設計師心目中就有一千套 大資料面試題。本文就是筆者認為可以用來面試大資料 程式設計師的面試題。 這套題的題目跟公司和業務都沒有關係,而且也並不代表筆者本人可以把這些題回答得非常好,筆者只是將一部分覺得比較好的題從收集的面試
居然有這種操作?各路公司面試題(作者:馬克-to-win)
OS tar exce 數組 interface stringbu pro AC 線程 我喜歡考試,不考試,誰知道哪些掌握了哪些沒有?? 面試什麽的最有愛了(變態笑)~~~ http://www.mark-to-win.com/JavaBeginner/JavaBegin
python3 開發面試題(%s和format的區別)5.31
什麽 字符 color 對象屬性 pri arguments 坐標 保留兩位小數 面試題 在格式化字符串中有兩種方法: 1、%s 2、format 大家常用的是哪一種方法?為什麽要用你選的這種方法? 我們先看一個例子: 首先我們定義一個我軍需要擊殺的恐怖分
JAVA面試題(1年工作經驗!)
以下面試題,為自己面試所遇到的一些問題: 1.webservice 應用的框架: JAX-WS,AXIS1,xFire,Axis2 這邊專案用到的是:JAX-WS 2.SQL分頁: MYSQL:LIMIT; SQLSERVICE:R
Java面試題(每日兩題9.28)—如何實現在main()方法執行前輸出“Hello World”
眾所周知,在 Java語言中,main()方法是程式的入口方法,在程式執行時,最先載入的就是main()方法,但這是否意味著main()方法就是程式執行時第一個被執行的模組呢? 答案是否定的。在Jawa語言中,由於靜態塊在類被載入時就會被呼叫,因此可以在main()方
華為面試題(8分鐘寫出程式碼) 有兩個陣列a,b,大小都為n,陣列元素的值任意,無序; 要求:通過交換a,b中的元素,使陣列a元素的和與陣列b元素的和之間的差最小
先上程式碼 java程式碼: public class MinDiff { public static void main(String[] args){ int[] aa={2,5,4,3,1,0}; int[] bb={7,9,8,10,6,11}
2018北京3月份java面試題(3~5年工作經驗)
僅提供面試知識點,詳細內容後續補充一、java基礎 1.final修飾方法、類、成員變數的特點 2.集合框架 ArrayList,HashMap,ConcurrentHashMap(這三個問到的頻率最高) 知識點:底層資料結構,知道哪些是執行緒安全的
java工程師最新面試題(集合,string,object)
Java基礎類的集中在以下的包中 lang包 util包 io包 掌握基礎lang包 掌握常用的math類和object類 掌握string類,以及其他與StringBuffer類的區別 掌握util包的基本類 重點理解java對幾何的處理 掌握java集合核心介面 區別h
六類運算子+鍵盤錄入+流程控制語句之順序結構與選擇結構中的if與switch語句部分+練習+面試題(java基礎語法篇二)
一。運算子(對常量和變數進行操作的符號)1.1算術運算子嘗試敲出以下程式碼,看看輸出的結果分別是什麼?相信通過敲出上面程式碼並執行,已經看出 /運算子是取商,而且商是向下取整,%則是取餘數沒理解的話做下下面兩個題試試(答案在下面,先自己計算出答案再敲出來驗證) 第一題答案 9
資料探勘、資料分析、海量資料處理的面試題(總結july的部落格)
緣由 由於有面試通知,現在複習一下十道和海量資料處理相關的題。兩篇部落格已經講的非常完備了,但是我怕讀懂了並非真的懂,所以必須自己複述一遍。 面試歸類 下面6個方面覆蓋了大多數關於海量資料處理的面試題: 分而治之/hash對映 + hash統計 + 堆/快速/歸併排序
hibernate相關面試題(不看後悔,一看必懂)
概述 hibernate框架應用在dao層,,hibernate的底層程式碼是jdbc,它是一個開源的輕量級的框架. hibernate通過orm思想對資料庫進行crud操作.orm中文翻譯過來就是物件關係對映,它讓實體類(就是通常所說的pojo)和資料庫表對應,讓實體類的欄位和表裡的欄
軟體測試經典面試題(面試必看)
引用與指標有什麼區別? 1) 引用必須被初始化,指標不必。 2) 引用初始化以後不能被改變,指標可以改變所指的物件。 3) 不存在指向空值的引用,但是存在指向空值的指標。 Internet.採用哪種網路協議?該協議的主要層次結構?Internet.實體地址和IP.地址轉
必看,經典sql面試題(學生表_課程表_成績表_教師表)
tin bsp get upload png idt cimage gem 公開 點擊鏈接加入QQ群 522720170(免費公開課、視頻應有盡有):https://jq.qq.com/?_wv=1027&k=5C08ATe http://xqtesting.com
網際網路運營、產品崗一定要看的面試題(一線二線網際網路企業面試題整理)
小夥伴們也許會有疑問: 這些題都是往年的,會再考嗎? 小菌覺得,70%左右不會了。 那麼把時間花在不會問到的題目上,不是一種浪費嗎? 小菌覺得,不會。 因為,企業在為每個崗位選擇人才的時候,崗位的能力和素質模型是固定的,即什麼樣的崗位需要哪幾方面的能力,是不變
前端面試大全:JS 基礎知識點及常考面試題(一)
(內容同步自小鄒的頭條號:滬漂程式設計師的生活史) 原始(Primitive)型別 涉及面試題:原始型別有哪幾種?null 是物件嘛? 在 JS 中,存在著 6 種原始值,分別是: boolean null undefined number
Redis面試題、你值得看一看
1.Redis與Memorycache的區別? Redis使用單執行緒,而Memcached是多執行緒, Redis使用現場申請記憶體的方式來儲存資料,並且可以配置虛擬記憶體;Memcached使用預分配的記憶體池的方式。 Redis實現了持久化和主