iOS 面試題-2019.上

  1. UIViewCALayer是什么關(guān)系
  • UIView繼承自UIResponder類智袭,可以響應(yīng)事件
  • CALayer直接繼承自NSObject類,不可以響應(yīng)事件
  • UIViewCALayerdelegate(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勃黍,但是LayerView多了個(gè)AnchorPoint
  1. NSCacheNSMutableDictionary的相同點(diǎn)與區(qū)別

相同點(diǎn):
NSCacheNSMutableDictionary功能用法基本是相同的
區(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)存
NSCacheKey只是對(duì)對(duì)象進(jìn)行了Strong引用,而非拷貝瓢省,所以不需要實(shí)現(xiàn)NSCopying協(xié)議

  1. 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è)完整的值,但獲取到的值不可控
  1. 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è)方法
  1. 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ò)

  1. 你是否接觸過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:));
  1. 這個(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)行添加/修改

  1. 如何讓自己的類用copy修飾符

若想令自己所寫的對(duì)象具有拷貝功能,則需實(shí)現(xiàn)NSCopying協(xié)議职员。如果自定義的對(duì)象分為可變版本與不可變版本麻蹋,那么就要同時(shí)實(shí)現(xiàn)NSCopyingNSMutableCopying協(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;
}
  1. 為什么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ì)造成野指針

  1. 請(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

  1. 一個(gè)view已經(jīng)初始化完畢绵估,view上面添加了n個(gè)button(可能使用循環(huán)創(chuàng)建),除用viewtag之外卡骂,還可以采用什么辦法來找到自己想要的button來修改Button的值

第一種:如果是點(diǎn)擊某個(gè)按鈕后国裳,才會(huì)刷新它的值,其它不用修改全跨,那么不用引用任何按鈕缝左,直接在回調(diào)時(shí),就已經(jīng)將接收響應(yīng)的按鈕給傳過來了浓若,直接通過它修改即可
第二種:點(diǎn)擊某個(gè)按鈕后渺杉,所有與之同類型的按鈕都要修改值,那么可以通過在創(chuàng)建按鈕時(shí)將按鈕存入到數(shù)組中挪钓,在需要的時(shí)候遍歷查找

  1. UIViewControllerviewDidUnload是越、viewDidLoadloadView分別什么時(shí)候調(diào)用?UIViewdrawRectlayoutSubviews分別起什么作用

第一個(gè)問題:
在控制器被銷毀前會(huì)調(diào)用viewDidUnloadMRC下才會(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

  1. 自動(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ì)象銷毀

  1. 蘋果是如何實(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ù)
  1. 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)釋放池了
}
  1. 簡述內(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í)例

  1. 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

  1. 什么是離屏渲染女揭?什么情況下會(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)前屏幕

  1. 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

  1. 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)用類方法(通過類名)
  1. 能否向編譯后得到的類中增加實(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_setIvarLayoutclass_setWeakIvarLayout來處理strong weak引用妇多,所以不能向存在的類中添加實(shí)例變量
    運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量伤哺,調(diào)用class_addIvar函數(shù)。但是得在調(diào)用objc_allocateClassPair之后,objc_registerClassPair之前立莉,原因同上
  1. 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

  1. objc_msgSend、_objc_msgForward都是做什么的雇逞?OC 中的消息調(diào)用流程是怎樣的
  • objc_msgSend是用來做消息發(fā)送的荤懂。在OC中,對(duì)方法的調(diào)用都會(huì)被轉(zhuǎn)換成內(nèi)部的消息發(fā)送執(zhí)行
  • _objc_msgForwardIMP類型(函數(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ù)值
  1. 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é)果為其本身

  1. 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ì)崩潰

  1. _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

  1. 什么時(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è)方法返回的不是nilself企锌,整個(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ì)象

  1. 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ā)其viewlayoutSubviews方法
  • 滑動(dòng)UIScrollView會(huì)調(diào)用scrollviewscrollview上的viewlayoutSubviews方法
  • 旋轉(zhuǎn)設(shè)備只會(huì)調(diào)用VCviewlayoutSubviews方法
  • 直接調(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]
  1. 下面代碼會(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)鍵字
weaksetter沒有保留新值的操作,所以不會(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ì)將NSStringNSNumber對(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ì)象予权,例如NSNumberNSDate(后來可以存儲(chǔ)小字符串)
Tagged Pointer指針的值不再是地址了,而是真正的值转绷。所以伟件,實(shí)際上它不再是一個(gè)對(duì)象了,它只是一個(gè)披著對(duì)象皮的普通變量而已
它的內(nèi)存并不存儲(chǔ)在中议经,也不需要malloc和free斧账,所以擁有極快的讀取和創(chuàng)建速度

附:我的博客地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市煞肾,隨后出現(xiàn)的幾起案子咧织,更是在濱河造成了極大的恐慌,老刑警劉巖籍救,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件习绢,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝙昙,警方通過查閱死者的電腦和手機(jī)闪萄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇颠,“玉大人败去,你說我怎么就攤上這事×揖埽” “怎么了圆裕?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荆几。 經(jīng)常有香客問我吓妆,道長,這世上最難降的妖魔是什么吨铸? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任行拢,我火速辦了婚禮,結(jié)果婚禮上诞吱,老公的妹妹穿的比我還像新娘剂陡。我一直安慰自己狈涮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布鸭栖。 她就那樣靜靜地躺著歌馍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晕鹊。 梳的紋絲不亂的頭發(fā)上松却,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音溅话,去河邊找鬼晓锻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛飞几,可吹牛的內(nèi)容都是我干的砚哆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼屑墨,長吁一口氣:“原來是場噩夢啊……” “哼躁锁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卵史,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤战转,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后以躯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體槐秧,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年忧设,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刁标。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡址晕,死狀恐怖膀懈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斩箫,我是刑警寧澤吏砂,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布撵儿,位于F島的核電站乘客,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淀歇。R本人自食惡果不足惜易核,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浪默。 院中可真熱鬧牡直,春花似錦缀匕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饵史,卻和暖如春满钟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胳喷。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來泰國打工湃番, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吭露。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓吠撮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讲竿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泥兰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,111評(píng)論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承戴卜、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來控制對(duì)象的生命周期逾条。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,108評(píng)論 0 10
  • 出題者簡介: 孫源(sunnyxx),目前就職于百度投剥,負(fù)責(zé)百度知道 iOS 客戶端的開發(fā)工作师脂,對(duì)技術(shù)喜歡刨根問底和...
    戈多_于勒閱讀 1,798評(píng)論 0 5
  • 白露為霜夜深涼,墻頭蛛網(wǎng)密密織江锨,煩心愁緒近三載吃警,百年孤獨(dú)心蒼涼!
    艾薇糖糖閱讀 174評(píng)論 0 0
  • 來歲民是咱馬坊木張人啄育,我初中時(shí)的同學(xué)酌心。他雖然個(gè)子不高,身材瘦小挑豌、單薄安券,但人精明能干,機(jī)智靈活氓英,勤勞質(zhì)樸侯勉,老...
    耿平海閱讀 2,956評(píng)論 47 95