-
UIView
和CALayer
是什么關(guān)系
UIView
繼承自UIResponder
類智袭,可以響應(yīng)事件CALayer
直接繼承自NSObject
類,不可以響應(yīng)事件UIView
是CALayer
的delegate
(CALayerDelegate
)UIView
主要處理事件艺普,CALayer
負(fù)責(zé)繪制- 每個(gè)
UIView
內(nèi)部都有一個(gè)CALayer
在背后提供內(nèi)容的繪制和顯示,并且UIView
的尺寸樣式都由內(nèi)部的Layer
所提供。兩者都有樹狀層級(jí)結(jié)構(gòu)眶熬,Layer
內(nèi)部有SubLayers
蒙秒,View
內(nèi)部有SubViews
勃黍,但是Layer
比View
多了個(gè)AnchorPoint
-
NSCache
和NSMutableDictionary
的相同點(diǎn)與區(qū)別
相同點(diǎn):
NSCache
和NSMutableDictionary
功能用法基本是相同的
區(qū)別:
NSCache
是線程安全的,NSMutableDictionary
線程不安全晕讲,Mutable開發(fā)的類
一般都是線程不安全的
當(dāng)內(nèi)存不足時(shí)NSCache
會(huì)自動(dòng)釋放內(nèi)存(所以從緩存中取數(shù)據(jù)的時(shí)候總要判斷是否為空)
NSCache
可以指定緩存的限額覆获,當(dāng)緩存超出限額自動(dòng)釋放內(nèi)存
NSCache
的Key
只是對(duì)對(duì)象進(jìn)行了Strong
引用,而非拷貝瓢省,所以不需要實(shí)現(xiàn)NSCopying
協(xié)議
-
atomic
的實(shí)現(xiàn)機(jī)制弄息;為什么不能保證絕對(duì)的線程安全(最好可以結(jié)合場景來說)
atomic
會(huì)對(duì)屬性的setter/getter
方法進(jìn)行加鎖,這僅僅只能保證在操作setter/getter
方法是安全的勤婚。不能保證其他線程的安全- 例如:線程1調(diào)用了某一屬性的
setter
方法并進(jìn)行到了一半摹量,線程2調(diào)用其getter
方法,那么會(huì)執(zhí)行完setter
操作后蛔六,再執(zhí)行getter
操作荆永,線程2會(huì)獲取到線程1setter
后的完整的值;當(dāng)幾個(gè)線程同時(shí)調(diào)用同一屬性的setter国章、getter
方法時(shí)具钥,會(huì)獲取到一個(gè)完整的值,但獲取到的值不可控
- iOS 中內(nèi)省的幾個(gè)方法
對(duì)象在運(yùn)行時(shí)獲取其類型的能力稱為內(nèi)省液兽。內(nèi)省可以有多種方法實(shí)現(xiàn)
OC運(yùn)行時(shí)內(nèi)省的4個(gè)方法:
- 判斷對(duì)象類型:
-(BOOL) isKindOfClass: // 判斷是否是這個(gè)類或者這個(gè)類的子類的實(shí)例
-(BOOL) isMemberOfClass: // 判斷是否是這個(gè)類的實(shí)例
- 判斷對(duì)象/類是否有這個(gè)方法
-(BOOL) respondsToSelector: // 判斷實(shí)例是否有這樣方法
+(BOOL) instancesRespondToSelector: // 判斷類是否有這個(gè)方法
-
objc
在向一個(gè)對(duì)象發(fā)送消息時(shí)骂删,發(fā)生了什么
根據(jù)對(duì)象的isa指針找到該對(duì)象所屬的類,去objc的對(duì)應(yīng)的類中找方法
1.首先四啰,在相應(yīng)操作的對(duì)象中的緩存方法列表中找調(diào)用的方法宁玫,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)并執(zhí)行
2.如果沒找到柑晒,在相應(yīng)操作的對(duì)象中的方法列表中找調(diào)用的方法欧瘪,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)執(zhí)行
3.如果沒找到匙赞,去父類指針?biāo)赶虻膶?duì)象中執(zhí)行1佛掖,2.
4.以此類推妖碉,如果一直到根類還沒找到,轉(zhuǎn)向攔截調(diào)用芥被,走消息轉(zhuǎn)發(fā)機(jī)制
5.如果沒有重寫攔截調(diào)用的方法欧宜,程序報(bào)錯(cuò)
- 你是否接觸過OC中的反射機(jī)制?簡單聊一下概念和使用
-
class
反射 - 通過類名的字符串形式實(shí)例化對(duì)象
Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
- 將類名變?yōu)樽址?/li>
Class class = [Student class];
NSString *className = NSStringFromClass(class);
-
SEL
的反射 - 通過方法的字符串形式實(shí)例化方法
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
- 將方法變成字符串
NSStringFromSelector(@selector(setName:));
- 這個(gè)寫法會(huì)出什么問題
@property (nonatomic, copy) NSMutableArray *arr;
添加拴魄,刪除冗茸,修改數(shù)組內(nèi)元素的時(shí)候,程序會(huì)因?yàn)檎也坏綄?duì)應(yīng)的方法而崩潰匹中。原因:是因?yàn)?code>copy就是復(fù)制一個(gè)不可變
NSArray
的對(duì)象夏漱,不能對(duì)NSArray
對(duì)象進(jìn)行添加/修改
- 如何讓自己的類用
copy
修飾符
若想令自己所寫的對(duì)象具有拷貝功能,則需實(shí)現(xiàn)
NSCopying
協(xié)議职员。如果自定義的對(duì)象分為可變版本與不可變版本麻蹋,那么就要同時(shí)實(shí)現(xiàn)NSCopying
與NSMutableCopying
協(xié)議。
具體步驟:
1.需聲明該類遵從NSCopying
協(xié)議
2.實(shí)現(xiàn)NSCopying
協(xié)議的方法焊切,具體區(qū)別戳這里
-
NSCopying
協(xié)議方法為:
- (id)copyWithZone:(NSZone *)zone {
MyObject *copy = [[[self class] allocWithZone: zone] init];
copy.username = self.username;
return copy;
}
- 為什么
assign
不能用于修飾對(duì)象
首先我們需要明確扮授,對(duì)象的內(nèi)存一般被分配到堆上,基本數(shù)據(jù)類型和oc數(shù)據(jù)類型的內(nèi)存一般被分配在棧上
如果用assign
修飾對(duì)象专肪,當(dāng)對(duì)象被釋放后刹勃,指針的地址還是存在的,也就是說指針并沒有被置為nil
嚎尤,從而造成了野指針荔仁。因?yàn)閷?duì)象是分配在堆上的,堆上的內(nèi)存由程序員分配釋放芽死。而因?yàn)橹羔槢]有被置為nil
乏梁,如果后續(xù)的內(nèi)存分配中,剛好分配到了這塊內(nèi)存关贵,就會(huì)造成崩潰
而assign
修飾基本數(shù)據(jù)類型或oc數(shù)據(jù)類型遇骑,因?yàn)榛緮?shù)據(jù)類型是分配在棧上的,由系統(tǒng)分配和釋放揖曾,所以不會(huì)造成野指針
- 請(qǐng)寫出以下代碼輸出
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr + 1));
參考答案:2落萎,隨機(jī)值
分析:
a
代表有5個(gè)元素的數(shù)組的首地址,a[5]
的元素分別是1炭剪,2练链,3,4奴拦,5媒鼓。接下來,a + 1
表示數(shù)據(jù)首地址加1,那么就是a[1]
绿鸣,也就是對(duì)應(yīng)于值為2瓷产,但是,這里是&a + 1
枚驻,因?yàn)?code>a代表的是整個(gè)數(shù)組,它的空間大小為5 * sizeof(int)
株旷,因此&a + 1
就是a + 5
再登。a
是個(gè)常量指針,指向當(dāng)前數(shù)組的首地址晾剖,指針+1就是移動(dòng)sizeof(int)
個(gè)字節(jié)
因此锉矢,ptr
是指向int *
類型的指針,而ptr
指向的就是a + 5
齿尽,那么ptr + 1
也相當(dāng)于a + 6
沽损,所以最后的*(ptr + 1)
就是一個(gè)隨機(jī)值了。而*(ptr – 1)
就相當(dāng)于a + 4
循头,對(duì)應(yīng)的值就是5
- 一個(gè)
view
已經(jīng)初始化完畢绵估,view
上面添加了n個(gè)button
(可能使用循環(huán)創(chuàng)建),除用view
的tag
之外卡骂,還可以采用什么辦法來找到自己想要的button
來修改Button
的值
第一種:如果是點(diǎn)擊某個(gè)按鈕后国裳,才會(huì)刷新它的值,其它不用修改全跨,那么不用引用任何按鈕缝左,直接在回調(diào)時(shí),就已經(jīng)將接收響應(yīng)的按鈕給傳過來了浓若,直接通過它修改即可
第二種:點(diǎn)擊某個(gè)按鈕后渺杉,所有與之同類型的按鈕都要修改值,那么可以通過在創(chuàng)建按鈕時(shí)將按鈕存入到數(shù)組中挪钓,在需要的時(shí)候遍歷查找
-
UIViewController
的viewDidUnload是越、viewDidLoad
和loadView
分別什么時(shí)候調(diào)用?UIView
的drawRect
和layoutSubviews
分別起什么作用
第一個(gè)問題:
在控制器被銷毀前會(huì)調(diào)用viewDidUnload
(MRC
下才會(huì)調(diào)用)
在控制器沒有任何view
時(shí)诵原,會(huì)調(diào)用loadView
在view
加載完成時(shí)英妓,會(huì)調(diào)用viewDidLoad
第二個(gè)問題:
在調(diào)用setNeedsDisplay
后,會(huì)調(diào)用drawRect
方法绍赛,我們通過在此方法中可以獲取到context
(設(shè)置上下文)蔓纠,就可以實(shí)現(xiàn)繪圖
在調(diào)用setNeedsLayout
后,會(huì)調(diào)用layoutSubviews
方法吗蚌,我們可以通過在此方法去調(diào)整UI腿倚。當(dāng)然能引起layoutSubviews
調(diào)用的方式有很多種的,比如添加子視圖蚯妇、滾動(dòng)scrollview
敷燎、修改視圖的frame
等
- 自動(dòng)釋放池工作原理
自動(dòng)釋放池是
NSAutorelease
類的一個(gè)實(shí)例暂筝,當(dāng)向一個(gè)對(duì)象發(fā)送autorelease
消息時(shí),該對(duì)象會(huì)自動(dòng)入池硬贯,待池銷毀時(shí)焕襟,將會(huì)向池中所有對(duì)象發(fā)送一條release
消息,釋放對(duì)象
[pool release]饭豹、[pool drain]
表示的是池本身不會(huì)銷毀鸵赖,而是池子中的臨時(shí)對(duì)象都被發(fā)送release
,從而將對(duì)象銷毀
- 蘋果是如何實(shí)現(xiàn)
autoreleasepool
的
autoreleasepool
是由AutoreleasePoolPage
以雙向鏈表的方式實(shí)現(xiàn)的拄衰,主要通過下列三個(gè)函數(shù)完成:
- 由
objc_autoreleasePoolPush
作為自動(dòng)釋放池作用域的第一個(gè)函數(shù)- 使用
objc_autorelease
將對(duì)象加入自動(dòng)釋放池- 由
objc_autoreleasePoolPop
作為自動(dòng)釋放池作用域的最后一個(gè)函數(shù)
-
autorelease
的對(duì)象何時(shí)被釋放
RunLoop
在每個(gè)事件循環(huán)結(jié)束后會(huì)去自動(dòng)釋放池將所有自動(dòng)釋放對(duì)象的引用計(jì)數(shù)減一它褪,若引用計(jì)數(shù)變成了0,則會(huì)將對(duì)象真正銷毀掉翘悉,回收內(nèi)存茫打。
在沒有手動(dòng)添加Autorelease Pool
的情況下,autorelease
的對(duì)象是在每個(gè)事件循環(huán)結(jié)束后妖混,自動(dòng)釋放池才會(huì)對(duì)所有自動(dòng)釋放的對(duì)象的引用計(jì)數(shù)減一老赤,若引用計(jì)數(shù)變成了0,則釋放對(duì)象源葫,回收內(nèi)存诗越。因此,若想要早一點(diǎn)釋放掉autorelease
對(duì)象息堂,那么我們可以在對(duì)象外加一個(gè)自動(dòng)釋放池嚷狞。比如,在循環(huán)處理數(shù)據(jù)時(shí)荣堰,臨時(shí)變量要快速釋放床未,就應(yīng)該采用這種方式:
// 通過alloc創(chuàng)建的對(duì)象,直接加入@autoreleasepool沒有作用振坚,需在創(chuàng)建對(duì)象后面顯式添加autorelease
// 通過類方法創(chuàng)建的對(duì)象不需要顯式添加autorelease薇搁,原因是類方法創(chuàng)建的對(duì)象系統(tǒng)會(huì)自動(dòng)添加autorelease
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *str = @"Abc";
str = [str lowercaseString];
str = [str stringByAppendingString:@"xyz"];
NSLog(@"%@", str);
} // 出了這里,就會(huì)去遍歷該自動(dòng)釋放池了
}
- 簡述內(nèi)存管理基本原則
OC內(nèi)存管理遵循
誰創(chuàng)建渡八,誰釋放啃洋,誰引用,誰管理
的機(jī)制屎鳍,當(dāng)使用alloc宏娄、copy(mutableCopy)或者retian
一個(gè)對(duì)象時(shí),你就有義務(wù)向它發(fā)送一條release或者autorelease
消息釋放該對(duì)象逮壁,其他方法創(chuàng)建的對(duì)象孵坚,不需要由你來管理內(nèi)存,當(dāng)對(duì)象引用計(jì)數(shù)為0時(shí),系統(tǒng)將釋放該對(duì)象卖宠,這是OC的手動(dòng)管理機(jī)制(MRC
)
向一個(gè)對(duì)象發(fā)送一條autorelease
消息巍杈,這個(gè)對(duì)象并不會(huì)立即銷毀羽杰,而是將這個(gè)對(duì)象放入了自動(dòng)釋放池狼牺,待池子釋放時(shí),它會(huì)向池中每一個(gè)對(duì)象發(fā)送一條release
消息黍判,以此來釋放對(duì)象
向一個(gè)對(duì)象發(fā)送release
消息刺洒,并不意味著這個(gè)對(duì)象被銷毀了汁咏,而是當(dāng)這個(gè)對(duì)象的引用計(jì)數(shù)為0時(shí),系統(tǒng)才會(huì)調(diào)用dealloc
方法釋放該對(duì)象和對(duì)象本身所擁有的實(shí)例
-
sizeof
關(guān)鍵字
sizeof
是在編譯階段處理作媚,且不能被編譯為機(jī)器碼。sizeof
的結(jié)果等于對(duì)象或類型所占的內(nèi)存字節(jié)數(shù)帅刊。sizeof
的返回值類型為size_t
變量:int a; sizeof(a)
為4纸泡;
指針:int *p; sizeof(p)
為4;
數(shù)組:int b[10]; sizeof(b)
為數(shù)組的大小4*10赖瞒;int c[0]; sizeof(c)
等于0
sizeof(void)
等于1
sizeof(void *)
等于4
- 什么是離屏渲染女揭?什么情況下會(huì)觸發(fā)?離屏渲染消耗性能的原因
離屏渲染就是在當(dāng)前屏幕緩沖區(qū)以外栏饮,新開辟一個(gè)緩沖區(qū)進(jìn)行操作
離屏渲染觸發(fā)的場景有以下:
- 圓角(同時(shí)設(shè)置
layer.masksToBounds = YES吧兔、layer.cornerRadius
大于0)- 圖層蒙版
- 陰影,
layer.shadowXXX
袍嬉,如果設(shè)置了layer.shadowPath
就不會(huì)產(chǎn)生離屏渲染- 遮罩境蔼,
layer.mask
- 光柵化,
layer.shouldRasterize = YES
離屏渲染消耗性能的原因
需要?jiǎng)?chuàng)建新的緩沖區(qū)伺通,離屏渲染的整個(gè)過程箍土,需要多次切換上下文環(huán)境,先是從當(dāng)前屏幕(On-Screen
)切換到離屏(Off-Screen
)等到離屏渲染結(jié)束以后罐监,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上吴藻,又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕
- ARC 下,不顯式指定任何屬性關(guān)鍵字時(shí)弓柱,默認(rèn)的關(guān)鍵字都有哪些
基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是:
atomic, readwrite, assign
普通Objective-C
對(duì)象默認(rèn)關(guān)鍵字是:atomic, readwrite, strong
- OC中的類方法和實(shí)例方法有什么本質(zhì)區(qū)別和聯(lián)系
類方法:
- 類方法是屬于類對(duì)象的
- 類方法只能通過類對(duì)象調(diào)用
- 類方法中的 self 是類對(duì)象
- 類方法可以調(diào)用其他的類方法
- 類方法中不能訪問成員變量
- 類方法中不能直接調(diào)用對(duì)象方法
實(shí)例方法:
- 實(shí)例方法是屬于實(shí)例對(duì)象的
- 實(shí)例方法只能通過實(shí)例對(duì)象調(diào)用
- 實(shí)例方法中的 self 是實(shí)例對(duì)象
- 實(shí)例方法中可以訪問成員變量
- 實(shí)例方法中直接調(diào)用實(shí)例方法
- 實(shí)例方法中也可以調(diào)用類方法(通過類名)
- 能否向編譯后得到的類中增加實(shí)例變量沟堡?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么矢空?
- 不能向編譯后得到的類中增加實(shí)例變量
- 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量
- 因?yàn)榫幾g后的類已經(jīng)注冊在
runtime
中航罗,類結(jié)構(gòu)體中的objc_ivar_list
實(shí)例變量的鏈表和instance_size
實(shí)例變量的內(nèi)存大小已經(jīng)確定,同時(shí)runtime
會(huì)調(diào)用class_setIvarLayout
或class_setWeakIvarLayout
來處理strong weak
引用妇多,所以不能向存在的類中添加實(shí)例變量
運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量伤哺,調(diào)用class_addIvar
函數(shù)。但是得在調(diào)用objc_allocateClassPair
之后,objc_registerClassPair
之前立莉,原因同上
-
runtime
如何通過selector
找到對(duì)應(yīng)的IMP
地址(分別考慮實(shí)例方法和類方法)Selector绢彤、Method 和 IMP
的有什么區(qū)別與聯(lián)系
對(duì)于實(shí)例方法,每個(gè)實(shí)例的
isa
指針指向著對(duì)應(yīng)類對(duì)象蜓耻,而每一個(gè)類對(duì)象中都有一個(gè)對(duì)象方法列表茫舶。對(duì)于類方法,每個(gè)類對(duì)象的isa
指針都指向著對(duì)應(yīng)的元類對(duì)象刹淌,而每一個(gè)元類對(duì)象中都有一個(gè)類方法列表饶氏。方法列表中記錄著方法的名稱,方法實(shí)現(xiàn)有勾,以及參數(shù)類型疹启,其實(shí)selector
本質(zhì)就是方法名稱,通過這個(gè)方法名稱就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn)
Selector蔼卡、Method 和 IMP
的關(guān)系可以這樣描述:在運(yùn)行期分發(fā)消息喊崖,方法列表中的每一個(gè)實(shí)體都是一個(gè)方法(Method
)它的名字叫做選擇器(SEL
)對(duì)應(yīng)著一種方法實(shí)現(xiàn)(IMP
)
-
objc_msgSend、_objc_msgForward
都是做什么的雇逞?OC 中的消息調(diào)用流程是怎樣的
objc_msgSend
是用來做消息發(fā)送的荤懂。在OC
中,對(duì)方法的調(diào)用都會(huì)被轉(zhuǎn)換成內(nèi)部的消息發(fā)送執(zhí)行_objc_msgForward
是IMP
類型(函數(shù)指針)用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息塘砸,但它并沒有實(shí)現(xiàn)的時(shí)候节仿,_objc_msgForward
會(huì)嘗試做消息轉(zhuǎn)發(fā)- 在消息調(diào)用的過程中,
objc_msgSend
的動(dòng)作比較清晰:首先在Class
中的緩存查找IMP
(沒緩存則初始化緩存)如果沒找到掉蔬,則向父類的Class
查找廊宪。如果一直查找到根類仍舊沒有實(shí)現(xiàn),則用_objc_msgForward
函數(shù)指針代替IMP
女轿。最后挤忙,執(zhí)行這個(gè)IMP
。當(dāng)調(diào)用一個(gè)NSObject
對(duì)象不存在的方法時(shí)谈喳,并不會(huì)馬上拋出異常册烈,而是會(huì)經(jīng)過多層轉(zhuǎn)發(fā),層層調(diào)用對(duì)象的-resolveInstanceMethod:婿禽、-forwardingTargetForSelector:赏僧、-methodSignatureForSelector:、-forwardInvocation:
等方法扭倾。其中最后-forwardInvocation:
是會(huì)有一個(gè)NSInvocation
對(duì)象淀零,這個(gè)NSInvocation
對(duì)象保存了這個(gè)方法調(diào)用的所有信息,包括Selector名膛壹,參數(shù)和返回值類型
驾中,可以從這個(gè)NSInvocation
對(duì)象里拿到調(diào)用的所有參數(shù)值
-
class
方法和objc_getClass
方法有什么區(qū)別
object_getClass(obj)
返回的是obj
中的isa
指針唉堪,即指向類對(duì)象的指針;而[obj class]
則分兩種情況:一是當(dāng)obj
為實(shí)例對(duì)象時(shí)肩民,[obj class]
中class
是實(shí)例方法唠亚,返回的是obj
對(duì)象中的isa
指針;二是當(dāng)obj
為類對(duì)象(包括元類和根類以及根元類)時(shí)持痰,調(diào)用的是類方法灶搜,返回的結(jié)果為其本身
- OC中向一個(gè)
nil
對(duì)象發(fā)送消息將會(huì)發(fā)生什么
在
OC
中向nil
發(fā)送消息是完全有效的,只是在運(yùn)行時(shí)不會(huì)有任何作用工窍;向一個(gè)nil
對(duì)象發(fā)送消息割卖,首先在尋找對(duì)象的isa
指針時(shí)就是0地址
返回了,所以不會(huì)出現(xiàn)任何錯(cuò)誤患雏,也不會(huì)崩潰
-
_objc_msgForward
函數(shù)是做什么的鹏溯?直接調(diào)用它將會(huì)發(fā)生什么
_objc_msgForward
是一個(gè)函數(shù)指針(和IMP
的類型一樣)用于消息轉(zhuǎn)發(fā);當(dāng)向一個(gè)對(duì)象發(fā)送一條消息淹仑,但它并沒有實(shí)現(xiàn)的時(shí)候剿涮,_objc_msgForward
會(huì)嘗試做消息轉(zhuǎn)發(fā)
objc_msgSend
在消息傳遞
中的作用。在消息傳遞
過程中攻人,objc_msgSend
的動(dòng)作比較清晰:首先在Class
中的緩存查找IMP
(沒有緩存則初始化緩存
)如果沒找到,則向父類的Class
查找悬槽。如果一直查找到根類
仍舊沒有實(shí)現(xiàn)怀吻,則用_objc_msgForward
函數(shù)指針代替IMP
,最后執(zhí)行這個(gè)IMP
一旦調(diào)用了_objc_msgForward
初婆,將跳過查找IMP
的過程蓬坡,直接觸發(fā)消息轉(zhuǎn)發(fā)
,如果調(diào)用了_objc_msgForward
磅叛,即使這個(gè)對(duì)象確實(shí)已經(jīng)實(shí)現(xiàn)了這個(gè)方法屑咳,你也會(huì)告訴objc_msgSend
,我沒有在這個(gè)對(duì)象里找到這個(gè)方法的實(shí)現(xiàn)弊琴,如果用不好會(huì)直接導(dǎo)致程序Crash
- 什么時(shí)候會(huì)報(bào)
unrecognized selector
的異常
- 當(dāng)調(diào)用該對(duì)象上某個(gè)方法兆龙,而該對(duì)象上沒有實(shí)現(xiàn)這個(gè)方法的時(shí)候∏枚可以通過
消息轉(zhuǎn)發(fā)
進(jìn)行解決紫皇,流程見下圖
-
OC
在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime
庫會(huì)根據(jù)對(duì)象的isa
指針找到該對(duì)象實(shí)際所屬的類腋寨,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行聪铺,如果在最頂層的父類中依然找不到相應(yīng)的方法時(shí),程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX
但是在這之前萄窜,OC的運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì) - Method resolution(消息動(dòng)態(tài)解析)
OC
運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:
或者+resolveClassMethod:
铃剔,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)撒桨。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程键兜,否則凤类,運(yùn)行時(shí)就會(huì)移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding
)
// 重寫 resolveInstanceMethod: 添加對(duì)象方法實(shí)現(xiàn)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 如果是執(zhí)行 run 函數(shù)蝶押,就動(dòng)態(tài)解析踱蠢,指定新的 IMP
if (sel == NSSelectorFromString(@"run:")) {
// class: 給哪個(gè)類添加方法
// SEL: 添加哪個(gè)方法
// IMP: 方法實(shí)現(xiàn) => 函數(shù) => 函數(shù)入口 => 函數(shù)名
// type: 方法類型:void用v來表示,id參數(shù)用@來表示棋电,SEL用:來表示
class_addMethod(self, sel, (IMP)runMethod, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//新的 run 函數(shù)
void runMethod(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
- Fast forwarding(消息接受者重定向)
如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:
茎截,Runtime
這時(shí)就會(huì)調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)赶盔。只要這個(gè)方法返回的不是nil
和self
企锌,整個(gè)消息發(fā)送的過程就會(huì)被重啟,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象于未。否則撕攒,就會(huì)繼續(xù)Normal Fowarding
。 這里叫Fast
烘浦,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制抖坪。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè)NSInvocation
對(duì)象闷叉,所以相對(duì)更快點(diǎn)
// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run:)) {
return [[Person alloc] init];
// 返回 Person 對(duì)象擦俐,讓 Person 對(duì)象接收這個(gè)消息
}
return [super forwardingTargetForSelector:aSelector];
}
- Normal forwarding(消息重定向)
這一步是Runtime
最后一次給你挽救的機(jī)會(huì)。首先它會(huì)發(fā)送-methodSignatureForSelector:
消息獲得函數(shù)的參數(shù)和返回值類型握侧。如果-methodSignatureForSelector:
返回nil
蚯瞧,Runtime
則會(huì)發(fā)出-doesNotRecognizeSelector:
消息,程序這時(shí)也就掛掉了品擎。如果返回了一個(gè)函數(shù)簽名埋合,Runtime
就會(huì)創(chuàng)建一個(gè)NSInvocation
對(duì)象并發(fā)送-forwardInvocation:
消息給目標(biāo)對(duì)象
// 獲取函數(shù)的參數(shù)和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 從 anInvocation 中獲取消息
SEL sel = anInvocation.selector;
if (sel == NSSelectorFromString(@"run:")) {
// 1. 指定當(dāng)前類的一個(gè)方法作為IMP
// anInvocation.selector = @selector(readBook:);
// [anInvocation invoke];
// 2. 指定其他類來執(zhí)行這個(gè)IMP
Person *p = [[Person alloc] init];
// 判斷 Person 對(duì)象方法是否可以響應(yīng) sel
if([p respondsToSelector:sel]) {
// 若可以響應(yīng)萄传,則將消息轉(zhuǎn)發(fā)給其他對(duì)象處理
[anInvocation invokeWithTarget:p];
} else {
// 若仍然無法響應(yīng)甚颂,則報(bào)錯(cuò):找不到響應(yīng)方法
[self doesNotRecognizeSelector:sel];
}
}else{
[super forwardInvocation:anInvocation];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
[super doesNotRecognizeSelector:aSelector];
}
既然-forwardingTargetForSelector:
和-forwardInvocation:
都可以將消息轉(zhuǎn)發(fā)給其他對(duì)象處理,那么兩者的區(qū)別在哪秀菱?
區(qū)別就在于-forwardingTargetForSelector:
只能將消息轉(zhuǎn)發(fā)給一個(gè)對(duì)象西设。而-forwardInvocation:
可以把消息存儲(chǔ),在你覺得合適的時(shí)機(jī)轉(zhuǎn)發(fā)出去答朋,或者不處理這個(gè)消息贷揽。修改消息的target,selector梦碗,參數(shù)等禽绪。將消息轉(zhuǎn)發(fā)給多個(gè)對(duì)象
-
iOS layoutSubviews
什么時(shí)候會(huì)被調(diào)用
init
方法不會(huì)調(diào)用layoutSubviews
蓖救,但是是用initWithFrame
進(jìn)行初始化時(shí),當(dāng)rect
的值不為CGRectZero
時(shí)印屁,會(huì)觸發(fā)addSubview
會(huì)觸發(fā)layoutSubviews
方法setFrame
只有當(dāng)設(shè)置的frame
的參數(shù)的size
與原來的size
不同循捺,才會(huì)觸發(fā)其view
的layoutSubviews
方法- 滑動(dòng)
UIScrollView
會(huì)調(diào)用scrollview
及scrollview
上的view
的layoutSubviews
方法- 旋轉(zhuǎn)設(shè)備只會(huì)調(diào)用
VC
的view
的layoutSubviews
方法- 直接調(diào)用
[self setNeedsLayout];
(這個(gè)在上面蘋果官方文檔里有說明)
-layoutSubviews
方法:這個(gè)方法默認(rèn)沒有做任何事情,需要子類進(jìn)行重寫
-setNeedsLayout
方法:標(biāo)記為需要重新布局雄人,異步調(diào)用layoutIfNeeded
刷新布局从橘,不立即刷新,但layoutSubviews
一定會(huì)被調(diào)用
-layoutIfNeeded
方法:如果有需要刷新的標(biāo)記础钠,立即調(diào)用layoutSubviews
進(jìn)行布局(如果沒有標(biāo)記恰力,不會(huì)調(diào)用layoutSubviews
)
如果要立即刷新,要先調(diào)用[view setNeedsLayout]
旗吁,把標(biāo)記設(shè)為需要布局踩萎,然后馬上調(diào)用[view layoutIfNeeded]
,實(shí)現(xiàn)布局
在視圖第一次顯示之前很钓,標(biāo)記總是需要刷新
的香府,可以直接調(diào)用[view layoutIfNeeded]
- 下面代碼會(huì)發(fā)生什么問題
@property (nonatomic, strong) NSString *str;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];
});
}
會(huì)crash
。因?yàn)樵诓⑿嘘?duì)列DISPATCH_QUEUE_CONCURRENT
中異步dispatch_async
對(duì)str
屬性進(jìn)行賦值码倦,就會(huì)導(dǎo)致str
已經(jīng)被release
了企孩,還會(huì)執(zhí)行release
。這就是向已釋放內(nèi)存的對(duì)象發(fā)送消息而發(fā)生crash
詳細(xì)解析:對(duì)str
屬性strong
修飾進(jìn)行賦值袁稽,相當(dāng)與MRC
中的
- (void)setStr:(NSString *)str{
if (str == _str) return;
id pre = _str;
[str retain];//1.先保留新值
_str = str;//2.再進(jìn)行賦值
[pre release];//3.釋放舊值
}
那么假如并發(fā)隊(duì)列
里調(diào)度的線程A
執(zhí)行到步驟1
勿璃,還沒到步驟2
時(shí),線程B
執(zhí)行到步驟3
运提,那么當(dāng)線程A
再執(zhí)行步驟3
時(shí),舊值就會(huì)被過度釋放
闻葵,導(dǎo)致向已釋放內(nèi)存的對(duì)象發(fā)送消息而崩潰
- 追問:怎么修改這段代碼變?yōu)椴槐罎⒛?/li>
1民泵、使用串行隊(duì)列
將set
方法改成在串行隊(duì)列中執(zhí)行就行,這樣即使異步槽畔,但所有block
操作追加在隊(duì)列最后依次執(zhí)行
2栈妆、使用atomic
atomic
關(guān)鍵字相當(dāng)于在setter
方法加鎖,這樣每次執(zhí)行setter
都是線程安全的厢钧,但這只是單獨(dú)針對(duì)setter
方法而言的狹義的線程安全
3鳞尔、使用weak
關(guān)鍵字
weak
的setter
沒有保留新值
的操作,所以不會(huì)引發(fā)重復(fù)釋放早直。當(dāng)然這個(gè)時(shí)候要看具體情況能否使用weak
寥假,可能值并不是所需要的值
4、使用互斥鎖霞扬,保證數(shù)據(jù)訪問的唯一性@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];}
5糕韧、使用Tagged Pointer
Tagged Pointer
是蘋果在64位系統(tǒng)引入的內(nèi)存技術(shù)枫振。簡單來說就是對(duì)于NSString
(內(nèi)存小于60位的字符串)或NSNumber
(小于2^31),64位的指針有8個(gè)字節(jié)萤彩,完全可以直接用這個(gè)空間來直接表示值粪滤,這樣的話其實(shí)會(huì)將NSString
和NSNumber
對(duì)象由一個(gè)指針
轉(zhuǎn)換成一個(gè)值類型
,而值類型的setter和getter
又是原子的雀扶,從而線程安全
- 發(fā)散:下面代碼會(huì)
crash
嗎
@property (nonatomic, strong) NSString *str;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
// 相比上面杖小,僅字符串變短了
self.str = [NSString stringWithFormat:@"%d",i];
NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str);
});
}
不會(huì)crash
。而且發(fā)現(xiàn)str
這個(gè)字符串類型是NSTaggedPointerString
Tagged Pointer
是一個(gè)能夠提升性能愚墓、節(jié)省內(nèi)存的有趣的技術(shù)
Tagged Pointer
專門用來存儲(chǔ)小的對(duì)象予权,例如NSNumber
和NSDate
(后來可以存儲(chǔ)小字符串)
Tagged Pointer指針的值
不再是地址
了,而是真正的值
转绷。所以伟件,實(shí)際上它不再是一個(gè)對(duì)象
了,它只是一個(gè)披著對(duì)象皮的普通變量
而已
它的內(nèi)存并不存儲(chǔ)在堆
中议经,也不需要malloc和free
斧账,所以擁有極快的讀取和創(chuàng)建速度