首先感謝這些博主:我這里只是學習和摘抄
七秒記憶的魚兒 -- 2017年iOS面試題總結
就叫yang -- # iOS基礎 # iOS面試題一
iOS-Interview -- 有條理性
iOS 系統層次架構
iOS 系統層次架構
繼承和多態的區別
1、屬性關鍵字
#默認值:
1.數據類型
atomic assign readwrite
2.對象類型
atomic strong readwrite
1). readwrite 是可讀可寫特性;需要生成getter方法和setter方法時
2). readonly 是只讀特性 只會生成getter方法 不會生成setter方法 ;不希望屬性在類外改變
3). assign 是賦值特性,setter方法將傳入參數賦值給實例變量;僅設置變量時;
assign 主要用于修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。
4). weak表示指向但不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。
5). copy 表示賦值特性,setter方法將傳入對象復制一份;需要完全一份新的變量時。
6). nonatomic 非原子操作,決定編譯器生成的setter getter是否是原子操作,atomic表示多線程安全,一般使用nonatomic
7). strong 表示指向并擁有該對象。其修飾的對象引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。
8). retain 表示持有特性,setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1;
iOS9的幾個新關鍵字: nonnull、nullable、null_resettable、__null_unspecified
1. nonnull:字面意思就能知道:不能為空(用來修飾屬性,或者方法的參數,方法的返回值)
//三種使用方式都可以
@property (nonatomic, copy, nonnull) NSString *name;
@property (nonatomic, copy) NSString * _Nonnull name;
@property (nonatomic, copy) NSString * __nonnull name;
//不適用于assign屬性,因為它是專門用來修飾指針的
@property (nonatomic, assign) NSUInteger age;
//用下面宏包裹起來的屬性全部都具nonnull特征,當然,如果其中某個屬性你不希望有這個特征,也可以自己定義,比如加個nullable
//在NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END之間,定義的所有對象屬性和方法默認都是nonnull
//也可以在定義方法的時候使用
//返回值和參數都不能為空
- (nonnull NSString *)test:(nonnull NSString *)name;
//同上
- (NSString * _Nonnull)test1:(NSString * _Nonnull)name;
2. nullable 表示可以為空。
//三種使用方式
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, copy) NSString *_Nullable name;
@property (nonatomic, copy) NSString *__nullable name;
3、null_resettable: get:不能返回空, set可以為空(注意:如果使用null_resettable,
必須重寫get方法或者set方法,處理傳遞的值為空的情況)
// 書寫方式:
@property (nonatomic, copy, null_resettable) NSString *name;
設置一下set或get方法
- (void)setName:(NSString *)name
{
if (name == nil) {
name = @"Jone";
}
_name = name;
}
- (NSString *)name
{
if (_name == nil) {
_name = @"Jone";
}
return _name;
}
4、_Null_unspecified:不確定是否為空
使用方式只有這兩種:
@property (nonatomic, strong) NSString *_Null_unspecified name;
@property (nonatomic, strong) NSString *__null_unspecified name;
1.1深拷貝和淺拷貝?
淺拷貝:淺拷貝并不拷貝對象本身,只是對指向對象的指針進行拷貝
深拷貝:直接拷貝對象到內存中一塊區域,然后把新對象的指針指向這塊內存(生成新對象)
#在非集合類對象中:
[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //深復制
[mutableObject copy] //深復制
[mutableObject mutableCopy] //深復制
#在集合對象中:
對 immutable 對象進行 copy,是指針復制【淺拷貝】, mutableCopy 是內容復制【深拷貝】;對 mutable 對象進行 copy 和 mutableCopy 都是內容復制。
#但是:集合對象的內容復制僅限于對象本身,對象里面的每個元素仍然是指針復制。
[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //單層深復制
[mutableObject copy] //單層深復制
[mutableObject mutableCopy] //單層深復制
#iOS 中集合對象的“深拷貝”只拷貝了一個殼,對于殼內的每個元素是淺拷貝。
1.2.0 如何讓自己的類用copy修飾符?
若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對象分為可變版本與不可變版本,
那么就要同時實現 NSCopying 與 NSMutableCopying 協議。
具體步驟:
1)需聲明該類遵從 NSCopying 協議
2)實現 NSCopying 協議。該協議只有一個方法:
- (id)copyWithZone:(NSZone *)zone;
1.2 寫一個setter方法用于完成@property (nonatomic,retain)NSString *name,寫一個setter方法用于完成@property(nonatomic,copy)NSString *name
- (void)setName:(NSString*)str
{
[str retain];
[name release];
name = str;
}
- (void)setName:(NSString *)str
{
id t = [str copy];
[name release];
name = t;
}
1.3 用@property聲明的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,
是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,
他們之間可能進行賦值操作(就是把可變的賦值給不可變的),為確保對象中的字符串值不會無意間變動,
應該在設置新屬性值時拷貝一份。
1. 因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,
使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
2. 如果我們使用是 strong ,那么這個屬性就有可能指向一個可變對象,
如果這個可變對象在外部被修改了,那么會影響該屬性。
#總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,
# 可變類型對象的值發送變化會無意間篡改不可變類型對象原來的值。
1.4.請解釋以下keywords的區別: assign vs weak, __block vs __weak
它們都是是一個弱引用。
assign適用于基本數據類型,weak是適用于NSObject對象,
assign其實也可以用來修飾對象,那么我們為什么不用它呢?因為被assign修飾的對象在釋放之后,
指針的地址還是存在的,也就是說指針并沒有被置為nil。
如果在后續的內存分配中,剛好分到了這塊地址,程序就會崩潰掉。
#而weak修飾的對象在釋放之后,指針地址會被置為nil。所以現在一般弱引用就是用weak。
首先__block是用來修飾一個變量,這個變量就可以在block中被修改(參考block實現原理)
__block:使用__block修飾的變量在block代碼快中會被retain(ARC下,MRC下不會retain)
__weak:使用__weak修飾的變量不會在block代碼塊中被retain
同時,在ARC下,要避免block出現循環引用 __weak
1.5 synthesize和dynamic分別有什么作用
@synthesize的作用:
為Property指定生成要生成的成員變量名,并生成getter和setter方法。
用途:對于只讀屬性,如果同時重新setter和getter方法,就需要使用synthesize來手動合成成員變量
@dynamic的作用:
告訴編譯器,不用為指定的Property生成getter和setter。使用方式:當我們在分類中使用Property為類擴展屬性時,
編譯器默認不會為此property生成getter和setter,這時就需要用dynamic告訴編譯器,自己合成了
2、說一下appdelegate的幾個方法?從后臺到前臺調用了哪些方法?第一次啟動調用了哪些方法?從前臺到后臺調用了哪些方法?iOS應用程序生命周期(前后臺切換,應用的各種狀態)詳解
image
image
3.ViewController生命周期
按照執行順序排列:
1. initWithCoder:通過nib文件初始化時觸發。
2. awakeFromNib:nib文件被加載的時候,會發生一個awakeFromNib的消息到nib文件中的每個對象。
//如果不是nib初始化 上面兩個換成 initWithNibName:bundle:
3. loadView:開始加載視圖控制器自帶的view。
4. viewDidLoad:視圖控制器的view被加載完成。
5. viewWillAppear:視圖控制器的view將要顯示在window上。
6. updateViewConstraints:視圖控制器的view開始更新AutoLayout約束。
7. viewWillLayoutSubviews:視圖控制器的view將要更新內容視圖的位置。
8. viewDidLayoutSubviews:視圖控制器的view已經更新視圖的位置。
9. viewDidAppear:視圖控制器的view已經展示到window上。
10.viewWillDisappear:視圖控制器的view將要從window上消失。
11.viewDidDisappear:視圖控制器的view已經從window上消失。
3.1 layoutsubviews是在什么時機調用的?
1.init初始化不會觸發。
2.addSubview時。
3.設置frame且前后值變化,frame為zero且不添加到指定視圖不會觸發。
(ps: 當view的size的值為0的時候,addSubview也不會調用layoutSubviews。
當要給這個view添加子控件的時候不管他的size有沒有值都會調用)
4.旋轉Screen會觸發父視圖的layoutSubviews。
5.滾動UIScrollView引起View重新布局時會觸發layoutSubviews。
3.2 什么是異步渲染?
異步渲染就是在子線程進行繪制,然后拿到主線程顯示。
UIView的顯示是通過CALayer實現的,CALayer的顯示則是通過contents進行的。
異步渲染的實現原理是當我們改變UIView的frame時,會調用layer的setNeedsDisplay,然后調用layer的display方法。
我們不能在非主線程將內容繪制到layer的context上,但我們單獨開一個子線程通過CGBitmapContextCreateImage()繪制內容,
繪制完成之后切回主線程,將內容賦值到contents上。
3.3 frame和Bounds 以及frame和bounds區別
1.frame不管對于位置還是大小,改變的都是自己本身
2、frame的位置是以父視圖的坐標系為參照,從而確定當前視圖在父視圖中的位置
3、frame的大小改變時,當前視圖的左上角位置不會發生改變,只是大小發生改變
4、bounds改變位置時,改變的是子視圖的位置,自身沒有影響;
其實就是改變了本身的坐標系原點,默認本身坐標系的原點是左上角
5、bounds的大小改變時,當前視圖的中心點不會發生改變,
當前視圖的大小發生改變,看起來效果就想縮放一樣
3.4 UIView和CALayer是啥關系?
一、UIView 負責響應事件,CALayer 負責繪制 UI
1.首先從繼承關系來分析兩者:UIView : UIResponder,CALayer : NSObject。
2.UIView 對 CALayer 封裝屬性
UIView 中持有一個 layer 對象,同時是這個 layer 對象 delegate,UIView 和 CALayer 協同工作。
平時我們對 UIView 設置 frame、center、bounds 等位置信息,其實都是 UIView 對 CALayer 進一層封裝,
使得我們可以很方便地設置控件的位置;例如圓角、陰影等屬性, UIView 就沒有進一步封裝,
所以我們還是需要去設置 Layer 的屬性來實現功能。
Frame 屬性主要是依賴:bounds、anchorPoint、transform、和position。
3.UIView 是 CALayer 的代理
UIView 持有一個 CALayer 的屬性,并且是該屬性的代理,用來提供一些 CALayer 行的數據,例如動畫和繪制。
//繪制相關
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
//動畫相關
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;
4.self 和 super
self 是類的隱藏參數,指向當前調用方法的這個類的實例。
super是一個Magic Keyword,它本質是一個編譯器標示符,和self是指向的同一個消息接收者。
不同的是:super會告訴編譯器,調用class這個方法時,要去父類的方法,而不是本類里的。
NSArray與NSSet的區別?
1.
NSArray內存中存儲地址連續,而NSSet不連續
NSSet效率高,內部使用hash查找;NSArray查找需要遍歷
NSSet通過anyObject訪問元素,NSArray通過下標訪問
2、NSHashTable與NSMapTable?
NSHashTable是NSSet的通用版本,對元素弱引用,可變類型;可以在訪問成員時copy
NSMapTable是NSDictionary的通用版本,對元素弱引用,可變類型;可以在訪問成員時copy
(注:NSHashTable與NSSet的區別:NSHashTable可以通過option設置元素弱引用/copyin,只有可變類型。但是添加對象的時候NSHashTable耗費時間是NSSet的兩倍。
NSMapTable與NSDictionary的區別:同上)
3.說說NSCache優于NSDictionary的幾點
1.NSCache是可以自動釋放內存的。
2. NSCache是線程安全的,我們可以在不同的線程中添加,刪除和查詢緩存中的對象。
3.NSCache可以設置緩存上限,限制對象個數和總緩存開銷。定義了刪除緩存對象的時機。這個機制只對NSCache起到指導作用,不會一定執行。
4.一個緩存對象不會拷貝key對象。
5.事件傳遞和響應者鏈條
#響應者鏈條
1.如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
2.在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,
則其將事件或消息傳遞給window對象進行處理
3.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
4.如果UIApplication也不能處理該事件或消息,則將其丟棄
#點擊屏幕時是如何互動的?
1.iOS系統檢測到手指觸摸(Touch)操作時會將其打包成一個UIEvent對象,
2.并放入當前活動Application的事件隊列中,
3.單例的UIApplication會從事件隊列中取出觸摸事件并傳遞給單例的UIWindow來處理,
4.UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),
即需要將觸摸事件傳遞給其處理的視圖,這個過程稱之為hit-test view。
UIWindow實例對象會首先在它的內容視圖上調用hitTest:withEvent:,
此方法會在其視圖層級結構中的每個視圖上調用pointInside:withEvent:
(該方法用來判斷點擊事件發生的位置是否處于當前視圖范圍內,以確定用戶是不是點擊了當前視圖),
如果pointInside:withEvent:返回YES,否則繼續逐級調用,直到找到touch操作發生的位置,
這個視圖也就是要找的hit-test view。
#hitTest:withEvent:方法的處理流程如下:
1、首先調用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內;
2。若返回NO,則hitTest:withEvent:返回nil;
3.若返回YES,則向當前視圖的所有子視圖(subviews)發送hitTest:withEvent:消息,
所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,
即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;
4.若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象,處理結束;
如所有子視圖都返回非,則hitTest:withEvent:方法返回自身(self)。
#事件的傳遞和響應分兩個鏈:
#傳遞鏈:由系統向離用戶最近的view傳遞。
UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
#響應鏈:由離用戶最近的view向系統傳遞。
initial view –> super view –> …..–> view controller –> window –> Application
6.category 的作用?它為什么會覆蓋掉原來的方法?
一、什么是分類:利用Objective-C的動態運行時分配機制,可以為現有的類添加新方法
我們添加的實例方法,會被動態的添加到類結構里面的methodList列表里面。
1).Category 的實現原理?
* Category 實際上是 Category_t的結構體,在運行時,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一個方法,執行的實際上是最后一個。
* Category 在剛剛編譯完的時候,和原來的類是分開的,只有在程序運行起來后,通過 Runtime ,Category 和原來的類才會合并到一起。
二、主要作用:
1. 將類的實現分開到不同的文件中
2. 對私有方法的前向引用
3. 向對象添加非正式協議
三、它為什么會覆蓋掉原來的方法?
category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面,
這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,
它只要一找到對應名字的方法,就會罷休^_^,殊不知后面可能還有一樣名字的方法。
所以我們只要順著方法列表找到最后一個對應名字的方法,就可以調用原來類的方法
四、類別和類擴展的區別。
答:category和extensions的不同在于 后者可以添加屬性。另外后者添加的方法是必須要實現的。
6.1.category為什么不能添加屬性?
category 它是在運行期決議的,因為在運行期,對象的內存布局已經確定,如果添加實例變量就會破壞類的內部布局,
這對編譯型語言來說是災難性的。
extension看起來很像一個匿名的category,但是extension和有名字的category幾乎完全是兩個東西。
extension在編譯期決議,它就是類的一部分,在編譯期和頭文件里的@interface以及實現文件里的
@implement一起形成一個完整的類,它伴隨類的產生而產生,亦隨之一起消亡。extension一般用來隱藏類的私有信息,
你必須有一個類的源碼才能為一個類添加extension,所以你無法為系統的類比如NSString添加extension。
但是category則完全不一樣,它是在運行期決議的。
就category和extension的區別來看,我們可以推導出一個明顯的事實,
extension可以添加實例變量,而category是無法添加實例變量的。
6.2. 那為什么 使用Runtime技術中的關聯對象可以為類別添加屬性。
1.其原因是:關聯對象都由AssociationsManager管理,
AssociationsManager里面是由一個靜態AssociationsHashMap來存儲所有的關聯對象的。
這相當于把所有對象的關聯對象都存在一個全局map里面。
而map的的key是這個對象的指針地址(任意兩個不同對象的指針地址一定是不同的),
而這個map的value又是另外一個AssociationsHashMap,里面保存了關聯對象的kv對。
2.如合清理關聯對象?
runtime的銷毀對象函數objc_destructInstance里面會判斷這個對象有沒有關聯對象,
如果有,會調用_object_remove_assocations做關聯對象的清理工作。
Objective-C Associated Objects 的實現原理
7.Block --Block的本質
Block 底層結構
1) 什么是Block和它的本質是什么(block 是帶自動變量的匿名函數)
block本質上也是一個OC對象,它內部也有個isa指針
block是封裝了函數調用以及函數調用環境的OC對象
block是封裝函數及其上下文的OC對象
2) Block 的類型:
__NSGlobalBlock:不使用外部變量的block是全局block
__NSStackBlock: 使用外部變量并且未進行copy操作的block是棧block
__NSMallocBlock:對棧block進行copy操作,就是堆block,而對全局block進行copy,仍是全局block
Block訪問外界變量
MRC 環境下:訪問外界變量的 Block 默認存儲棧中。
ARC 環境下:訪問外界變量的 Block 默認存儲在堆中(實際是放在棧區,然后ARC情況下自動又拷貝到堆區),自動釋放。
3) 在ARC環境下,編譯器會根據情況自動將棧上的block復制到堆上的幾種情況?
1.block作為函數返回值時
2.將block賦值給__strong指針時
3.block作為Cocoa API中方法名含有usingBlock的方法參數時
4.block作為GCD API的方法參數時
4) block 為什么用copy 修飾?
block在創建的時候,它的內存是分配在棧上的,而不是在堆上。他本身的作于域是屬于創建時候的作用域,一旦在創建時候的作用域外面調用block將導致程序崩潰。
因為棧區的特點就是創建的對象隨時可能被銷毀,一旦被銷毀后續再次調用空對象就可能會造成程序崩潰,在對block進行copy后,block存放在堆區.
ps) swift中 closure 與OC中block的區別?
1、closure是匿名函數、block是一個結構體對象
2、closure通過逃逸閉包來在內部修改變量,block 通過 __block 修飾符
Block原理、Block變量截獲、Block的三種形式、__block
8.消息的傳遞方式
#1.KVC也叫鍵值編碼
KVC是基于runtime機制實現的,運用 isa 指針,通過 key 間接對對象的屬性進行操作
程序優先調用setKey屬性值方法-->_key -->_isKey-->如果上面列出的方法或者成員變量都不存在, 系統將會執行該對象的setValue:forUndefinedKey:方法,默認是拋出異常。
#2.KVO也叫鍵值監聽
當某個類的屬性第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,
在這個派生類中重寫基類中任何被觀察屬性的setter 方法。
并修改當前 isa 指針指向這個類,從而在給被監控屬性賦值時執行的是派生類的setter方法
蘋果還重寫了class 方法,返回原來的類
#深入 如何手動調用KVO?
子類setter方法剖析:KVO的鍵值觀察通知依賴于
NSObject 的兩個方法:willChangeValueForKey:和 didChangevlueForKey:,
在存取數值的前后分別調用2個方法: 被觀察屬性發生改變之前,willChangeValueForKey:被調用,
通知系統該 keyPath?的屬性值即將變更;
當改變發生后, didChangeValueForKey: 被調用,
通知系統該 keyPath 的屬性值已經變更;
之后,observeValueForKeyPath:ofObject:change:context: 也會被調用。
且重寫觀察屬性的setter方法這種繼承方式的注入是在運行時而不是編譯時實現的。
#通知和代理有什么區別
通知是觀察者模式,適合一對多的場景
代理模式適合一對一的反向傳值
通知耦合度低,代理的耦合度高
#block和delegate的區別
1.delegate運行成本低,block的運行成本高
block出棧需要將使用的數據從棧內存拷貝到堆內存,當然對象的話就是加計數,
使用完或者block置nil后才消除。delegate只是保存了一個對象指針,直接回調,沒有額外消耗。
就像C的函數指針,只多做了一個查表動作。
2.delegate更適用于多個回調方法(3個以上),block則適用于1,2個回調時。
9.設計模式
單例模式:
簡單的來說,一個單例類,在整個程序中只有一個實例,并且提供一個類方法供全局調用,
在編譯時初始化這個類,然后一直保存在內存中,到程序(APP)退出時由系統自動釋放這部分內存。
優點:
(1)、在整個程序中只會實例化一次,所以在程序如果出了問題,可以快速的定位問題所在;
(2)、由于在整個程序中只存在一個對象,節省了系統內存資源,提高了程序的運行效率;
缺點:
(1)、不能被繼承,不能有子類;
(2)、不易被重寫或擴展(可以使用分類);
(3)、同時,由于單例對象只要程序在運行中就會一直占用系統內存,該對象在閑置時并不能銷毀,在閑置時也消耗了系統內存資源;
Q: 如果單例的靜態變量被置為nil了,是否內存會得到釋放?
A1: 靜態變量修飾的指針保存在了全局區域,不會被釋放。但是指針保存的首地址關聯的對象是保存在堆區的,是會被釋放的。
A2:不會被釋放的,如果想要釋放的話 需要重寫attempDelloc方法,在里面講onceToke置為nil,instance置為nil
代理模式:
一種一對一的消息傳遞方式,由代理對象、委托者、協議三部分組成。
Q:代理用weak還是assign ?
A:assign是指針賦值,不對引用計數操作,使用之后如果沒有置為nil,可能就會產生野指針;而weak一旦不進行使用后,永遠不會使用了,就不會產生野指針。
觀察者模式:
一種一對多的消息傳遞方式,當一個物體發生變化時,會通知所有觀察這個物體的觀察者讓其做出反應
10. 內存管理
10.1 iOS內存分區情況
1. 棧區(Stack)
由編譯器自動分配釋放,存放函數的參數,局部變量的值等
棧是向低地址擴展的數據結構,是一塊連續的內存區域
2.堆區(Heap)
由程序員分配釋放
是向高地址擴展的數據結構,是不連續的內存區域
3.全局區
全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,
未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域
程序結束后由系統釋放
4.常量區
常量字符串就是放在這里的
程序結束后由系統釋放
5.代碼區
存放函數體的二進制代碼
注:
在 iOS 中,堆區的內存是應用程序共享的,堆中的內存分配是系統負責的
系統使用一個鏈表來維護所有已經分配的內存空間(系統僅僅記錄,并不管理具體的內容)
變量使用結束后,需要釋放內存,OC 中是判斷引用計數是否為 0,如果是就說明沒有任何變量使用該空間,那么系統將其回收
當一個 app 啟動后,代碼區、常量區、全局區大小就已經固定,因此指向這些區的指針不會產生崩潰性的錯誤。
而堆區和棧區是時時刻刻變化的(堆的創建銷毀,棧的彈入彈出),所以當使用一個指針指向這個區里面的內存時,
一定要注意內存是否已經被釋放,否則會產生程序崩潰(也即是野指針報錯)
Q1:遞歸調用中棧溢出原因?
函數調用的參數是通過棧空間來傳遞的,在調用過程中會占用線程的棧資源。
而遞歸調用,只有走到最后的結束點后函數才能依次退出,而未到達最后的
結束點之前,占用的棧空間一直沒有釋放,如果遞歸調用次數過多,就可能
導致占用的棧資源超過線程的最大值,從而導致棧溢出,
導致程序的異常退出。
Q2: 無限長的字符串作為函數參數傳參時,會不會有內存問題?
不會,字符串在常量區
1) 什么是 ARC?
ARC全稱是 Automatic Reference Counting,是Objective-C的內存管理機制。
簡單地來說,就是代碼中自動加入了retain/release,原先需要手動添加的
用來處理內存管理的引用計數的代碼,可以自動地由編譯器完成了。
#ARC的使用是為了解決:對象retain和release匹配的問題。
以前手動管理造成內存泄漏或者重復釋放的問題將不復存在。
2) 什么是 MRC?
以前需要手動的通過retain去為對象獲取內存,
并用release釋放內存。所以以前的操作稱為MRC (Manual Reference Counting)。
#內存管理的原則
1.自己生成的對象,自己持有
// 使用了alloc分配了內存,obj指向了對象,該對象本身引用計數為1,不需要retain
id obj = [[NSObject alloc] init];
// 使用了new分配了內存,objc指向了對象,該對象本身引用計數為1,不需要retain
id obj = [NSObject new];
2.也可持有非自己生成的對象
// NSMutableArray通過類方法array產生了對象(并沒有使用alloc、new、copy、mutableCopt來產生對象),因此該對象不屬于obj自身產生的
// 因此,需要使用retain方法讓對象計數器+1,從而obj可以持有該對象(盡管該對象不是他產生的)
id obj = [NSMutableArray array];
[obj retain];
3.不再需要自己持有對象時釋放
id obj = [NSMutableArray array];
[obj retain];
// 當obj不在需要持有的對象,那么,obj應該發送release消息
[obj release];
// 釋放了對象還進行釋放,會導致奔潰
[obj release];
4.非自己持有的對象無法釋放
// 釋放一個不屬于自己的對象
id obj = [NSMutableArray array];
// obj沒有進行retain操作而進行release操作,然后autoreleasePool也會對其進行一次release操作,導致奔潰。
[obj release];
# 針對[NSMutableArray array]方法取得的對象存在,自己卻不持有對象,底層大致實現:
+ (id)object {
//自己持有對象
id obj = [[NSObject alloc]init];
[obj autorelease];
//取得的對象存在,但自己不持有對象
return obj;
}
#注意這些原則里的一些關鍵詞與方法的對應關系:
『生成』- alloc\new\copy\mutableCopy
『持有』- retain
『釋放』- release
『廢棄』- dealloc
10.2 AutoreleasePool底層實現原理
Objective-C Autorelease Pool 的實現原理
使用場景:1、降低內存使用峰值(當你使用類似for循環這樣的邏輯需要產生大量的中間變量時,Autorelease Pool無意是最佳的一種解決方案)
Autorelease Pool作用:緩存池,可以避免我們經常寫relase的一種方式。其實就是延遲release,
將創建的對象,添加到最近的autoreleasePool中,
等到autoreleasePool作用域結束的時候,會將里面所有的對象的引用計數器-1.
#autorelease何時釋放
1.在沒有使用@autoreleasepool的情況,autorelease對象是在當前的runloop迭代結束時釋放。
每個runloop中都會創建一個 autoreleasepool 并在runloop迭代結束進行釋放。
2.如果是手動創建autoreleasepool,自己創建Pool并釋放:
指針常量和常量指針的區別?
1. 指針常量就是指針本身是常量,換句話說,就是指針里面所存儲的內容(內存地址)是常量,不能改變。
但是,內存地址所對應的內容是可以通過指針改變的。
/*指針常量的例子*/
int a,b;
int * const p;
p = &a;//錯誤
p = &b;//錯誤
*p = 20;//正確
2.
常量指針就是指向常量的指針,換句話說,就是指針指向的是常量,
它指向的內容不能發生改變,不能通過指針來修改它指向的內容。
但是,指針自身不是常量,它自身的值可以改變,從而指向另一個常量。
/*常量指針的例子*/
int a,b;
int const *p;
p = &a;//正確
p = &b;//正確
*p = 20;//錯誤
ps: 關于區分指針常量的一個小技巧:const后的內容為不能修改的
數組和鏈表的區別
數組:數組元素在內存上連續存放,可以通過下標查找元素;
插入、刪除需要移動大量元素,比較適用于元素很少變化的情況
鏈表:鏈表中的元素在內存中不是順序存儲的,
查找慢,插入、刪除只需要對元素指針重新賦值,效率高
11.UITableview的優化方法 YYKit 作者--iOS 保持界面流暢的技巧
1、減少所有對主線程有影響的無意義操作
2、避免cell的過多重新布局,差別太大的cell之間不要選擇重用。
3、盡量減少動態添加View的操作
4、簡化cell 的層次結構
4、緩存cell的高度,內容
5、cell中的圖片加載用異步加載,緩存等
6、按需加載,局部更新cell
7、減少離屏渲染
會觸發離屏渲染的操作
(ps:--shouldRasterize(光柵化)
--masks(遮罩)
--shadows(陰影)
--edge antialiasing(抗鋸齒)
--group opacity(不透明)
--復雜形狀設置圓角等
--漸變)
13.網絡
1. post 和 get 的區別
1.GET在瀏覽器回退時是無害的,而POST會再次提交請求。
2.GET產生的URL地址可以被Bookmark,而POST不可以。
3.GET請求會被瀏覽器主動cache,而POST不會,除非手動設置。
4.GET請求只能進行url編碼,而POST支持多種編碼方式。
5.GET請求參數會被完整保留在瀏覽器歷史記錄里,而POST中的參數不會被保留。
6.GET請求在URL中傳送的參數是有長度限制的,而POST么有。
對參數的數據類型,GET只接受ASCII字符,而POST沒有限制。
7.GET比POST更不安全,因為參數直接暴露在URL上,所以不能用來傳遞敏感信息。
8.GET參數通過URL傳遞,POST放在Request body中。
iOS開發之HTTP與HTTPS網絡請求
2. iOS即時通訊,從入門到“放棄”?
3.IM消息送達保證機制實現
14.多線程
1.并行 和 并發 有什么區別?
2.GCD 和 NSOperation 區別及各自應用場景
多線程
14.1進程和線程的區別?同步異步的區別?并行和并發的區別?
1.進程:是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,
進程是系統進行資源分配和調度的一個獨立單位.
2.線程:是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.
線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),
但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.
3.同步:阻塞當前線程操作,不能開辟線程。
4.異步:不阻礙線程繼續操作,可以開辟線程來執行任務。
5.并發:當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,
它只能把CPU運行時間劃分成若干個時間段,再將時間 段分配給各個線程執行,在一個時間段的線程代碼運行時,
其它線程處于掛起狀。.這種方式我們稱之為并發(Concurrent)。
5.并行:當系統有一個以上CPU時,則線程的操作有可能非并發。當一個CPU執行一個線程時,
另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行,這種方式我們稱之為并行(Parallel)。
6.區別:并發和并行是即相似又有區別的兩個概念,并行是指兩個或者多個事件在同一時刻發生;
而并發是指兩個或多個事件在同一時間間隔內發生。在多道程序環境下,并發性是指在一段時間內宏觀上有多個程序在同時運行,
但在單處理機系統中,每一時刻卻僅能有一道程序執行,故微觀上這些程序只能是分時地交替執行。
倘若在計算機系統中有多個處理機,則這些可以并發執行的程序便可被分配到多個處理機上,實現并行執行,
即利用每個處理機來處理一個可并發執行的程序,這樣,多個程序便可以同時執行。
15.Runtime
1.isa指針的理解,對象的isa指針指向哪里?isa指針有哪兩種類型?
* isa 等價于 is kind of
1)實例對象的 isa 指向類對象
2)類對象的 isa 指向元類對象
3)元類對象的 isa 指向元類的基類
* isa 有兩種類型
1)純指針,指向內存地址
2)NON_POINTER_ISA,除了內存地址,還存有一些其他信息
2.能否向編譯后得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?為什么?
不能向編譯后得到的類中增加實例變量;
能向運行時創建的類中添加實例變量;
1.因為編譯后的類已經注冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和
instance_size 實例變量的內存大小已經確定,同時runtime會調用 class_setvarlayout 或
class_setWeaklvarLayout 來處理strong weak 引用.所以不能向存在的類中添加實例變量。
2.運行時創建的類是可以添加實例變量,調用class_addIvar函數. 但是的在調用
objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
3.runtime如何實現weak變量的自動置nil?
runtime 對注冊的類, 會進行布局,
對于 weak 修飾的對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,
當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,
那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
4.runtime如何通過selector找到對應的IMP地址?
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現,以及參數類型,
其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現.
5.objc在向一個對象發送消息時,發生了什么?
1.根據對象的isa指針找到類對象id,
2.在查詢類對象里面的methodLists方法函數列表,
3.如果沒有找到,再沿著superClass,尋找父類,
4.再在父類methodLists方法列表里面查詢,
5.最終找到SEL,根據id和SEL確認IMP(指針函數),在發送消息;
6.當最終查詢不到的時候我們會報unrecognized selector錯誤
6.objc中向一個nil對象發送消息將會發生什么?(返回值是對象,是標量,結構體)
1. 如果一個方法返回值是一個對象,那么發送給nil的消息將返回0(nil)。
例如:Person * motherInlaw = [ aPerson spouse] mother];
如果spouse對象為nil,那么發送給nil的消息mother也將返回nil。
2. 如果方法返回值為指針類型,
其指針大小為小于或者等于sizeof(void*),float,double,long double
或者long long的整型標量,
發送給nil的消息將返回0。
3. 如果方法返回值為結構體,發送給nil的消息將返回0。
結構體中各個字段的值將都是0。其他的結構體數據類型將不是用0填充的。
4. 如果方法的返回值不是上述提到的幾種情況,那么發送給nil的消息的返回值將是未定義的。
7.什么時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
消息轉發機制
答:
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該
類中的方法列表以及其父類方法列表中尋找方法運行,如果,在最頂層的父類中依然找不到相應的方
法時,會進入消息轉發階段,如果消息三次轉發流程仍未實現,則程序在運行時會掛掉并拋出異常
unrecognized selector sent to XXX
#原因:
當發送消息的時候,我們會根據類里面的methodLists列表去查詢我們要動用的SEL,
當查詢不到的時候,我們會一直沿著父類查詢,
當最終查詢不到的時候我們會報unrecognized selector錯誤
#處理機制:會有三次機會處理
#第一次機會:
當系統查詢不到方法的時候,會調用+(BOOL)resolveInstanceMethod:(SEL)sel
動態添加一個新方法并執行的機會。
#第二次機會:
或者我們可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector
這個方法是系統提供的一個將 SEL 轉給其他對象的機會
#第三次機會:
這一步是Runtime最后一次給你挽救的機會。
1.首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。
2.如果-methodSignatureForSelector:返回nil,
Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。
3.如果返回了一個函數簽名,Runtime就會創建一個NSInvocation對象
并發送-forwardInvocation:消息給目標對象。
16.RunLoop -- YYKit 大神對RunLoop的理解 和 sunnyxx 大神對RunLoop 的理解 -- 視頻
1.runloop是來做什么的?runloop和線程有什么關系?主線程默認開啟了runloop么?子線程呢?
RunLoop:RunLoop 實際上就是一個對象,這個對象管理了其需要處理的事件和消息。
一般來說:一個線程一次只能執行一個任務,執行完成后線程就會退出。RunLoop是讓線程能隨時處理事件但并不退出一種機制。
runloop和線程的關系:線程和 RunLoop 之間是一一對應的,其關系是保存在一個全局的 Dictionary 里
主線程默認是開啟一個runloop。也就是這個runloop才能保證我們程序正常的運行。
子線程是默認沒有開始runloop的
2.runloop的mode是用來做什么的?有幾種mode?
model:是runloop里面的模式,不同的模式下的runloop處理的事件和消息有一定的差別。
系統默認注冊了5個Mode:
(1)kCFRunLoopDefaultMode: App的默認 Mode,通常主線程是在這個 Mode 下運行的。
(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響。
(3)UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用。
(4)GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到。
(5)kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用。
注意iOS 對以上5中model進行了封裝
NSDefaultRunLoopMode;
NSRunLoopCommonModes
3.為什么把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環以后,滑動scrollview的時候NSTimer卻不動了?
NSTimer 對象是在 `NSDefaultRunLoopMode`下面調用消息的,
但是當我們滑動scrollview的時候,`NSDefaultRunLoopMode`模式就自動切換到`UITrackingRunLoopMode`模式下面,
卻不可以繼續響應nstime發送的消息。所以如果想在滑動scrollview的情況下面還調用NSTimer的消息,
我們可以把nsrunloop的模式更改為`NSRunLoopCommonModes`
4.AFNetworking 中如何運用 Runloop?
AFURLConnectionOperation 這個類是基于 NSURLConnection 構建的,其希望能在后臺線程接收
Delegate 回調。為此 AFNetworking 單獨創建了一個線程,并在這個線程中啟動了一個 RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
RunLoop 啟動前內部必須要有至少一個 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創建了一個新的 NSMachPort 添加進去了。通常情況下,調用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個 port 發送消息到 loop 內;但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒有用于實際的發送消息。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
當需要這個后臺線程執行任務時,AFNetworking 通過調用 [NSObject performSelector:onThread:..] 將這個任務扔到了后臺線程的 RunLoop 中。
說一下你對架構的理解? 技術架構如何搭建?
對于iOS架構的認識過程
iOS 從0到1搭建高可用App框架
總結:在MVVM的框架下視圖和模型是不能直接通信的。它們通過
ViewModel來通信,ViewModel通常要實現一個observer觀察者,當數據發
生變化,ViewModel能夠監聽到數據的這種變化,然后通知到對應的視圖做
自動更新,而當用戶操作視圖,ViewModel也能監聽到視圖的變化,然后通
知數據做改動,這實際上就實現了數據的雙向綁定。并且MVVM中的View 和
ViewModel可以互相通信。