1.@property 的本質是什么?ivar、getter、setter 是如何生成并添加到這個類中的
@property 的本質是什么?
@property = ivar + getter + setter;
下面解釋下:
“屬性” (property)有兩大概念:ivar(實例變量)、存取方法(access method = getter + setter)。
“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在于封裝對象中的數據。
ivar、getter、setter 是如何生成并添加到這個類中的?
“自動合成”( autosynthesis)
完成屬性定義后,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”(autosynthesis)。需要強調的是,這個過程由編譯 器在編譯期執行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。
2. @protocol 和 category 中如何使用 @property
在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協議的對象能實現該屬性
category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現,需要借助于運行時的兩個函數:
objc_setAssociatedObject
objc_getAssociatedObject
3.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。
4. @property中有哪些屬性關鍵字?/ @property 后面可以有哪些修飾符?
屬性可以擁有的特質分為四類:
原子性---nonatomic特質
在默認情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質,則不使用自旋鎖。請注意,盡管沒有名為“atomic”的特質(如果某屬性不具備 nonatomic 特質,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那么就應該遵從與屬性特質相符的原子性。
讀/寫權限---readwrite(讀寫)、readonly (只讀)
內存管理語義---assign、strong、weak、unsafe_unretained、copy
方法名---getter=、setter=
getter=的樣式:
@property (nonatomic, getter=isOn)BOOLon;
( `setter=`這種不常用,也不推薦使用。故不在這里給出寫法。)
setter=一般用在特殊的情境下,比如:
在數據反序列化、轉模型的過程中,服務器返回的字段如果以init開頭,所以你需要定義一個init開頭的屬性,但默認生成的setter與getter方法也會以init開頭,而編譯器會把所有以init開頭的方法當成初始化方法,而初始化方法只能返回 self 類型,因此編譯器會報錯。
這時你就可以使用下面的方式來避免編譯器報錯:
@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString*initBy;
另外也可以用關鍵字進行特殊說明,來避免編譯器報錯:
@property(nonatomic, readwrite, copy, null_resettable)NSString*initBy;- (NSString*)initBy__attribute__((objc_method_family(none)));
不常用的:nonnull,null_resettable,nullable
5. weak屬性需要在dealloc中置nil么?
不需要。
在ARC環境無論是強指針還是弱指針都無需在 dealloc 設置為 nil , ARC 會自動幫我們處理
即便是編譯器不幫我們做這些,weak也不需要在 dealloc 中置nil:
6.@synthesize和@dynamic分別有什么作用?
a.@property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize和 @dynamic都沒寫,那么默認的就是@syntheszie var = _var;
b.@synthesize 的語義是如果你沒有手動實現 setter 方法和 getter 方法,那么編譯器會自動為你加上這兩個方法。
c.@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現,不自動生成。(當然對于 readonly 的屬性只需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然后你沒有提供@setter方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到instance.var = someVar,由于缺 setter 方法會導致程序崩潰;或者當運行到someVar = var時,由于缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
7. ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些?
a.對應基本數據類型默認關鍵字是atomic,readwrite,assign?
b. 對于普通的 Objective-C 對象atomic,readwrite,strong
8. 用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
a.因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
b.如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性.
aa.在非集合類對象中:對 immutable 對象進行 copy 操作,是指針復制,mutableCopy 操作時內容復制;對 mutable 對象進行 copy 和 mutableCopy 都是內容復制.
bb.在集合類對象中,對 immutable 對象進行 copy,是指針復制, mutableCopy 是內容復制;對 mutable 對象進行 copy 和 mutableCopy 都是內容復制。但是:集合對象的內容復制僅限于對象本身,對象元素仍然是指針復制。
9. @synthesize合成實例變量的規則是什么?假如property名為foo,存在一個名為_foo的實例變量,那么還會自動合成新變量么?
實例變量 = 成員變量 = ivar
如果使用了屬性的話,那么編譯器就會自動編寫訪問屬性所需的方法,此過程叫做“自動合成”( auto synthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器里看不到這些“合成方法” (synthesized method)的源代碼。除了生成方法代碼之外,編譯器還要自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線,以此作為實例變量的名字。
@interfaceCYLPerson:NSObject@propertyNSString*firstName;@propertyNSString*lastName;@end
在上例中,會生成兩個實例變量,其名稱分別為_firstName與_lastName。也可以在類的實現代碼里通過@synthesize語法來指定實例變量的名字:
@implementationCYLPerson@synthesizefirstName = _myFirstName;@synthesizelastName = _myLastName;@end
上述語法會將生成的實例變量命名為_myFirstName與_myLastName,而不再使用默認的名字。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量,那么可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用默認的命名方案,因為如果所有人都堅持這套方案,那么寫出來的代碼大家都能看得懂。
總結下 @synthesize 合成實例變量的規則,有以下幾點:
如果指定了成員變量的名稱,會生成一個指定的名稱的成員變量,
如果這個成員已經存在了就不再生成了.
如果是@synthesize foo;還會生成一個名稱為foo的成員變量,也就是說:
如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量,
如果是@synthesize foo = _foo;就不會生成成員變量了.
假如 property 名為 foo,存在一個名為_foo的實例變量,那么還會自動合成新變量么? 不會。如下圖:
10.在有了自動合成屬性實例變量之后,@synthesize還有哪些使用場景?
回答這個問題前,我們要搞清楚一個問題,什么情況下不會autosynthesis(自動合成)?
同時重寫了 setter 和 getter 時
重寫了只讀屬性的 getter 時
使用了 @dynamic 時
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
重載的屬性
當你在子類中重載了父類中的屬性,你必須 使用@synthesize來手動合成ivar。
除了后三條,對其他幾個我們可以總結出一個規律:當你想手動管理 @property 的所有內容時,你就會嘗試通過實現 @property 的所有“存取方法”(the accessor methods)或者使用@dynamic來達到這個目的,這時編譯器就會認為你打算手動管理 @property,于是編譯器就禁用了 autosynthesis(自動合成)。
因為有了 autosynthesis(自動合成),大部分開發者已經習慣不去手動定義ivar,而是依賴于 autosynthesis(自動合成),但是一旦你需要使用ivar,而 autosynthesis(自動合成)又失效了,如果不去手動定義ivar,那么你就得借助@synthesize來手動合成 ivar。
其實,@synthesize語法還有一個應用場景,但是不太建議大家使用:
可以在類的實現代碼里通過@synthesize語法來指定實例變量的名字:
@implementationCYLPerson@synthesizefirstName = _myFirstName;@synthesizelastName = _myLastName;@end
上述語法會將生成的實例變量命名為_myFirstName與_myLastName,而不再使用默認的名字。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量,那么可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用默認的命名方案,因為如果所有人都堅持這套方案,那么寫出來的代碼大家都能看得懂。
11.objc中向一個nil對象發送消息將會發生什么?
在 Objective-C 中向 nil 發送消息是完全有效的——只是在運行時不會有任何作用:
如果一個方法返回值是一個對象,那么發送給nil的消息將返回0(nil)。例如:
Person * motherInlaw = [[aPersonspouse]mother];
如果 spouse 對象為 nil,那么發送給 nil 的消息 mother 也將返回 nil。 2. 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型標量,發送給 nil 的消息將返回0。 2. 如果方法返回值為結構體,發送給 nil 的消息將返回0。結構體中各個字段的值將都是0。 2. 如果方法的返回值不是上述提到的幾種情況,那么發送給 nil 的消息的返回值將是未定義的。
具體原因如下:
objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector)。
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,然后在發送消息的時候,objc_msgSend方法不會返回值,所謂的返回內容都是具體調用時執行的。 那么,回到本題,如果向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現任何錯誤。
12.objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什么關系?
[obj foo]該方法編譯之后就是objc_msgSend()函數調用.
[obj foo];在objc編譯時,會被轉意為:objc_msgSend(obj, @selector(foo));。
13.什么時候會報unrecognized selector的異常?
簡單來說:
當調用該對象上某個方法,而該對象上沒有實現這個方法的時候, 可以通過“消息轉發”進行解決。
簡單的流程如下,在上一題中也提到過:
objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector)。
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會:
?Method resolution
objc運行時會調用+resolveInstanceMethod:或者+resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數,那運行時系統就會重新啟動一次消息發送的過程,否則 ,運行時就會移到下一步,消息轉發(Message Forwarding)。
Fast forwarding
如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會。 只要這個方法返回的不是nil和self,整個消息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。否則,就會繼續Normal Fowarding。 這里叫Fast,只是為了區別下一步的轉發機制。因為這一步不會創建任何新的對象,但下一步轉發會創建一個NSInvocation對象,所以相對更快點。 3. Normal forwarding
這一步是Runtime最后一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數簽名,Runtime就會創建一個NSInvocation對象并發送-forwardInvocation:消息給目標對象。
14.一個objc對象如何進行內存布局?(考慮有父類的情況)
所有父類的成員變量和自己的成員變量都會存放在該對象所對應的存儲空間中.
每一個對象內部都有一個isa指針,指向他的類對象,類對象中存放著本對象的
對象方法列表(對象能夠接收的消息列表,保存在它所對應的類對象中)
成員變量的列表,
屬性列表,
它內部也有一個isa指針指向元對象(meta class),元對象內部存放的是類方法列表,類對象內部還有一個superclass的指針,指向他的父類對象。
每個 Objective-C 對象都有相同的結構,如下圖所示:
翻譯過來就是
Objective-C 對象的結構圖
ISA指針
根類的實例變量
倒數第二層父類的實例變量
...
父類的實例變量
類的實例變量
根對象就是NSObject,它的superclass指針指向nil
類對象既然稱為對象,那它也是一個實例。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。元類內部存放的是類方法列表,根元類的isa指針指向自己,superclass指針指向NSObject類。
15.一個objc對象的isa的指針指向什么?有什么作用?
指向他的類對象,從而可以找到對象上的方法
16.下面的代碼輸出什么?
@implementationSon:Father
- (id)init? {? ? ??
?self = [superinit]
;if(self) {NSLog(@"%@",NSStringFromClass([selfclass]));NSLog(@"%@",NSStringFromClass([superclass]));? ? ? }returnself;? }@end
答案:
都輸出 Son
NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son
這個題目主要是考察關于 Objective-C 中對 self 和 super 的理解。
我們都知道:self 是類的隱藏參數,指向當前調用方法的這個類的實例。那 super 呢?
很多人會想當然的認為“ super 和 self 類似,應該是指向父類的指針吧!”。這是很普遍的一個誤區。其實 super 是一個 Magic Keyword, 它本質是一個編譯器標示符,和 self 是指向的同一個消息接受者!他們兩個的不同點在于:super 會告訴編譯器,調用 class 這個方法時,要去父類的方法,而不是本類里的。
上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前Son *xxx這個對象。
當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。然后調用父類的這個方法。