摘自: ? http://www.cocoachina.com/ios/20150803/12872.html
說明:面試題來源是微博@我就叫Sunny怎么了的這篇博文:《招聘一個靠譜的 iOS》选脊,其中共55題,除第一題為糾錯題外,其他54道均為簡答題。
博文中給出了高質(zhì)量的面試題,但是未給出答案,我嘗試著總結(jié)了下答案,分兩篇發(fā):這是上篇 玉锌,下一篇文章將發(fā)布在這里,會把剩余問題總結(jié)下慌随,并且進行勘誤芬沉,歡迎各位指正文中的錯誤。請持續(xù)關(guān)注微博@iOS程序犭袁阁猜。(答案未經(jīng)出題者校對丸逸,如有紕漏,請向微博@iOS程序犭袁指正剃袍。)
出題者簡介: 孫源(sunnyxx)黄刚,目前就職于百度,負責(zé)百度知道 iOS 客戶端的開發(fā)工作民效,對技術(shù)喜歡刨根問底和總結(jié)最佳實踐憔维,熱愛分享和開源,維護一個叫 forkingdog 的開源小組畏邢。
1. 風(fēng)格糾錯題
修改方法有很多種业扒,現(xiàn)給出一種做示例:
下面對具體修改的地方,分兩部分做下介紹:硬傷部分和優(yōu)化部分舒萎。因為硬傷部分沒什么技術(shù)含量程储,為了節(jié)省大家時間,放在后面講臂寝,大神請直接看優(yōu)化部分章鲤。
優(yōu)化部分
1)enum建議使用 NS_ENUM 和 NS_OPTIONS 宏來定義枚舉類型,參見官方的Adopting Modern Objective-C一文:
1
2
3
4
5//定義一個枚舉
typedef?NS_ENUM(NSInteger,?CYLSex)?{
CYLSexMan,
CYLSexWoman
};
2)age屬性的類型:應(yīng)避免使用基本類型咆贬,建議使Foundation數(shù)據(jù)類型败徊,對應(yīng)關(guān)系如下:
1
2
3
4int?->?NSInteger
unsigned?->?NSUInteger
float?->?CGFloat
動畫時間?->?NSTimeInterval
同時考慮到age的特點,應(yīng)使用NSUInteger掏缎,而非int皱蹦。 這樣做的是基于64-bit 適配考慮煤杀,詳情可參考出題者的博文《64-bit Tips》。
3)如果工程項目非常龐大根欧,需要拆分成不同的模塊怜珍,可以在類、typedef宏命名的時候使用前綴凤粗。
4)doLogIn方法不應(yīng)寫在該類中:雖然LogIn的命名不太清晰,但筆者猜測是login的意思今豆,而登錄操作屬于業(yè)務(wù)邏輯嫌拣,觀察類名UserModel,以及屬性的命名方式呆躲,應(yīng)該使用的是MVC模式异逐,并非MVVM,在MVC中業(yè)務(wù)邏輯不應(yīng)當(dāng)寫在Model中插掂。(如果是MVVM灰瞻,拋開命名規(guī)范,UserModel這個類可能對應(yīng)的是用戶注冊頁面辅甥,如果有特殊的業(yè)務(wù)需求酝润,比如:login對應(yīng)的應(yīng)當(dāng)是注冊并登錄的一個Button,出現(xiàn)login方法也可能是合理的璃弄。)
5)doLogIn方法命名不規(guī)范:添加了多余的動詞前綴要销。 請牢記:
如果方法表示讓對象執(zhí)行一個動作,使用動詞打頭來命名夏块,注意不要使用do疏咐,does這種多余的關(guān)鍵字,動詞本身的暗示就足夠了脐供。
6)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中不要用with來連接兩個參數(shù):withAge:應(yīng)當(dāng)換為age:浑塞,age:已經(jīng)足以清晰說明參數(shù)的作用,也不建議用andAge::通常情況下政己,即使有類似withA:withB:的命名需求酌壕,也通常是使用withA:andB:這種命名,用來表示方法執(zhí)行了兩個相對獨立的操作(從設(shè)計上來說匹颤,這時候也可以拆分成兩個獨立的方法)仅孩,它不應(yīng)該用作闡明有多個參數(shù),比如下面的:
1
2
3
4
5
6//錯誤印蓖,不要使用"and"來連接參數(shù)
-?(int)runModalForDirectory:(NSString?*)path?andFile:(NSString?*)name?andTypes:(NSArray?*)fileTypes;
//錯誤辽慕,不要使用"and"來闡明有多個參數(shù)
-?(instancetype)initWithName:(CGFloat)width?andAge:(CGFloat)height;
//正確,使用"and"來表示兩個相對獨立的操作
-?(BOOL)openFile:(NSString?*)fullPath?withApplication:(NSString?*)appName?andDeactivate:(BOOL)flag;
7)由于字符串值可能會改變赦肃,所以要把相關(guān)屬性的“內(nèi)存管理語義”聲明為copy溅蛉。(原因在下文有詳細論述:用@property聲明的NSString(或NSArray公浪,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么船侧?)
8)“性別”(sex)屬性的:該類中只給出了一種“初始化方法” (initializer)用于設(shè)置“姓名”(Name)和“年齡”(Age)的初始值欠气,那如何對“性別”(Sex)初始化?
Objective-C 有 designated 和 secondary 初始化方法的觀念镜撩。 designated 初始化方法是提供所有的參數(shù)预柒,secondary 初始化方法是一個或多個,并且提供一個或者更多的默認參數(shù)來調(diào)用 designated 初始化方法的初始化方法袁梗。舉例說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//?.m文件
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
//
@implementation?CYLUser
-?(instancetype)initWithName:(NSString?*)name
age:(int)age
sex:(CYLSex)sex?{
if(self?=?[superinit])?{
_name?=?[name?copy];
_age?=?age;
_sex?=?sex;
}
returnself;
}
-?(instancetype)initWithName:(NSString?*)name
age:(int)age?{
return[self?initWithName:name?age:age?sex:nil];
}
@end
上面的代碼中initWithName:age:sex: 就是 designated 初始化方法宜鸯,另外的是 secondary 初始化方法。因為僅僅是調(diào)用類實現(xiàn)的 designated 初始化方法遮怜。
因為出題者沒有給出.m文件淋袖,所以有兩種猜測:1:本來打算只設(shè)計一個designated 初始化方法,但漏掉了“性別”(sex)屬性锯梁。那么最終的修改代碼就是上文給出的第一種修改方法即碗。2:不打算初始時初始化“性別”(sex)屬性,打算后期再修改陌凳,如果是這種情況剥懒,那么應(yīng)該把“性別”(sex)屬性設(shè)為readwrite屬性,最終給出的修改代碼應(yīng)該是:
.h中暴露 designated 初始化方法冯遂,是為了方便子類化 (想了解更多蕊肥,請戳--》 《禪與 Objective-C 編程藝術(shù) (Zen and the Art of the Objective-C Craftsmanship 中文翻譯)》。)
9)按照接口設(shè)計的慣例蛤肌,如果設(shè)計了“初始化方法” (initializer)壁却,也應(yīng)當(dāng)搭配一個快捷構(gòu)造方法。而快捷構(gòu)造方法的返回值裸准,建議為instancetype展东,為保持一致性,init方法和快捷構(gòu)造方法的返回類型最好都用instancetype炒俱。
10)如果基于第一種修改方法:既然該類中已經(jīng)有一個“初始化方法” (initializer)盐肃,用于設(shè)置“姓名”(Name)、“年齡”(Age)和“性別”(Sex)的初始值: 那么在設(shè)計對應(yīng)@property時就應(yīng)該盡量使用不可變的對象:其三個屬性都應(yīng)該設(shè)為“只讀”权悟。用初始化方法設(shè)置好屬性值之后砸王,就不能再改變了。在本例中峦阁,仍需聲明屬性的“內(nèi)存管理語義”谦铃。于是可以把屬性的定義改成這樣
1
2
3@property?(nonatomic,?copy,?readonly)?NSString?*name;
@property?(nonatomic,?assign,?readonly)?NSUInter?age;
@property?(nonatomic,?assign,?readonly)?CYLSex?sex;
由于是只讀屬性,所以編譯器不會為其創(chuàng)建對應(yīng)的“設(shè)置方法”榔昔,即便如此驹闰,我們還是要寫上這些屬性的語義瘪菌,以此表明初始化方法在設(shè)置這些屬性值時所用的方式。要是不寫明語義的話嘹朗,該類的調(diào)用者就不知道初始化方法里會拷貝這些屬性师妙,他們有可能會在調(diào)用初始化方法之前自行拷貝屬性值。這種操作多余而且低效屹培。
11)initUserModelWithUserName如果改為initWithName會更加簡潔默穴,而且足夠清晰。
12)UserModel如果改為User會更加簡潔褪秀,而且足夠清晰壁顶。
13)UserSex如果改為Sex會更加簡潔,而且足夠清晰溜歪。
硬傷部分
1)在-和(void)之間應(yīng)該有一個空格
2)enum中駝峰命名法和下劃線命名法混用錯誤:枚舉類型的命名規(guī)則和函數(shù)的命名規(guī)則相同:命名時使用駝峰命名法,勿使用下劃線命名法许蓖。
3)enum左括號前加一個空格蝴猪,或者將左括號換到下一行
4)enum右括號后加一個空格
5)UserModel :NSObject 應(yīng)為UserModel : NSObject,也就是:右側(cè)少了一個空格膊爪。
6)@interface與@property屬性聲明中間應(yīng)當(dāng)間隔一行自阱。
7)兩個方法定義之間不需要換行,有時為了區(qū)分方法的功能也可間隔一行米酬,但示例代碼中間隔了兩行沛豌。
8)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名與參數(shù)之間多了空格。而且- 與(id)之間少了空格赃额。
9)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中方法名與參數(shù)之間多了空格:(NSString*)name前多了空格加派。
10)-(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中(NSString*)name,應(yīng)為(NSString *)name,少了空格跳芳。
11)doLogIn方法命名不清晰:筆者猜測是login的意思芍锦,應(yīng)該是粗心手誤造成的。
12)第二個@property中assign和nonatomic調(diào)換位置飞盆。
2. 什么情況使用 weak 關(guān)鍵字娄琉,相比 assign 有什么不同?
什么情況使用 weak 關(guān)鍵字吓歇?
1)在ARC中,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用weak來解決,比如:delegate代理屬性
2)自身已經(jīng)對它進行一次強引用,沒有必要再強引用一次,此時也會使用weak,自定義IBOutlet控件屬性一般也使用weak孽水;當(dāng)然,也可以使用strong城看。在下文也有論述:《IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?》
不同點:
1)weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)女气。為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值析命,也不釋放舊值主卫。此特質(zhì)同assign類似逃默, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)簇搅。 而 assign 的“設(shè)置方法”只會執(zhí)行針對“純量類型” (scalar type完域,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。
2)assigin?可以用非OC對象,而weak必須用于OC對象
3. 怎么用 copy 關(guān)鍵字瘩将?
用途:
1)NSString吟税、NSArray、NSDictionary 等等經(jīng)常使用copy關(guān)鍵字姿现,是因為他們有對應(yīng)的可變類型:NSMutableString肠仪、NSMutableArray、NSMutableDictionary备典;
2)block也經(jīng)常使用copy關(guān)鍵字异旧,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks:
block使用copy是從MRC遺留下來的“傳統(tǒng)”,在MRC中,方法內(nèi)部的block是在棧區(qū)的,使用copy可以把它放到堆區(qū).在ARC中寫不寫都行:對于block使用copy還是strong效果是一樣的,但寫上copy也無傷大雅提佣,還能時刻提醒我們:編譯器自動對block進行了copy操作吮蛹。
下面做下解釋: copy此特質(zhì)所表達的所屬關(guān)系與strong類似。然而設(shè)置方法并不保留新值拌屏,而是將其“拷貝” (copy)潮针。 當(dāng)屬性類型為NSString時,經(jīng)常用此特質(zhì)來保護其封裝性倚喂,因為傳遞給設(shè)置方法的新值有可能指向一個NSMutableString類的實例每篷。這個類是NSString的子類,表示一種可修改其值的字符串端圈,此時若是不拷貝字符串焦读,那么設(shè)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改枫笛。所以吨灭,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動刑巧。只要實現(xiàn)屬性所用的對象是“可變的” (mutable)喧兄,就應(yīng)該在設(shè)置新屬性值時拷貝一份。
用@property聲明 NSString啊楚、NSArray吠冤、NSDictionary 經(jīng)常使用copy關(guān)鍵字,是因為他們有對應(yīng)的可變類型:NSMutableString恭理、NSMutableArray拯辙、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動涯保,應(yīng)該在設(shè)置新屬性值時拷貝一份诉濒。
該問題在下文中也有論述:用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字夕春,為什么未荒?如果改用strong關(guān)鍵字,可能造成什么問題及志?
4. 這個寫法會出什么問題: @property (copy) NSMutableArray *array;
兩個問題:
1片排、添加,刪除,修改數(shù)組內(nèi)的元素的時候,程序會因為找不到對應(yīng)的方法而崩潰.因為copy就是復(fù)制一個不可變NSArray的對象;
2速侈、使用了atomic屬性會嚴重影響性能率寡。
第1條的相關(guān)原因在下文中有論述《用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字倚搬,為什么冶共?如果改用strong關(guān)鍵字,可能造成什么問題每界?》 以及上文《怎么用 copy 關(guān)鍵字比默?》也有論述。
第2條原因盆犁,如下:
該屬性使用了同步鎖,會在創(chuàng)建時生成一些額外的代碼用于幫助編寫多線程程序篡九,這會帶來性能問題谐岁,通過聲明nonatomic可以節(jié)省這些雖然很小但是不必要額外開銷。
在默認情況下榛臼,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)伊佃。如果屬性具備nonatomic特質(zhì),則不使用同步鎖沛善。請注意航揉,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備nonatomic特質(zhì),那它就是“原子的”(atomic))金刁。
在iOS開發(fā)中帅涂,你會發(fā)現(xiàn),幾乎所有屬性都聲明為nonatomic尤蛮。
一般情況下并不要求屬性必須是“原子的”媳友,因為這并不能保證“線程安全” ( thread safety),若要實現(xiàn)“線程安全”的操作产捞,還需采用更為深層的鎖定機制才行醇锚。例如,一個線程在連續(xù)多次讀取某屬性值的過程中有別的線程在同時改寫該值坯临,那么即便將屬性聲明為atomic焊唬,也還是會讀到不同的屬性值恋昼。
因此,開發(fā)iOS程序時一般都會使用nonatomic屬性赶促。但是在開發(fā)Mac OS X程序時液肌,使用 atomic屬性通常都不會有性能瓶頸。
5. 如何讓自己的類用 copy 修飾符芳杏?如何重寫帶 copy 關(guān)鍵字的 setter矩屁?
若想令自己所寫的對象具有拷貝功能,則需實現(xiàn)NSCopying協(xié)議爵赵。如果自定義的對象分為可變版本與不可變版本吝秕,那么就要同時實現(xiàn)NSCopyiog與NSMutableCopying協(xié)議商佛。
具體步驟:
1)需聲明該類遵從NSCopying協(xié)議
2)實現(xiàn)NSCopying協(xié)議救巷。該協(xié)議只有一個方法:
1
-?(id)copyWithZone:?(NSZone*)?zone
注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法郊尝,其實真正需要實現(xiàn)的卻是“copyWithZone”方法秕铛。
以第一題的代碼為例:
然后實現(xiàn)協(xié)議中規(guī)定的方法:
但在實際的項目中约郁,不可能這么簡單,遇到更復(fù)雜一點但两,比如類對象中的數(shù)據(jù)結(jié)構(gòu)可能并未在初始化方法中設(shè)置好鬓梅,需要另行設(shè)置。舉個例子谨湘,假如CYLUser中含有一個數(shù)組绽快,與其他CYLUser對象建立或解除朋友關(guān)系的那些方法都需要操作這個數(shù)組。那么在這種情況下紧阔,你得把這個包含朋友對象的數(shù)組也一并拷貝過來坊罢。下面列出了實現(xiàn)此功能所需的全部代碼:
// .m文件
以上做法能滿足基本的需求,但是也有缺陷:如果你所寫的對象需要深拷貝擅耽,那么可考慮新增一個專門執(zhí)行深拷貝的方法活孩。
【注:深淺拷貝的概念,在下文中有介紹乖仇,詳見下文的:用@property聲明的NSString(或NSArray憾儒,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么乃沙?如果改用strong關(guān)鍵字航夺,可能造成什么問題?】
在例子中崔涂,存放朋友對象的set是用“copyWithZooe:”方法來拷貝的阳掐,這種淺拷貝方式不會逐個復(fù)制set中的元素。若需要深拷貝的話,則可像下面這樣缭保,編寫一個專供深拷貝所用的方法:
1
2
3
4
5
6
7
8
9-?(id)deepCopy?{
CYLUser?*copy?=?[[[self?copy]?allocWithZone:zone]
initWithName:_name
age:_age
sex:sex];
copy->_friends?=?[[NSMutableSet?alloc]?initWithSet:_friends
copyItems:YES];
returncopy;
}
至于如何重寫帶 copy 關(guān)鍵字的 setter這個問題汛闸,
如果拋開本例來回答的話,如下:
1
2
3-?(void)setName:(NSString?*)name?{
_name?=?[name?copy];
}
如果單單就上文的代碼而言艺骂,我們不需要也不能重寫name的 setter :由于是name是只讀屬性诸老,所以編譯器不會為其創(chuàng)建對應(yīng)的“設(shè)置方法”,用初始化方法設(shè)置好屬性值之后钳恕,就不能再改變了别伏。( 在本例中,之所以還要聲明屬性的“內(nèi)存管理語義”--copy忧额,是因為:如果不寫copy厘肮,該類的調(diào)用者就不知道初始化方法里會拷貝這些屬性,他們有可能會在調(diào)用初始化方法之前自行拷貝屬性值睦番。這種操作多余而低效类茂。)。
那如何確保name被copy托嚣?在初始化方法(initializer)中做:
1
2
3
4
5
6
7
8
9
10
11-?(instancetype)initWithName:(NSString?*)name
age:(int)age
sex:(CYLSex)sex?{
if(self?=?[superinit])?{
_name?=?[name?copy];
_age?=?age;
_sex?=?sex;
_friends?=?[[NSMutableSet?alloc]?init];
}
returnself;
}
6. @property 的本質(zhì)是什么巩检?ivar、getter示启、setter 是如何生成并添加到這個類中的兢哭。
@property 的本質(zhì)是什么?
@property = ivar + getter + setter;
下面解釋下:
“屬性” (property)有兩大概念:ivar(實例變量)夫嗓、存取方法(access method = getter + setter)厦瓢。
“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在于封裝對象中的數(shù)據(jù)啤月。 Objective-C 對象通常會把其所需要的數(shù)據(jù)保存為各種實例變量。實例變量一般通過“存取方法”(access method)來訪問劳跃。其中谎仲,“獲取方法” (getter)用于讀取變量值,而“設(shè)置方法” (setter)用于寫入變量值刨仑。這個概念已經(jīng)定型郑诺,并且經(jīng)由“屬性”這一特性而成為Objective-C 2.0的一部分。 而在正規(guī)的 Objective-C 編碼風(fēng)格中杉武,存取方法有著嚴格的命名規(guī)范辙诞。 正因為有了這種嚴格的命名規(guī)范,所以 Objective-C 這門語言才能根據(jù)名稱自動創(chuàng)建出存取方法轻抱。其實也可以把屬性當(dāng)做一種關(guān)鍵字飞涂,其表示:
編譯器會自動寫出一套存取方法,用以訪問給定類型中具有給定名稱的變量。 所以你也可以這么說:
@property = getter + setter;
例如下面這個類:
1
2
3
4@interface?Person?:?NSObject
@property?NSString?*firstName;
@property?NSString?*lastName;
@end
上述代碼寫出來的類與下面這種寫法等效:
1
2
3
4
5
6@interface?Person?:?NSObject
-?(NSString?*)firstName;
-?(void)setFirstName:(NSString?*)firstName;
-?(NSString?*)lastName;
-?(void)setLastName:(NSString?*)lastName;
@end
ivar较店、getter士八、setter 是如何生成并添加到這個類中的?
“自動合成”( autosynthesis)
完成屬性定義后,編譯器會自動編寫訪問這些屬性所需的方法梁呈,此過程叫做“自動合成”( autosynthesis)婚度。需要強調(diào)的是,這個過程由編譯 器在編譯期執(zhí)行官卡,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼蝗茁。除了生成方法代碼 getter、setter 之外寻咒,編譯器還要自動向類中添加適當(dāng)類型的實例變量哮翘,并且在屬性名前面加下劃線,以此作為實例變量的名字仔涩。在前例中忍坷,會生成兩個實例變量,其名稱分別為 _firstName與_lastName熔脂。也可以在類的實現(xiàn)代碼里通過 @synthesize語法來指定實例變量的名字.
1
2
3
4@implementation?Person
@synthesize?firstName?=?_myFirstName;
@synthesize?lastName?=?myLastName;
@end
我為了搞清屬性是怎么實現(xiàn)的,曾經(jīng)反編譯過相關(guān)的代碼,大致生成了五個東西:
1)OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset)佩研,這個偏移量是“硬編碼” (hardcode),表示該變量距離存放對象的內(nèi)存區(qū)域的起始地址有多遠霞揉。
2)setter與getter方法對應(yīng)的實現(xiàn)函數(shù)
3)ivar_list :成員變量列表
4)method_list :方法列表
5)prop_list :屬性列表
也就是說我們每次在增加一個屬性,系統(tǒng)都會在ivar_list中添加一個成員變量的描述,在method_list中增加setter與getter方法的描述,在屬性列表中增加一個屬性的描述,然后計算該屬性在對象中的偏移量,然后給出setter與getter方法對應(yīng)的實現(xiàn),在setter方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對象偏移量的指針類型進行了類型強轉(zhuǎn).
7. @protocol 和 category 中如何使用 @property
1)在protocol中使用property只會生成setter和getter方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對象能實現(xiàn)該屬性
2)category 使用 @property 也是只會生成setter和getter方法的聲明,如果我們真的需要給category增加屬性的實現(xiàn),需要借助于運行時的兩個函數(shù):
①objc_setAssociatedObject
②objc_getAssociatedObject
8. runtime 如何實現(xiàn) weak 屬性
要實現(xiàn)weak屬性旬薯,首先要搞清楚weak屬性的特點:
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)。為這種屬性設(shè)置新值時适秩,設(shè)置方法既不保留新值绊序,也不釋放舊值。此特質(zhì)同assign類似秽荞, 然而在屬性所指的對象遭到摧毀時骤公,屬性值也會清空(nil out)。
那么runtime如何實現(xiàn)weak變量的自動置nil扬跋?
runtime 對注冊的類阶捆, 會進行布局,對于 weak 對象會放入一個 hash 表中钦听。 用 weak 指向的對象內(nèi)存地址作為 key洒试,當(dāng)此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a朴上,那么就會以a為鍵垒棋, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象痪宰,從而設(shè)置為 nil叼架。
我們可以設(shè)計一個函數(shù)(偽代碼)來表示上述機制:
objc_storeWeak(&a, b)函數(shù):
objc_storeWeak函數(shù)把第二個參數(shù)--賦值對象(b)的內(nèi)存地址作為鍵值key畔裕,將第一個參數(shù)--weak修飾的屬性變量(a)的內(nèi)存地址(&a)作為value,注冊到 weak 表中碉碉。如果第二個參數(shù)(b)為0(nil)柴钻,那么把變量(a)的內(nèi)存地址(&a)從weak表中刪除,
你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key)垢粮,并且當(dāng)key變nil贴届,將value置nil。
在b非nil時蜡吧,a和b指向同一個內(nèi)存地址毫蚓,在b變nil時,a變nil昔善。此時向a發(fā)送消息不會崩潰:在Objective-C中向nil發(fā)送消息是安全的元潘。
而如果a是由assign修飾的,則: 在b非nil時君仆,a和b指向同一個內(nèi)存地址翩概,在b變nil時,a還是指向該內(nèi)存地址返咱,變野指針钥庇。此時向a發(fā)送消息極易崩潰。
下面我們將基于objc_storeWeak(&a, b)函數(shù)咖摹,使用偽代碼模擬“runtime如何實現(xiàn)weak屬性”:
1
2
3
4
5
6
7//?使用偽代碼模擬:runtime如何實現(xiàn)weak屬性
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
id?obj1;
objc_initWeak(&obj1,?obj);
/*obj引用計數(shù)變?yōu)?评姨,變量作用域結(jié)束*/
objc_destroyWeak(&obj1);
下面對用到的兩個方法objc_initWeak和objc_destroyWeak做下解釋:
總體說來,作用是: 通過objc_initWeak函數(shù)初始化“附有weak修飾符的變量(obj1)”萤晴,在變量作用域結(jié)束時通過objc_destoryWeak函數(shù)釋放該變量(obj1)吐句。
下面分別介紹下方法的內(nèi)部實現(xiàn):
objc_initWeak函數(shù)的實現(xiàn)是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)后,會將“賦值對象”(obj)作為參數(shù)店读,調(diào)用objc_storeWeak函數(shù)嗦枢。
1
2obj1?=?0;
obj_storeWeak(&obj1,?obj);
也就是說:
weak 修飾的指針默認值是 nil (在Objective-C中向nil發(fā)送消息是安全的)
然后obj_destroyWeak函數(shù)將0(nil)作為參數(shù)屯断,調(diào)用objc_storeWeak函數(shù)文虏。
1
objc_storeWeak(&obj1,?0);
前面的源代碼與下列源代碼相同。
1
2
3
4
5
6
7
8//?使用偽代碼模擬:runtime如何實現(xiàn)weak屬性
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
id?obj1;
obj1?=?0;
objc_storeWeak(&obj1,?obj);
/*?...?obj的引用計數(shù)變?yōu)?裹纳,被置nil?...?*/
objc_storeWeak(&obj1,?0);
objc_storeWeak函數(shù)把第二個參數(shù)--賦值對象(obj)的內(nèi)存地址作為鍵值,將第一個參數(shù)--weak修飾的屬性變量(obj1)的內(nèi)存地址注冊到 weak 表中紧武。如果第二個參數(shù)(obj)為0(nil)剃氧,那么把變量(obj1)的地址從weak表中刪除,在后面的相關(guān)一題會詳解阻星。
使用偽代碼是為了方便理解朋鞍,下面我們“真槍實彈”地實現(xiàn)下:
如何讓不使用weak修飾的@property已添,擁有weak的效果。
我們從setter方法入手:
1
2
3
4
5
6
7-?(void)setObject:(NSObject?*)object
{
objc_setAssociatedObject(self,"object",?object,?OBJC_ASSOCIATION_ASSIGN);
[object?cyl_runAtDealloc:^{
_object?=?nil;
}];
}
也就是有兩個步驟:
1)在setter方法中做如下設(shè)置:
1
objc_setAssociatedObject(self,"object",?object,?OBJC_ASSOCIATION_ASSIGN);
2)在屬性所指的對象遭到摧毀時滥酥,屬性值也會清空(nil out)更舞。做到這點,同樣要借助runtime:
1
2
3
4
5
6
7
8//要銷毀的目標(biāo)對象
id?objectToBeDeallocated;
//可以理解為一個“事件”:當(dāng)上面的目標(biāo)對象銷毀時坎吻,同時要發(fā)生的“事件”缆蝉。
id?objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
someUniqueKey,
objectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);
知道了思路,我們就開始實現(xiàn)cyl_runAtDealloc方法瘦真,實現(xiàn)過程分兩部分:
第一部分:創(chuàng)建一個類刊头,可以理解為一個“事件”:當(dāng)目標(biāo)對象銷毀時,同時要發(fā)生的“事件”诸尽。借助block執(zhí)行“事件”原杂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//?.h文件
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
//?這個類,可以理解為一個“事件”:當(dāng)目標(biāo)對象銷毀時您机,同時要發(fā)生的“事件”穿肄。借助block執(zhí)行“事件”。
typedef?void?(^voidBlock)(void);
@interface?CYLBlockExecutor?:?NSObject
-?(id)initWithBlock:(voidBlock)block;
@end
//?.m文件
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
//?這個類际看,可以理解為一個“事件”:當(dāng)目標(biāo)對象銷毀時咸产,同時要發(fā)生的“事件”。借助block執(zhí)行“事件”仿村。
#import?"CYLBlockExecutor.h"
@interface?CYLBlockExecutor()?{
voidBlock?_block;
}
@implementation?CYLBlockExecutor
-?(id)initWithBlock:(voidBlock)aBlock
{
self?=?[superinit];
if(self)?{
_block?=?[aBlock?copy];
}
returnself;
}
-?(void)dealloc
{
_block???_block()?:?nil;
}
@end
第二部分:核心代碼:利用runtime實現(xiàn)cyl_runAtDealloc方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34//?CYLNSObject+RunAtDealloc.h文件
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
//?利用runtime實現(xiàn)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實現(xiàn)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
使用方法: 導(dǎo)入
1
#import?"CYLNSObject+RunAtDealloc.h"
然后就可以使用了:
1
2
3
4NSObject?*foo?=?[[NSObject?alloc]?init];
[foo?cyl_runAtDealloc:^{
NSLog(@"正在釋放foo!");
}];
如果對cyl_runAtDealloc的實現(xiàn)原理有興趣锐朴,可以看下這篇博文Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object
9. @property中有哪些屬性關(guān)鍵字?/ @property 后面可以有哪些修飾符蔼囊?
屬性可以擁有的特質(zhì)分為四類:
原子性---nonatomic特質(zhì)
在默認情況下焚志,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備nonatomic特質(zhì)畏鼓,則不使用同步鎖酱酬。請注意,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備nonatomic特質(zhì)云矫,那它就是“原子的” ( atomic) )膳沽,但是仍然可以在屬性特質(zhì)中寫明這一點,編譯器不會報錯让禀。若是自己定義存取方法挑社,那么就應(yīng)該遵從與屬性特質(zhì)相符的原子性。
讀/寫權(quán)限---readwrite(讀寫)巡揍、readooly (只讀)
內(nèi)存管理語義---assign痛阻、strong、 weak腮敌、unsafe_unretained阱当、copy
方法名---getter=俏扩、setter=
getter=的樣式:
1
@property?(nonatomic,?getter=isOn)?BOOL?on;
( setter=這種不常用,也不推薦使用弊添。故不在這里給出寫法录淡。)
不常用的:nonnull,null_resettable,nullable
10. weak屬性需要在dealloc中置nil么?
不需要油坝。
在ARC環(huán)境無論是強指針還是弱指針都無需在deallco設(shè)置為nil嫉戚,ARC會自動幫我們處理。
即便是編譯器不幫我們做這些免钻,weak也不需要在dealloc中置nil:
正如上文的:runtime 如何實現(xiàn) weak 屬性 中提到的:
我們模擬下weak的setter方法彼水,應(yīng)該如下:
1
2
3
4
5
6
7-?(void)setObject:(NSObject?*)object
{
objc_setAssociatedObject(self,"object",?object,?OBJC_ASSOCIATION_ASSIGN);
[object?cyl_runAtDealloc:^{
_object?=?nil;
}];
}
也即:在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)极舔。
11. @synthesize和@dynamic分別有什么作用凤覆?
1)@property有兩個對應(yīng)的詞,一個是@synthesize拆魏,一個是@dynamic盯桦。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;
2)@synthesize的語義是如果你沒有手動實現(xiàn)setter方法和getter方法渤刃,那么編譯器會自動為你加上這兩個方法拥峦。
3)@dynamic告訴編譯器:屬性的setter與getter方法由用戶自己實現(xiàn),不自動生成卖子。(當(dāng)然對于readonly的屬性只需提供getter即可)略号。假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法洋闽,編譯的時候沒問題玄柠,但是當(dāng)程序運行到instance.var = someVar,由于缺setter方法會導(dǎo)致程序崩潰诫舅;或者當(dāng)運行到 someVar = var時羽利,由于缺getter方法同樣會導(dǎo)致崩潰。編譯時沒問題刊懈,運行時才執(zhí)行相應(yīng)的方法这弧,這就是所謂的動態(tài)綁定。
12. ARC下虚汛,不顯式指定任何屬性關(guān)鍵字時匾浪,默認的關(guān)鍵字都有哪些?
對應(yīng)基本數(shù)據(jù)類型默認關(guān)鍵字是
atomic,readwrite,assign
對于普通的OC對象
atomic,readwrite,strong
參考鏈接:
Objective-C ARC: strong vs retain and weak vs assign
Variable property attributes or Modifiers in iOS
13. 用@property聲明的NSString(或NSArray卷哩,NSDictionary)經(jīng)常使用copy關(guān)鍵字蛋辈,為什么?如果改用strong關(guān)鍵字殉疼,可能造成什么問題梯浪?
1)因為父類指針可以指向子類對象,使用copy的目的是為了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
2)如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性.
copy此特質(zhì)所表達的所屬關(guān)系與strong類似。然而設(shè)置方法并不保留新值瓢娜,而是將其“拷貝” (copy)挂洛。 當(dāng)屬性類型為NSString時,經(jīng)常用此特質(zhì)來保護其封裝性眠砾,因為傳遞給設(shè)置方法的新值有可能指向一個NSMutableString類的實例虏劲。這個類是NSString的子類,表示一種可修改其值的字符串褒颈,此時若是不拷貝字符串柒巫,那么設(shè)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改谷丸。所以堡掏,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動刨疼。只要實現(xiàn)屬性所用的對象是“可變的” (mutable)泉唁,就應(yīng)該在設(shè)置新屬性值時拷貝一份。
為了理解這種做法揩慕,首先要知道亭畜,對非集合類對象的copy操作:
在非集合類對象中:對immutable對象進行copy操作,是指針復(fù)制迎卤,mutableCopy操作時內(nèi)容復(fù)制拴鸵;對mutable對象進行copy和mutableCopy都是內(nèi)容復(fù)制。用代碼簡單表示如下:
[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //深復(fù)制
[mutableObject copy] //深復(fù)制
[mutableObject mutableCopy] //深復(fù)制
比如以下代碼:
1
2NSMutableString?*string?=?[NSMutableString?stringWithString:@"origin"];//copy
NSString?*stringCopy?=?[string?copy];
查看內(nèi)存蜗搔,會發(fā)現(xiàn) string劲藐、stringCopy 內(nèi)存地址都不一樣,說明此時都是做內(nèi)容拷貝碍扔、深拷貝瘩燥。即使你進行如下操作:
1
[string?appendString:@"origion!"]
stringCopy的值也不會因此改變,但是如果不使用copy不同,stringCopy的值就會被改變厉膀。 集合類對象以此類推。 所以二拐,
用@property聲明 NSString服鹅、NSArray、NSDictionary 經(jīng)常使用copy關(guān)鍵字百新,是因為他們有對應(yīng)的可變類型:NSMutableString企软、NSMutableArray、NSMutableDictionary饭望,他們之間可能進行賦值操作仗哨,為確保對象中的字符串值不會無意間變動形庭,應(yīng)該在設(shè)置新屬性值時拷貝一份。
14. @synthesize合成實例變量的規(guī)則是什么厌漂?假如property名為foo萨醒,存在一個名為_foo的實例變量,那么還會自動合成新變量么苇倡?
在回答之前先說明下一個概念:
實例變量 = 成員變量 = ivar
這些說法富纸,筆者下文中,可能都會用到旨椒,指的是一個東西晓褪。
正如Apple官方文檔 You Can Customize Synthesized Instance Variable Names所說:
如果使用了屬性的話,那么編譯器就會自動編寫訪問屬性所需的方法综慎,此過程叫做“自動合成”( auto synthesis)涣仿。需要強調(diào)的是,這個過程由編譯器在編譯期執(zhí)行示惊,所以編輯器里看不到這些“合成方法” (synthesized method)的源代碼变过。除了生成方法代碼之外,編譯器還要自動向類中添加適當(dāng)類型的實例變量涝涤,并且在屬性名前面加下劃線媚狰,以此作為實例變量的名字。
1
2
3
4@interface?CYLPerson?:?NSObject
@property?NSString?*firstName;
@property?NSString?*lastName;
@end
在上例中阔拳,會生成兩個實例變量崭孤,其名稱分別為 _firstName與_lastName。也可以在類的實現(xiàn)代碼里通過@synthesize語法來指定實例變量的名字:
1
2
3
4@implementation?CYLPerson
@synthesize?firstName?=?_myFirstName;
@synthesize?lastName?=?_myLastName;
@end
上述語法會將生成的實例變量命名為_myFirstName與_myLastName糊肠,而不再使用默認的名字辨宠。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量货裹,那么可以用這個辦法將其改為自己想要的名字嗤形。筆者還是推薦使用默認的命名方案,因為如果所有人都堅持這套方案弧圆,那么寫出來的代碼大家都能看得懂赋兵。
總結(jié)下@synthesize合成實例變量的規(guī)則,有以下幾點:
1)如果指定了成員變量的名稱,會生成一個指定的名稱的成員變量,
2)如果這個成員已經(jīng)存在了就不再生成了.
3)如果是 @synthesize foo; 還會生成一個名稱為foo的成員變量搔预,也就是說:如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量霹期。
4)如果是 @synthesize foo = _foo; 就不會生成成員變量了.
假如property名為foo,存在一個名為_foo的實例變量拯田,那么還會自動合成新變量么历造? 不會。如下圖:
15. 在有了自動合成屬性實例變量之后,@synthesize還有哪些使用場景吭产?
回答這個問題前侣监,我們要搞清楚一個問題,什么情況下不會autosynthesis(自動合成)臣淤?
同時重寫了setter和getter時
重寫了只讀屬性的getter時
使用了@dynamic時
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
重載的屬性
當(dāng)你在子類中重載了父類中的屬性达吞,你必須 使用@synthesize來手動合成ivar。
除了后三條荒典,對其他幾個我們可以總結(jié)出一個規(guī)律:當(dāng)你想手動管理@property的所有內(nèi)容時,你就會嘗試通過實現(xiàn)@property的所有“存取方法”(the accessor methods)或者使用@dynamic來達到這個目的吞鸭,這時編譯器就會認為你打算手動管理@property寺董,于是編譯器就禁用了autosynthesis(自動合成)。
因為有了autosynthesis(自動合成)刻剥,大部分開發(fā)者已經(jīng)習(xí)慣不去手動定義ivar遮咖,而是依賴于autosynthesis(自動合成),但是一旦你需要使用ivar造虏,而autosynthesis(自動合成)又失效了御吞,如果不去手動定義ivar,那么你就得借助@synthesize來手動合成ivar漓藕。
其實陶珠,@synthesize語法還有一個應(yīng)用場景,但是不太建議大家使用:
可以在類的實現(xiàn)代碼里通過@synthesize語法來指定實例變量的名字:
1
2
3
4@implementation?CYLPerson
@synthesize?firstName?=?_myFirstName;
@synthesize?lastName?=?_myLastName;
@end
上述語法會將生成的實例變量命名為_myFirstName與_myLastName享钞,而不再使用默認的名字揍诽。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量栗竖,那么可以用這個辦法將其改為自己想要的名字暑脆。筆者還是推薦使用默認的命名案,因為如果所有人都堅持這套方案狐肢,那么寫出來的代碼大家都能看得懂添吗。
舉例說明:應(yīng)用場景:
結(jié)果編譯器報錯:
當(dāng)你同時重寫了setter和getter時,系統(tǒng)就不會生成ivar(實例變量/成員變量)份名。這時候有兩種選擇:
要么如第14行:手動創(chuàng)建ivar
要么如第17行:使用@synthesize foo = _foo; 碟联,關(guān)聯(lián)@property與ivar。
更多信息僵腺,請戳- 》When should I use @synthesize explicitly?
16. objc中向一個nil對象發(fā)送消息將會發(fā)生什么玄帕?
在Objective-C中向nil發(fā)送消息是完全有效的——只是在運行時不會有任何作用:
如果一個方法返回值是一個對象,那么發(fā)送給nil的消息將返回0(nil)想邦。例如:
1
Person?*?motherInlaw?=?[[aPerson?spouse]?mother];
如果spouse對象為nil裤纹,那么發(fā)送給nil的消息mother也將返回nil。
1)如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*)鹰椒,float锡移,double,long double 或者long long的整型標(biāo)量漆际,發(fā)送給nil的消息將返回0淆珊。
2)如果方法返回值為結(jié)構(gòu)體,發(fā)送給nil的消息將返回0。結(jié)構(gòu)體中各個字段的值將都是0奸汇。
3)如果方法的返回值不是上述提到的幾種情況施符,那么發(fā)送給nil的消息的返回值將是未定義的。
具體原因如下:
objc是動態(tài)語言擂找,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送戳吝,即:objc_msgSend(receiver, selector)。
那么贯涎,為了方便理解這個內(nèi)容听哭,還是貼一個objc的源代碼:
1
2
3//?runtime.h(類在runtime中的定義)
//http://weibo.com/luohanchenyilong/
//https://github.com/ChenYilong
1
2
3
4
5
6
7
8
9
10
11
12
13
14struct?objc_class?{
Class?isa?OBJC_ISA_AVAILABILITY;//isa指針指向Meta?Class,因為Objc的類的本身也是一個Object塘雳,為了處理這個關(guān)系陆盘,runtime就創(chuàng)造了Meta?Class,當(dāng)給類發(fā)送[NSObject?alloc]這樣消息時败明,實際上是把這個消息發(fā)給了Class?Object
#if?!__OBJC2__
Class?super_class?OBJC2_UNAVAILABLE;//?父類
const?char?*name?OBJC2_UNAVAILABLE;//?類名
long?version?OBJC2_UNAVAILABLE;//?類的版本信息隘马,默認為0
long?info?OBJC2_UNAVAILABLE;//?類信息,供運行期使用的一些位標(biāo)識
long?instance_size?OBJC2_UNAVAILABLE;//?該類的實例變量大小
struct?objc_ivar_list?*ivars?OBJC2_UNAVAILABLE;//?該類的成員變量鏈表
struct?objc_method_list?**methodLists?OBJC2_UNAVAILABLE;//?方法定義的鏈表
struct?objc_cache?*cache?OBJC2_UNAVAILABLE;//?方法緩存妻顶,對象接到一個消息會根據(jù)isa指針查找消息對象祟霍,這時會在method?Lists中遍歷,如果cache了盈包,常用的方法調(diào)用時就能夠提高調(diào)用的效率沸呐。
struct?objc_protocol_list?*protocols?OBJC2_UNAVAILABLE;//?協(xié)議鏈表
#endif
}?OBJC2_UNAVAILABLE;
objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類呢燥,然后在該類中的方法列表以及其父類方法列表中尋找方法運行崭添,然后在發(fā)送消息的時候,objc_msgSend方法不會返回值叛氨,所謂的返回內(nèi)容都是具體調(diào)用時執(zhí)行的呼渣。 那么,回到本題寞埠,如果向一個nil對象發(fā)送消息屁置,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現(xiàn)任何錯誤仁连。
17. objc中向一個對象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系蓝角?
具體原因同上題:該方法編譯之后就是objc_msgSend()函數(shù)調(diào)用.如果我沒有記錯的大概是這樣的:
1
((void?()(id,?SEL))(void?)objc_msgSend)((id)obj,?sel_registerName("foo"));
也就是說:
[obj foo];在objc動態(tài)編譯時阱穗,會被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));。
18. 什么時候會報unrecognized selector的異常使鹅?
簡單來說:當(dāng)該對象上某個方法,而該對象上沒有實現(xiàn)這個方法的時候揪阶, 可以通過“消息轉(zhuǎn)發(fā)”進行解決。
簡單的流程如下患朱,在上一題中也提到過:objc是動態(tài)語言鲁僚,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)裁厅。
objc在向一個對象發(fā)送消息時冰沙,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行执虹,如果拓挥,在最頂層的父類中依然找不到相應(yīng)的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 声畏。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會:
Method resolution
objc運行時會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:姻成,讓你有機會提供一個函數(shù)實現(xiàn)。如果你添加了函數(shù)并返回 YES,那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程扭屁,如果 resolve 方法返回 NO 堪旧,運行時就會移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)才睹。
Fast forwarding
如果目標(biāo)對象實現(xiàn)了-forwardingTargetForSelector:徘跪,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會琅攘。 只要這個方法返回的不是nil和self垮庐,整個消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個對象坞琴。否則哨查,就會繼續(xù)Normal Fowarding。 這里叫Fast剧辐,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機制寒亥。因為這一步不會創(chuàng)建任何新的對象,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象荧关,所以相對更快點溉奕。
Normal forwarding
這一步是Runtime最后一次給你挽救的機會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型忍啤。如果-methodSignatureForSelector:返回nil加勤,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數(shù)簽名胸竞,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象欺嗤。
19. 一個objc對象如何進行內(nèi)存布局?(考慮有父類的情況)
所有父類的成員變量和自己的成員變量都會存放在該對象所對應(yīng)的存儲空間中.
每一個對象內(nèi)部都有一個isa指針,指向他的類對象,類對象中存放著本對象的
1)對象方法列表(對象能夠接收的消息列表卫枝,保存在它所對應(yīng)的類對象中)
2)成員變量的列表
3)屬性列表
它內(nèi)部也有一個isa指針指向元對象(meta class),元對象內(nèi)部存放的是類方法列表,類對象內(nèi)部還有一個superclass的指針,指向他的父類對象煎饼。
1)根對象就是NSobject,它的superclass指針指向nil校赤。
2)類對象既然稱為對象吆玖,那它也是一個實例。類對象中也有一個isa指針指向它的元類(meta class)马篮,即類對象是元類的實例沾乘。元類內(nèi)部存放的是類方法列表,根元類的isa指針指向自己浑测,superclass指針指向NSObject類翅阵。
如圖:
20. 一個objc對象的isa的指針指向什么?有什么作用迁央?
指向他的類對象,從而可以找到對象上的方法
21. 下面的代碼輸出什么掷匠?
1
2
3
4
5
6
7
8
9
10
11@implementation?Son?:?Father
-?(id)init
{
self?=?[superinit];
if(self)?{
NSLog(@"%@",?NSStringFromClass([self?class]));
NSLog(@"%@",?NSStringFromClass([superclass]));
}
returnself;
}
@end
答案:
都輸出 Son
1
2NSStringFromClass([self?class])?=?Son
NSStringFromClass([superclass])?=?Son
解惑:
(以下解惑部分摘自微博@Chun_iOS的博文刨根問底Objective-C Runtime(1)- Self & Super)
這個題目主要是考察關(guān)于objc中對 self 和 super 的理解。
self 是類的隱藏參數(shù)岖圈,指向當(dāng)前調(diào)用方法的這個類的實例讹语。而 super 是一個 Magic Keyword, 它本質(zhì)是一個編譯器標(biāo)示符蜂科,和 self 是指向的同一個消息接受者顽决。
上面的例子不管調(diào)用[self class]還是[super class],接受消息的對象都是當(dāng)前 Son *xxx 這個對象导匣。而不同的是才菠,super是告訴編譯器,調(diào)用 class 這個方法時贡定,要去父類的方法鸠儿,而不是本類里的。
當(dāng)使用 self 調(diào)用方法時厕氨,會從當(dāng)前類的方法列表中開始找进每,如果沒有,就從父類中再找命斧;而當(dāng)使用 super 時田晚,則從父類的方法列表中開始找。然后調(diào)用父類的這個方法国葬。
真的是這樣嗎贤徒?繼續(xù)看:
使用clang重寫命令:
1
$?clang?-rewrite-objc?test.m
發(fā)現(xiàn)上述代碼被轉(zhuǎn)化為:
1
2NSLog((NSString?*)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0,?NSStringFromClass(((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)self,?sel_registerName("class"))));
NSLog((NSString?*)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1,?NSStringFromClass(((Class?(*)(__rw_objc_super?*,?SEL))(void?*)objc_msgSendSuper)((__rw_objc_super){?(id)self,?(id)class_getSuperclass(objc_getClass("Son"))?},?sel_registerName("class"))));
從上面的代碼中芹壕,我們可以發(fā)現(xiàn)在調(diào)用 [self class] 時,會轉(zhuǎn)化成 objc_msgSend函數(shù)接奈√哂浚看下函數(shù)定義:
1
id?objc_msgSend(id?self,?SEL?op,?...)
我們把 self 做為第一個參數(shù)傳遞進去。
而在調(diào)用 [super class]時序宦,會轉(zhuǎn)化成 objc_msgSendSuper函數(shù)睁壁。看下函數(shù)定義:
1
id?objc_msgSendSuper(struct?objc_super?*super,?SEL?op,?...)
第一個參數(shù)是 objc_super 這樣一個結(jié)構(gòu)體互捌,其定義如下:
1
2
3
4struct?objc_super?{
__unsafe_unretained?id?receiver;
__unsafe_unretained?Class?super_class;
};
結(jié)構(gòu)體有兩個成員潘明,第一個成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個參數(shù)self 。第二個成員是記錄當(dāng)前類的父類是什么秕噪。
所以钳降,當(dāng)調(diào)用 [self class] 時,實際先調(diào)用的是 objc_msgSend函數(shù)腌巾,第一個參數(shù)是 Son當(dāng)前的這個實例遂填,然后在 Son 這個類里面去找 - (Class)class這個方法,沒有澈蝙,去父類 Father里找吓坚,也沒有,最后在 NSObject類中發(fā)現(xiàn)這個方法碉克。而 - (Class)class的實現(xiàn)就是返回self的類別凌唬,故上述輸出結(jié)果為 Son并齐。
objc Runtime開源代碼對- (Class)class方法的實現(xiàn):
1
2
3-?(Class)class?{
returnobject_getClass(self);
}
而當(dāng)調(diào)用 [super class]時漏麦,會轉(zhuǎn)換成objc_msgSendSuper函數(shù)。第一步先構(gòu)造 objc_super 結(jié)構(gòu)體况褪,結(jié)構(gòu)體第一個成員就是 self 撕贞。 第二個成員是 (id)class_getSuperclass(objc_getClass(“Son”)) , 實際該函數(shù)輸出結(jié)果為 Father。 第二步是去 Father這個類里去找 - (Class)class测垛,沒有捏膨,然后去NSObject類去找,找到了食侮。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用号涯, 此時已經(jīng)和[self class]調(diào)用相同了,故上述輸出結(jié)果仍然返回 Son锯七。
22. runtime如何通過selector找到對應(yīng)的IMP地址链快?(分別考慮類方法和實例方法)
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型,其實selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實現(xiàn).
23. 使用runtime Associate方法關(guān)聯(lián)的對象,需要在主對象dealloc的時候釋放么眉尸?
在ARC下不需要
在MRC中,對于使用retain或copy策略的需要
無論在MRC下還是ARC下均不需要
2011年版本的Apple API 官方文檔 - Associative References一節(jié)中有一個MRC環(huán)境下的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//?在MRC下域蜗,使用runtime?Associate方法關(guān)聯(lián)的對象巨双,不需要在主對象dealloc的時候釋放
//http://weibo.com/luohanchenyilong/(微博@iOS程序犭袁)
//https://github.com/ChenYilong
//?摘自2011年版本的Apple?API?官方文檔?-?Associative?References
static?char?overviewKey;
NSArray?*array?=
[[NSArray?alloc]?initWithObjects:@"One",?@"Two",?@"Three",?nil];
//?For?the?purposes?of?illustration,?use?initWithFormat:?to?ensure
//?the?string?can?be?deallocated
NSString?*overview?=
[[NSString?alloc]?initWithFormat:@"%@",?@"First?three?numbers"];
objc_setAssociatedObject?(
array,
&overviewKey,
overview,
OBJC_ASSOCIATION_RETAIN
);
[overview?release];
//?(1)?overview?valid
[array?release];
//?(2)?overview?invalid
文檔指出
At point 1, the string overview is still valid because the OBJC_ASSOCIATION_RETAIN policy specifies that the array retains the associated object. When the array is deallocated, however (at point 2), overview is released and so in this case also deallocated.
我們可以看到,在[array release];之后霉祸,overview就會被release釋放掉了筑累。
既然會被銷毀,那么具體在什么時間點丝蹭?
根據(jù)WWDC 2011, Session 322 (第36分22秒)中發(fā)布的內(nèi)存銷毀時間表慢宗,被關(guān)聯(lián)的對象在生命周期內(nèi)要比對象本身釋放的晚很多。它們會在被 NSObject -dealloc 調(diào)用的 object_dispose() 方法中釋放半夷。
對象的內(nèi)存銷毀時間表婆廊,分四個步驟:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//?對象的內(nèi)存銷毀時間表
//http://weibo.com/luohanchenyilong/(微博@iOS程序犭袁)
//https://github.com/ChenYilong
//?根據(jù)?WWDC?2011,?Session?322?(36分22秒)中發(fā)布的內(nèi)存銷毀時間表
1.?調(diào)用?-release?:引用計數(shù)變?yōu)榱?/p>
*?對象正在被銷毀,生命周期即將結(jié)束.
*?不能再有新的?__weak?弱引用巫橄,?否則將指向?nil.
*?調(diào)用?[self?dealloc]
2.?父類?調(diào)用?-dealloc
*?繼承關(guān)系中最底層的父類?在調(diào)用?-dealloc
*?如果是?MRC?代碼?則會手動釋放實例變量們(iVars)
*?繼承關(guān)系中每一層的父類?都在調(diào)用?-dealloc
3.?NSObject?調(diào)?-dealloc
*?只做一件事:調(diào)用?Objective-C?runtime?中的?object_dispose()?方法
4.?調(diào)用?object_dispose()
*?為?C++?的實例變量們(iVars)調(diào)用?destructors
*?為?ARC?狀態(tài)下的?實例變量們(iVars)?調(diào)用?-release
*?解除所有使用?runtime?Associate方法關(guān)聯(lián)的對象
*?解除所有?__weak?引用
*?調(diào)用?free()
對象的內(nèi)存銷毀時間表:參考鏈接淘邻。
24. objc中的類方法和實例方法有什么本質(zhì)區(qū)別和聯(lián)系?
類方法:
類方法是屬于類對象的
類方法只能通過類對象調(diào)用
類方法中的self是類對象
類方法可以調(diào)用其他的類方法
類方法中不能訪問成員變量
類方法中不定直接調(diào)用對象方法
實例方法:
實例方法是屬于實例對象的
實例方法只能通過實例對象調(diào)用
實例方法中的self是實例對象
實例方法中可以訪問成員變量
實例方法中直接調(diào)用實例方法
實例方法中也可以調(diào)用類方法(通過類名)