1.iOS 類(class)和結構體(struct)有什么區(qū)別?
Swift 中,類是引用類型,結構體是值類型。值類型在傳遞和賦值時將進行復制,而引用類型則只會使用引用對象的一個"指向"。所以他們兩者之間的區(qū)別就是兩個類型的區(qū)別。
舉個簡單的例子,代碼如下
class Temperature {
var value: Float = 37.0
}
class Person {
var temp: Temperature?
func sick() {
temp?.value = 41.0
}
}
let A = Person()
let B = Person()
let temp = Temperature()
A.temp = temp
B.temp = temp
A.sick() 上面這段代碼,由于 Temperature 是 class ,為引用類型,故 A 的 temp 和 B 的 temp指向同一個對象。A 的 temp修改了,B 的 temp 也隨之修改。這樣 A 和 B 的 temp 的值都被改成了41.0。如果將 Temperature 改為 struct,為值類型,則 A 的 temp 修改不影響 B 的 temp。
內存中,引用類型諸如類是在堆(heap)上,而值類型諸如結構體實在棧(stack)上進行存儲和操作。相比于棧上的操作,堆上的操作更加復雜耗時,所以蘋果官方推薦使用結構體,這樣可以提高 App 運行的效率。
class有這幾個功能struct沒有的:
class可以繼承,這樣子類可以使用父類的特性和方法 類型轉換可以在runtime的時候檢查和解釋一個實例的類型 可以用deinit來釋放資源 一個類可以被多次引用 struct也有這樣幾個優(yōu)勢:
結構較小,適用于復制操作,相比于一個class的實例被多次引用更加安全。 無須擔心內存memory leak或者多線程沖突問題
作為一個開發(fā)者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS開發(fā)交流群:130595548,不管你是小白還是大牛都歡迎入駐 ,讓我們一起進步,共同發(fā)展?。ㄈ簝葧赓M提供一些群主收藏的免費學習書籍資料以及整理好的幾百道面試題和答案文檔?。?/strong>
2.iOS自動釋放池是什么,如何工作 ?
當您向一個對象發(fā)送一個autorelease消息時,Cocoa就會將該對象的一個引用放入到最新的自動釋放池。它仍然是個正當的對象,因此自動釋放池定義的作用域內的其它對象可以向它發(fā)送消息。當程序執(zhí)行到作用域結束的位置時,自動釋放池就會被釋放,池中的所有對象也就被釋放。
1.object-c 是通過一種"referring counting"(引用計數)的方式來管理內存的, 對象在開始分配內存(alloc)的時候引用計數為一,以后每當碰到有copy,retain的時候引用計數都會加一, 每當碰到release和autorelease的時候引用計數就會減一,如果此對象的計數變?yōu)榱?, 就會被系統(tǒng)銷毀.
2.NSAutoreleasePool 就是用來做引用計數的管理工作的,這個東西一般不用你管的.
3.autorelease和release沒什么區(qū)別,只是引用計數減一的時機不同而已,autorelease會在對象的使用真正結束的時候才做引用計數減一.
3.iOS你在項目中用過 runtime 嗎?舉個例子
Objective-C 語言是一門動態(tài)語言,編譯器不需要關心接受消息的對象是何種類型,接收消息的對象問題也要在運行時處理。
pragramming 層面的 runtime 主要體現在以下幾個方面:
1.關聯對象 Associated Objects
2.消息發(fā)送 Messaging
3.消息轉發(fā) Message Forwarding
4.方法調配 Method Swizzling
5.“類對象” NSProxy Foundation | Apple Developer Documentation
6.KVC、KVO About Key-Value Coding
4.KVC /KVO的底層原理和使用場景
1 KVC(KeyValueCoding)
1.1 KVC 常用的方法
(1)賦值類方法
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
(2)取值類方法
// 能取得私有成員變量的值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
1.2 KVC 底層實現原理
當一個對象調用setValue:forKey: 方法時,方法內部會做以下操作:
1.判斷有沒有指定key的set方法,如果有set方法,就會調用set方法,給該屬性賦值
2.如果沒有set方法,判斷有沒有跟key值相同且?guī)в邢聞澗€的成員屬性(_key).如果有,直接給該成員屬性進行賦值
3.如果沒有成員屬性_key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進行賦值
4.如果都沒有,就會調用 valueforUndefinedKey 和setValue:forUndefinedKey:方法
1.3 KVC 的使用場景
1.3.1 賦值
(1) KVC 簡單屬性賦值
Person *p = [[Person alloc] init];
// p.name = @"jack";
// p.money = 22.2;
使用setValue: forKey:方法能夠給屬性賦值,等價于直接給屬性賦值
[p setValue:@"rose" forKey:@"name"];
[p setValue:@"22.2" forKey:@"money"];
(2) KVC復雜屬性賦值
//給Person添加 Dog屬性
Person *p = [[Person alloc] init];
p.dog = [[Dog alloc] init];
// p.dog.name = @"阿黃";
1)setValue: forKeyPath: 方法的使用
//修改p.dog 的name 屬性
[p.dog setValue:@"wangcai" forKeyPath:@"name"];
[p setValue:@"阿花" forKeyPath:@"dog.name"];
2)setValue: forKey: 錯誤用法
[p setValue:@"阿花" forKey:@"dog.name"];
NSLog(@"%@", p.dog.name);
3)直接修改私有成員變量
[p setValue:@"旺財" forKeyPath:@"_name"];
(3) 添加私有成員變量
Person 類中添加私有成員變量_age
[p setValue:@"22" forKeyPath:@"_age"];
1.3.2 字典轉模型
(1)簡單的字典轉模型
+(instancetype)videoWithDict:(NSDictionary *)dict
{
JLVideo *videItem = [[JLVideo alloc] init];
//以前
// videItem.name = dict[@"name"];
// videItem.money = [dict[@"money"] doubleValue] ;
//KVC,使用setValuesForKeysWithDictionary:方法,該方法默認根據字典中每個鍵值對,調用setValue:forKey方法
// 缺點:字典中的鍵值對必須與模型中的鍵值對完全對應,否則程序會崩潰
[videItem setValuesForKeysWithDictionary:dict];
return videItem;
}
(2)復雜的字典轉模型
注意:復雜字典轉模型不能直接通過KVC 賦值,KVC只能在簡單字典中使用,比如:
NSDictionary *dict = @{
@"name" : @"jack",
@"money": @"22.2",
@"dog" : @{
@"name" : @"wangcai",
@"money": @"11.1",
}
};
JLPerson *p = [[JLPerson alloc]init]; // p是一個模型對象
[p setValuesForKeysWithDictionary:dict];
內部轉換原理:
// [p setValue:@"jack" forKey:@"name"];
// [p setValue:@"22.2" forKey:@"money"];
// [p setValue:@{
// @"name" : @"wangcai",
// @"money": @"11.1",
//
// } forKey:@"dog"]; //給 dog賦值一個字典肯定是不對的
(3)KVC解析復雜字典的正確步驟
NSDictionary *dict = @{
@"name" : @"jack",
@"money": @"22.2",
@"dog" : @{
@"name" : @"wangcai",
@"price": @"11.1",
},
//人有好多書
@"books" : @[
@{
@"name" : @"5分鐘突破iOS開發(fā)",
@"price" : @"19.8"
},
@{
@"name" : @"3分鐘突破iOS開發(fā)",
@"price" : @"24.8"
},
@{
@"name" : @"1分鐘突破iOS開發(fā)",
@"price" : @"29.8"
}
]
};
XMGPerson *p = [[XMGPerson alloc] init];
p.dog = [[XMGDog alloc] init];
[p.dog setValuesForKeysWithDictionary:dict[@"dog"]];
//保存模型的可變數組
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dict in dict[@"books"]) {
//創(chuàng)建模型
Book *book = [[Book alloc] init];
//KVC
[book setValuesForKeysWithDictionary:dict];
//將模型保存
[arrayM addObject:book];
}
p.books = arrayM;
備注:
(1)當字典中的鍵值對很復雜,不適合用KVC;
(2)服務器返還的數據,你可能不會全用上,如果在模型一個一個寫屬性非常麻煩,所以不建議使用KVC字典轉模型
1.3.3 取值
(1) 模型轉字典
Person *p = [[Person alloc]init];
p.name = @"jack";
p.money = 11.1;
//KVC取值
NSLog(@"%@ %@", [p valueForKey:@"name"], [p valueForKey:@"money"]);
//模型轉字典, 根據數組中的鍵獲取到值,然后放到字典中
NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]];
NSLog(@"%@", dict);
(2) 訪問數組中元素的屬性值
Book *book1 = [[Book alloc] init];
book1.name = @"5分鐘突破iOS開發(fā)";
book1.price = 10.7;
Book *book2 = [[Book alloc] init];
book2.name = @"4分鐘突破iOS開發(fā)";
book2.price = 109.7;
Book *book3 = [[Book alloc] init];
book3.name = @"1分鐘突破iOS開發(fā)";
book3.price = 1580.7;
// 如果valueForKeyPath:方法的調用者是數組,那么就是去訪問數組元素的屬性值
// 取得books數組中所有Book對象的name屬性值,放在一個新的數組中返回
NSArray *books = @[book1, book2, book3];
NSArray *names = [books valueForKeyPath:@"name"];
NSLog(@"%@", names);
//訪問屬性數組中元素的屬性值
Person *p = [[Person alloc]init];
p.books = @[book1, book2, book3];
NSArray *names = [p valueForKeyPath:@"books.name"];
NSLog(@"%@", names);
2 KVO (Key Value Observing)
2.1 KVO 的底層實現原理
(1)KVO 是基于 runtime 機制實現的
(2)當一個對象(假設是person對象,對應的類為 JLperson)的屬性值age發(fā)生改變時,系統(tǒng)會自動生成一個繼承自JLperson的類NSKVONotifying_JLPerson,在這個類的 setAge 方法里面調用
[super setAge:age];
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
三個方法,而后面兩個方法內部會主動調用
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在該方法中可以拿到屬性改變前后的值.
2.2 KVO的作用
作用:能夠監(jiān)聽某個對象屬性值的改變
// 利用KVO監(jiān)聽p對象name 屬性值的改變
Person *p = [[XMGPerson alloc] init];
p.name = @"jack";
/* 對象p添加一個觀察者(監(jiān)聽器)
Observer:觀察者(監(jiān)聽器)
KeyPath:屬性名(需要監(jiān)聽哪個屬性)
*/
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"123"];
/**
* 利用KVO 監(jiān)聽到對象屬性值改變后,就會調用這個方法
*
* @param keyPath 哪一個屬性被改了
* @param object 哪一個對象的屬性被改了
* @param change 改成什么樣了
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
// NSKeyValueChangeNewKey == @"new"
NSString *new = change[NSKeyValueChangeNewKey];
// NSKeyValueChangeOldKey == @"old"
NSString *old = change[NSKeyValueChangeOldKey];
NSLog(@"%@-%@",new,old);
}
5.iOS中持久化方式有哪些?
屬性列表文件 -- NSUserDefaults 的存儲,實際是本地生成一個 plist 文件,將所需屬性存儲在 plist 文件中
對象歸檔 -- 本地創(chuàng)建文件并寫入數據,文件類型不限
SQLite 數據庫 -- 本地創(chuàng)建數據庫文件,進行數據處理
CoreData -- 同數據庫處理思想相同,但實現方式不同
6.什么是KVC和KVO?
KVC(Key-Value-Coding)內部的實現:一個對象在調用setValue的時候
(1)首先根據方法名找到運行方法的時候所需要的環(huán)境參數。
(2)他會從自己isa指針結合環(huán)境參數,找到具體的方法實現的接口。
(3)再直接查找得來的具體的方法實現。KVO(Key-Value- Observing):當觀察者為一個對象的屬性進行了注冊,被觀察對象的isa指針被修改的時候,isa指針就會指向一個中間類,而不是真實的類。所以 isa指針其實不需要指向實例對象真實的類。所以我們的程序最好不要依賴于isa指針。在調用類的方法的時候,最好要明確對象實例的類名
7.iOS中屬性修飾符的作用?
ios5之前是MRC,內存需要程序員進行管理,ios5之后是ARC,除非特殊情況,比如C框架或者循環(huán)引用,其他時候是不需要程序員手動管理內存的。 ios中當我們定義屬性@property的時候就需要屬性修飾符,下面我們就看一下不同屬性修飾符的作用。有錯誤和不足的地方還請大家諒解并批評指正。
主要的屬性修飾符有下面幾種:
- copy
- assign
- retain
- strong
- weak
- readwrite/readonly (讀寫策略、訪問權限)
- nonatomic/atomic (安全策略)
如果以MRC和ARC進行區(qū)分修飾符使用情況,可以按照如下方式進行分組:
1. MRC: assign/ retain/ copy/ readwrite、readonly/ nonatomic、atomic 等。
2. ARC: assign/ strong/ weak/ copy/ readwrite、readonly/ nonatomic、atomic 等。
屬性修飾符對retainCount計數的影響。
- alloc為對象分配內存,retainCount 為1 。
- retain MRC下 retainCount + 1。
- copy 一個對象變成新的對象,retainCount為 1, 原有的對象計數不變。
- release 對象的引用計數 -1。
- autorelease 對象的引用計數 retainCount - 1,如果為0,等到最近一個pool結束時釋放。
不管MRC還是ARC,其實都是看reference count是否為0,如果為0那么該對象就被釋放,不同的地方是MRC需要程序員自己主動去添加retain 和 release,而ARC apple已經給大家做好,自動的在合適的地方插入retain 和 release類似的內存管理代碼,具體原理如下,圖片摘自官方文檔。

MRC 和 ARC原理
下面就詳述上所列的幾種屬性修飾符的使用場景,應用舉例和注意事項。
8.iOS atomatic nonatomic區(qū)別和理解
第一種
atomic和nonatomic區(qū)別用來決定編譯器生成的getter和setter是否為原子操作。atomic提供多線程安全,是描述該變量是否支持多線程的同步訪問,如果選擇了atomic 那么就是說,系統(tǒng)會自動的創(chuàng)建lock鎖,鎖定變量。nonatomic禁止多線程,變量保護,提高性能。
atomic:默認是有該屬性的,這個屬性是為了保證程序在多線程情況下,編譯器會自動生成一些互斥加鎖代碼,避免該變量的讀寫不同步問題。
nonatomic:如果該對象無需考慮多線程的情況,請加入這個屬性,這樣會讓編譯器少生成一些互斥加鎖代碼,可以提高效率。
atomic的意思就是setter/getter這個函數,是一個原語操作。如果有多個線程同時調用setter的話,不會出現某一個線程執(zhí)行完setter全部語句之前,另一個線程開始執(zhí)行setter情況,相當于函數頭尾加了鎖一樣,可以保證數據的完整性。nonatomic不保證setter/getter的原語行,所以你可能會取到不完整的東西。因此,在多線程的環(huán)境下原子操作是非常必要的,否則有可能會引起錯誤的結果。
比如setter函數里面改變兩個成員變量,如果你用nonatomic的話,getter可能會取到只更改了其中一個變量時候的狀態(tài),這樣取到的東西會有問題,就是不完整的。當然如果不需要多線程支持的話,用nonatomic就夠了,因為不涉及到線程鎖的操作,所以它執(zhí)行率相對快些。
下面是載錄的網上一段加了atomic的例子:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
可以看出來,用atomic會在多線程的設值取值時加鎖,中間的執(zhí)行層是處于被保護的一種狀態(tài),atomic是oc使用的一種線程保護技術,基本上來講,就是防止在寫入未完成的時候被另外一個線程讀取,造成數據錯誤。而這種機制是耗費系統(tǒng)資源的,所以在iPhone這種小型設備上,如果沒有使用多線程間的通訊編程,那么nonatomic是一個非常好的選擇。
第二種
atomic和nonatomic用來決定編譯器生成的getter和setter是否為原子操作。
atomic
設置成員變量的@property屬性時,默認為atomic,提供多線程安全。
在多線程環(huán)境下,原子操作是必要的,否則有可能引起錯誤的結果。加了atomic,setter函數會變成下面這樣:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
nonatomic
3禁止多線程,變量保護,提高性能。
3atomic是Objc使用的一種線程保護技術,基本上來講,是防止在寫未完成的時候被另外一個線程讀取,造成數據錯誤。而這種機制是耗費系統(tǒng)資源的,所以在iPhone這種小型設備上,如果沒有使用多線程間的通訊編程,那么nonatomic是一個非常好的選擇。
3指出訪問器不是原子操作,而默認地,訪問器是原子操作。這也就是說,在多線程環(huán)境下,解析的訪問器提供一個對屬性的安全訪問,從獲取器得到的返回值或者通過設置器設置的值可以一次完成,即便是別的線程也正在對其進行訪問。如果你不指定 nonatomic ,在自己管理內存的環(huán)境中,解析的訪問器保留并自動釋放返回的值,如果指定了 nonatomic ,那么訪問器只是簡單地返回這個值。
9.iOS UIViewController的完整生命周期
UIViewController的完整生命周期
-[ViewControllerinitWithNibName:bundle:];
-[ViewControllerinit];
-[ViewControllerloadView];
-[ViewControllerviewDidLoad];
-[ViewControllerviewWillDisappear:];
-[ViewControllerviewWillAppear:];
-[ViewControllerviewDidAppear:];
-[ViewControllerviewDidDisappear:];
1、 alloc 創(chuàng)建對象,分配空間
2、init(initWithNibName) 初始化對象,初始化數據
3、loadView 從nib載入視圖 ,通常這一步不需要去干涉。除非你沒有使用xib文件創(chuàng)建視圖
4、viewDidLoad 載入完成,可以進行自定義數據以及動態(tài)創(chuàng)建其他控件
5、viewWillAppear 視圖將出現在屏幕之前,馬上這個視圖就會被展現在屏幕上了
6、viewDidAppear 視圖已在屏幕上渲染完成
當一個視圖被移除屏幕并且銷毀的時候的執(zhí)行順序,這個順序差不多和上面的相反
1、viewWillDisappear 視圖將被從屏幕上移除之前執(zhí)行
2、viewDidDisappear 視圖已經被從屏幕上移除,用戶看不到這個視圖了
3、dealloc 視圖被銷毀,此處需要對你在init和viewDidLoad中創(chuàng)建的對象進行釋放
ViewController 的 loadView,、viewDidLoad,、viewDidUnload 分別是在什么時候調用的?
viewDidLoad在view從nib文件初始化時調用,loadView在controller的view為nil時調用。
此方法在編程實現view時調用,view控制器默認會注冊memory warning notification,當view controller的任何view沒有用的時候,viewDidUnload會被調用,在這里實現將retain的view release,如果是retain的IBOutlet view屬性則不要在這里release,IBOutlet會負責release。
10.ios7 層協議,tcp四層協議及如何對應的?

11.iOS應用導航模式有哪些?
平鋪模式,一般由scrollView和pageControl組合而成的展示方式。手機自帶的天氣比較典型。
標簽模式,tabBar的展示方式,這個比較常見。
樹狀模式,tableView的多態(tài)展示方式,常見的9宮格、系統(tǒng)自帶的郵箱等展現方式。
12.一個參數既可以是const還可以是volatile嗎?解釋為什么。
? 是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。
13.iOS 響應者鏈的事件傳遞過程?
如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
如果UIApplication也不能處理該事件或消息,則將其丟棄
14.iOS 請說明并比較以下關鍵詞:weak,block
weak與weak基本相同。前者用于修飾變量(variable),后者用于修飾屬性(property)。weak 主要用于防止block中的循環(huán)引用。 block也用于修飾變量。它是引用修飾,所以其修飾的值是動態(tài)變化的,即可以被重新賦值的。block用于修飾某些block內部將要修改的外部變量。 weak和block的使用場景幾乎與block息息相關。而所謂block,就是Objective-C對于閉包的實現。閉包就是沒有名字的函數,或者理解為指向函數的指針。
15.iOS UIView的Touch事件注意點
如果父控件不能接收觸摸事件,那么子控件就不可能接收到觸摸事件(掌握)
UIView不接收觸摸事件的三種情況:
不接收用戶交互 : userInteractionEnabled = NO
隱藏 : hidden = YES
透明 : alpha = 0.0 ~ 0.01
UIImageView的userInteractionEnabled默認就是NO,因此UIImageView以及它的子控件默認是不能接收觸摸事件的
16.iOS 說明并比較關鍵詞:strong, weak, assign, copy等等
strong表示指向并擁有該對象。其修飾的對象引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。當然強行將其設為nil可以銷毀它。
weak表示指向但不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。
assign主要用于修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。
weak 一般用來修飾對象,assign一般用來修飾基本數據類型。原因是assign修飾的對象被釋放后,指針的地址依然存在,造成野指針,在堆上容易造成崩潰。而棧上的內存系統(tǒng)會自動處理,不會造成野指針。
copy與strong類似。不同之處是strong的復制是多個指針指向同一個地址,而copy的復制每次會在內存中拷貝一份對象,指針指向不同地址。copy一般用在修飾有可變對應類型的不可變對象上,如NSString, NSArray, NSDictionary。
Objective-C 中,基本數據類型的默認關鍵字是atomic, readwrite, assign;普通屬性的默認關鍵字是atomic, readwrite, strong。
1、屬性readwrite,readonly,assign,retain,copy,nonatomic 各自什么作用,他們在那種情況下用?
readwrite:默認的屬性,可讀可寫,生成setter和getter方法。
readonly:只讀,只生成getter方法,也就是說不能修改變量。
assign:用于聲明基本數據類型(int、float)僅設置變量,是賦值屬性。
retain:持有屬性,setter方法將傳入的參數先保留,再賦值,傳入的參數 引用計數retaincount 會加1
在堆上開辟一塊空間,用指針a指向,然后將指針a賦值(assign)給指針b,等于是a和b同時指向這塊堆空間,當a不使用這塊堆空間的時候,是否要釋放這塊堆空間?答案是肯定要的,但是這件堆空間被釋放后,b就成了野指針。
如何避免這樣的問題? 這就引出了引用計數器,當a指針這塊堆空間的時候,引用計數器+1,當b也指向的時候,引用計數器變成了2,當a不再指向這塊堆空間時,release-1,引用計數器為1,當b也不指向這塊堆空間時,release-1,引用計數器為0,調用dealloc函數,空間被釋放
總結:當數據類型為int,float原生類型時,可以使用assign。如果是上面那種情況(對象)就是用retain。
copy:是賦值特性,setter方法將傳入對象賦值一份;需要完全一份新的變量時,直接從堆區(qū)拿。
當屬性是 NSString、NSArray、NSDictionary時,既可以用strong 修飾,也可以用copy修飾。當用strong修飾的NSString 指向一個NSMutableString時,如果在不知情的情況下這個NSMutableString的別的引用修改了值,就會出現:一個不可變的字符串卻被改變了的情況, 使用copy就不會出現這種情況。
nonatomic:非原子性,可以多線程訪問,效率高。
atomic:原子性,屬性安全級別的表示,同一時刻只有一個線程訪問,具有資源的獨占性,但是效率很低。
strong:強引用,引用計數+ 1,ARC下,一個對象如果沒有強引用,系統(tǒng)就會釋放這個對象。
weak:弱引用,不會使引用計數+1.當一個指向對象的強引用都被釋放時,這塊空間依舊會被釋放掉。
使用場景:在ARC下,如果使用XIB 或者SB 來創(chuàng)建控件,就使用 weak。純代碼創(chuàng)建控件時,用strong修飾,如果想用weak 修飾,就需要先創(chuàng)建控件,然后賦值給用weak修飾的對象。
查找了一些資料,發(fā)現主要原因是,controller需要擁有它自己的view(這個view是所以子控件的父view),因此viewcontroller對view就必須是強引用(strong reference),得用strong修飾view。對于lable,它的父view是view,view需要擁有l(wèi)abel,但是controller是不需要擁有l(wèi)abel的。如果用strong修飾,在view銷毀的情況下,label還仍然占有內存,因為controller還對它強引用;如果用wak修飾,在view銷毀的時label的內存也同時被銷毀,避免了僵尸指針出現。
用引用計數回答就是:因為Controller并不直接“擁有”控件,控件由它的父view“擁有”。使用weak關鍵字可以不增加控件引用計數,確??丶c父view有相同的生命周期??丶诒籥ddSubview后,相當于控件引用計數+1;父view銷毀后,所有的子view引用計數-1,則可以確保父view銷毀時子view立即銷毀。weak的控件在removeFromSuperview后也會立即銷毀,而strong的控件不會,因為Controller還保有控件強引用。
總結歸納為:當控件的父view銷毀時,如果你還想繼續(xù)擁有這個控件,就用srtong;如果想保證控件和父view擁有相同的生命周期,就用weak。當然在大多數情況下用兩個都是可以的。
使用weak的時候需要特別注意的是:先將控件添加到superview上之后再賦值給self,避免控件被過早釋放。
17.iOS里什么是響應鏈,它是怎么工作的?
第一反應就是,響應鏈就是響應鏈啊,由一串UIResponder對象鏈接,收到響應事件時由上往下傳遞,直到能響應事件為止。
但其中卻大有文章...
1.由一串UIResponder對象鏈接 ?
我們知道UIResponder類里有個屬性:
@property(nonatomic, readonly, nullable) UIResponder *nextResponder;
如果我們對響應鏈原理不清楚的話,會很容易的認為,這條鏈是由 nextResponder 指針連接起來的,在尋找響應者的時候是順著這個指針找下去直到找到響應者為止的,但這是錯誤的認為。 舉個例子: 現在我們有這樣一個場景:

E0B0C758-8B7D-4B6A-85F3-72F3A174DCEA.png
AppDelegate上的Window上有一個UIViewController *ViewController, 然后在ViewController.view 上按順序添加viewA和viewB,viewB稍微覆蓋viewA一部分用來測試, 給viewA,viewB 分別添加點擊手勢tapA 和 tapB,然后把viewB.userInteractionEnabled = NO,讓viewB不能響應點擊。
然后我們點擊重復的那塊區(qū)域,會發(fā)現viewA響應了tap手勢,執(zhí)行了tapA的事件。 我們知道viewB設置了viewB.userInteractionEnabled = NO,不響應tap手勢是正常的,但怎么會透過viewB,viewA響應了手勢?
我們知道nextResponder指針指向的規(guī)則:
- UIView
- 如果 view 是一個 view controller 的 root view,nextResponder 是這個 view controller.
- 如果 view 不是 view controller 的 root view,nextResponder 則是這個 view 的 superview
- UIViewController
- 如果 view controller 的 view 是 window 的 root view, view controller 的 nextResponder 是這個 window
- 如果 view controller 是被其他 view controller presented調起來的,那么 view controller 的 nextResponder 就是發(fā)起調起的那個 view controller
- UIWindow
- window 的 nextResponder 是 UIApplication 對象.
- UIApplication
- UIApplication 對象的 nextResponder 是 app delegate, 但是 app delegate 必須是 UIResponder 對象,并且不能使 view ,view controller 或 UIApplication 對象他本身.
那么上述情況下,viewB所在的響應者鏈應該是: viewB -> ViewController.view -> ViewController -> Window -> Application 這種情況下怎么也輪不到viewA去響應啊。
所以,當有事件需要響應時,nextResponder 并不是鏈接響應鏈的那根繩子,響應鏈的工作方式另有別的方式
2. 那么響應鏈是如何工作,正確找到應該響應該事件的響應者的?
UIKit使用基于視圖的hit-testing來確定touch事件發(fā)生的位置。具體解釋就是,UIKit將touch的位置和視圖層級中的view的邊界進行了比較,UIView的方法 hitTest:withEvent: 在視圖層級中進行,尋找包含指定touch的最深子視圖。這個視圖成為touch事件的第一個響應者。
說白了就是,當有touch事件來的時候,會從最下面的視圖開始執(zhí)行 hitTest:withEvent: ,如果符合成為響應者的條件,就會繼續(xù)遍歷它的 subviews 繼續(xù)執(zhí)行 hitTest:withEvent: ,直到找到最合適的view成為響應者。
這里要注意幾個點:
- 符合響應者的條件包括
- touch事件的位置在響應者區(qū)域內
- 響應者 hidden 屬性不為 YES
- 響應者 透明度 不是 0
- 響應者 userInteractionEnabled 不為 NO
- 遍歷 subview 時,是從上往下順序遍歷的,即 view.subviews 的 lastObject 到 firstObject 的順序,找到合適的響應者view,即停止遍歷.
所以再回看上面的例子,當我們點擊中間的重復區(qū)域時,流程其實是這樣:
- AppDelegate 的 window 收到事件,并開始執(zhí)行 hitTest:withEvent: ,發(fā)現符合要求,開始遍歷子view.
- window 上只有 viewcontroller.view ,所以viewcontroller.view 開始執(zhí)行 hitTest:withEvent: ,發(fā)現符合要求,開始遍歷子view.
- viewcontroller.view 有兩個子view, viewA 和 viewB ,但是viewB 在 viewA 上邊,所以先 viewB 執(zhí)行 hitTest:withEvent: ,結果發(fā)現viewB 不符合要求,因為viewB 的 userInteractionEnabled 為 NO.
- 接下來 viewA 執(zhí)行 hitTest:withEvent: ,發(fā)現符合條件,并且viewA 也沒有子view可去遍歷,于是返回viewA.
- viewA成了最終事件的響應者.
這樣就完美解釋了,最開始例子的響應狀況.
那么如果 viewB 的 userInteractionEnabled 屬性為YES的話,是怎么樣的呢?
如果 viewB 的 userInteractionEnabled 屬性為YES,上面流程的第三部就會發(fā)現viewB是符合要求的,而直接返回viewB作為最終響應者,中斷子view的遍歷,viewA都不會被遍歷到了.
這就是響應鏈相關的點,如果有什么不對的請留言提示,然后有什么別的需要補充的我會及時補充~
18.什么是iOS的動態(tài)綁定 ?
—在運行時確定要調用的方法
動態(tài)綁定將調用方法的確定也推遲到運行時。在編譯時,方法的調用并不和代碼綁定在一起,只有在消實發(fā)送出來之后,才確定被調用的代碼。通過動態(tài)類型和動態(tài)綁定技術,您的代碼每次執(zhí)行都可以得到不同的結果。運行時因子負責確定消息的接收者和被調用的方法。運行時的消息分發(fā)機制為動態(tài)綁定提供支持。當您向一個動態(tài)類型確定了的對象發(fā)送消息時,運行環(huán)境系統(tǒng)會通過接收者的isa指針定位對象的類,并以此為起點確定被調用的方法,方法和消息是動態(tài)綁定的。而且,您不必在Objective-C 代碼中做任何工作,就可以自動獲取動態(tài)綁定的好處。您在每次發(fā)送消息時,
特別是當消息的接收者是動態(tài)類型已經確定的對象時,動態(tài)綁定就會例行而透明地發(fā)生。
19.iOS單元測試框架有哪些?
OCUnit 是 OC 官方測試框架, 現在被 XCTest 所取代。 XCTest 是與 Foundation 框架平行的測試框架。 GHUnit 是第三方的測試框架。github地址OCMock都是第三方的測試框架。
20.iOS ARC全解?
考查點
我記得在剛接觸iOS的時候對這個ARC和MRC就討論頗深,認為ARC是對程序員的一種福利,讓我們節(jié)省了大量的代碼,那么ARC是什么呢?
ARC 是蘋果在 WWDC 2011 提出來的技術,因此很多新入行的同學可能對此技術細節(jié)并不熟悉。但是,雖然 ARC 極大地簡化了我們的內存管理工作,但是引用計數這種內存管理方案如果不被理解,那么就無法處理好那些棘手的循環(huán)引用問題。所以,這道面試題其實是考查同學對于 iOS 程序內存管理的理解深度。 答案
自動的引用計數(Automatic Reference Count 簡稱 ARC),是蘋果在 WWDC 2011 年大會上提出的用于內存管理的技術。
引用計數(Reference Count)是一個簡單而有效的管理對象生命周期的方式。當我們創(chuàng)建一個新對象的時候,它的引用計數為 1,當有一個新的指針指向這個對象時,我們將其引用計數加 1,當某個指針不再指向這個對象是,我們將其引用計數減 1,當對象的引用計數變?yōu)?0 時,說明這個對象不再被任何指針指向了,這個時候我們就可以將對象銷毀,回收內存。由于引用計數簡單有效,除了 Objective-C 語言外,微軟的 COM(Component Object Model )、C++11(C++11 提供了基于引用計數的智能指針 share_prt) 等語言也提供了基于引用計數的內存管理方式。
引用計數這種內存管理方式雖然簡單,但是手工寫大量的操作引用計數的代碼不但繁瑣,而且容易被遺漏。于是蘋果在 2011 年引入了 ARC。ARC 顧名思義,是自動幫我們填寫引用計數代碼的一項功能。
ARC 的想法來源于蘋果在早期設計 Xcode 的 Analyzer 的時候,發(fā)現編譯器在編譯時可以幫助大家發(fā)現很多內存管理中的問題。后來蘋果就想,能不能干脆編譯器在編譯的時候,把內存管理的代碼都自動補上,帶著這種想法,蘋果修改了一些內存管理代碼的書寫方式(例如引入了 @autoreleasepool 關鍵字)后,在 Xcode 中實現了這個想法。
ARC 的工作原理大致是這樣:當我們編譯源碼的時候,編譯器會分析源碼中每個對象的生命周期,然后基于這些對象的生命周期,來添加相應的引用計數操作代碼。所以,ARC 是工作在編譯期的一種技術方案,這樣的好處是:
編譯之后,ARC 與非 ARC 代碼是沒有什么差別的,所以二者可以在源碼中共存。實際上,你可以通過編譯參數 -fno-objc-arc 來關閉部分源代碼的 ARC 特性。
相對于垃圾回收這類內存管理方案,ARC 不會帶來運行時的額外開銷,所以對于應用的運行效率不會有影響。相反,由于 ARC 能夠深度分析每一個對象的生命周期,它能夠做到比人工管理引用計數更加高效。例如在一個函數中,對一個對象剛開始有一個引用計數 +1 的操作,之后又緊接著有一個 -1 的操作,那么編譯器就可以把這兩個操作都優(yōu)化掉。
但是也有人認為,ARC 也附帶有運行期的一些機制來使 ARC 能夠更好的工作,他們主要是指 weak 關鍵字。weak 變量能夠在引用計數為 0 時被自動設置成 nil,顯然是有運行時邏輯在工作的。我通常并沒有把這個算在 ARC 的概念當中,當然,這更多是一個概念或定義上的分歧,因為除開 weak 邏輯之外,ARC 核心的代碼都是在編譯期填充的。
21.iOS內存的使用和優(yōu)化的注意事項
重用問題:
如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews
設置正確的reuseIdentifier,充分重用;
盡量把views設置為不透明:
當opque為NO的時候,圖層的半透明取決于圖片和其本身合成的圖層為結果,可提高性能;
不要使用太復雜的XIB/Storyboard:
載入時就會將XIB/storyboard需要的所有資源,
包括圖片全部載入內存,即使未來很久才會使用。
那些相比純代碼寫的延遲加載,性能及內存就差了很多;
選擇正確的數據結構:
學會選擇對業(yè)務場景最合適的數組結構是寫出高效代碼的基礎。
比如,數組: 有序的一組值。
使用索引來查詢很快,使用值查詢很慢,插入/刪除很慢。
字典: 存儲鍵值對,用鍵來查找比較快。
集合: 無序的一組值,用值來查找很快,插入/刪除很快。
gzip/zip壓縮:
當從服務端下載相關附件時,可以通過gzip/zip壓縮后再下載,使得內存更小,下載速度也更快。
延遲加載:
對于不應該使用的數據,使用延遲加載方式。
對于不需要馬上顯示的視圖,使用延遲加載方式。
比如,網絡請求失敗時顯示的提示界面,可能一直都不會使用到,因此應該使用延遲加載。
數據緩存:
對于cell的行高要緩存起來,使得reload數據時,效率也極高。
而對于那些網絡數據,不需要每次都請求的,應該緩存起來,
可以寫入數據庫,也可以通過plist文件存儲。
處理內存警告:
一般在基類統(tǒng)一處理內存警告,將相關不用資源立即釋放掉
重用大開銷對象:
一些objects的初始化很慢,
比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它們。
通常是作為屬性存儲起來,防止反復創(chuàng)建。
避免反復處理數據:
許多應用需要從服務器加載功能所需的常為JSON或者XML格式的數據。
在服務器端和客戶端使用相同的數據結構很重要;
使用Autorelease Pool:
在某些循環(huán)創(chuàng)建臨時變量處理數據時,自動釋放池以保證能及時釋放內存;
22.什么是iOS的目標-動作機制 ?
目標是動作消息的接收者。一個控件,或者更為常見的是它的單元,以插座變量(參見"插座變量"部分)
的形式保有其動作消息的目標。
動作是控件發(fā)送給目標的消息,或者從目標的角度看,它是目標為了響應動作而實現的方法。
程序需要某些機制來進行事件和指令的翻譯。這個機制就是目標-動作機制。
23.iOS 事件傳遞的完整過程?
先將事件對象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個事件。
調用最合適控件的touches….方法
如果調用了[super touches….];就會將事件順著響應者鏈條往上傳遞,傳遞給上一個響應者
接著就會調用上一個響應者的touches….方法
如何判斷上一個響應者:
如果當前這個view是控制器的view,那么控制器就是上一個響應者
如果當前這個view不是控制器的view,那么父控件就是上一個響應者
24.什么是iOS的響應者鏈?
- 響應者鏈條:是由多個響應者對象連接起來的鏈條
- 作用:能很清楚的看見每個響應者之間的聯系,并且可以讓一個事件多個對象處理。
- 響應者對象:能處理事件的對象

25.iOS UIView的Touch事件有哪幾種觸摸事件?
處理事件的方法
UIView是UIResponder的子類,可以覆蓋下列4個方法處理不同的觸摸事件
//一根或者多根手指開始觸摸view
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//一根或者多根手指在view上移動
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
//一根或者多根手指離開view
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
//觸摸結束前,某個系統(tǒng)事件(例如電話呼入)會打斷觸摸過程
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
26.iOS開發(fā):Objective-C中通知與協議的區(qū)別?
what is difference between NSNotification and protocol? (通知和協議的不同之處?)
我想大家都知道這個東西怎么用,但是更深層次的思考可能就比較少了吧,眾所周知就是代理是一對一的,但是通知是可以多對多的.但是為什么是這個樣子,有沒有更深的思考過這個問題?
今天看了下網上的幾個視頻教程,KVO、KVC、謂詞、通知,算是開發(fā)中的高級點的東西了。通知和協議都是類似于回調一樣,于是就在思考通知和協議到底有什么不同,或者說什么時候該用通知,什么時候該用協議。
下面是網上摘抄的一段解釋:
協議有控制鏈(has-a)的關系,通知沒有。首先我一開始也不太明白,什么叫控制鏈(專業(yè)術語了~)。但是簡單分析下通知和代理的行為模式,我們大致可以有自己的理解簡單來說,通知的話,它可以一對多,一條消息可以發(fā)送給多個消息接受者。代理按我們的理解,到不是直接說不能一對多,比如我們知道的明星經濟代理人,很多時候一個經濟人負責好幾個明星的事務。只是對于不同明星間,代理的事物對象都是不一樣的,一一對應,不可能說明天要處理A明星要一個發(fā)布會,代理人發(fā)出處理發(fā)布會的消息后,別稱B的發(fā)布會了。但是通知就不一樣,他只關心發(fā)出通知,而不關心多少接收到感興趣要處理。因此控制鏈(has-a從英語單詞大致可以看出,單一擁有和可控制的對應關系。
1.通知:
通知需要有一個通知中心:NSNotificationCenter,自定義通知的話需要給一個名字,然后監(jiān)聽。
優(yōu)點:通知的發(fā)送者和接受者都不需要知道對方??梢灾付ń邮胀ㄖ木唧w方法。通知名可以是任何字符串。
缺點:較鍵值觀察(KVO)需要多點代碼,在刪掉前必須移除監(jiān)聽者。
2.協議
通過setDelegate來設置代理對象,最典型的例子是常用的TableView.
優(yōu)點:支持它的類有詳盡和具體信息。
缺點:該類必須支持委托。某一時間只能有一個委托連接到某一對象。
相信看到這些東西,認真思考一下,就可以知道在那種情況下使用通知,在那種情況下使用代理了吧.
27.寫一個NSString類的實現
+ (id)initWithCString:(c*****t char *)nullTerminatedCString encoding:(NSStringEncoding)encoding;**
+ (id) stringWithCString: (c*****t char*)nullTerminatedCString
encoding: (NSStringEncoding)encoding
{
NSString *obj;
obj = [self allocWithZone: NSDefaultMallocZone()];
obj = [obj initWithCString: nullTerminatedCString encoding: encoding];
return AUTORELEASE(obj);
}
28.iOS 事件的產生和傳遞流程
發(fā)生觸摸事件后,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中
UIApplication會從事件隊列中取出最前面的事件,并將事件分發(fā)下去以便處理,通常,先發(fā)送事件給應用程序的主窗口(keyWindow)
主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步
找到合適的視圖控件后,就會調用視圖控件的touches方法來作具體的事件處理 touchesBegan… touchesMoved… touchedEnded…
這些touches方法的默認做法是將事件順著響應者鏈條向上傳遞(不實現touches方法,系統(tǒng)會自動向上一個響應者傳遞),將事件交給上一個響應者進行處理
如果一個事件既想自己處理也想交給上一個響應者處理,那么自己實現touches方法,并且調用super的touches方法,[super touches、、、];
29.關鍵字volatile有什么含意?并給出三個不同的例子?
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。精確地說就是,優(yōu)化器在用到
這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:
? 并行設備的硬件寄存器(如:狀態(tài)寄存器)
? 一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
? 多線程應用中被幾個任務共享的變量
30.iOS hitTest方法&pointInside方法
hitTest方法
當事件傳遞給控件的時候,就會調用控件的這個方法,去尋找最合適的view
point:當前的觸摸點,point這個點的坐標系就是方法調用者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
pointInside方法
作用:判斷當前這個點在不在方法調用者(控件)上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
hitTest:withEvent:的實現原理
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.判斷當前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2. 判斷點在不在當前控件
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.從后往前遍歷自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[i];
// 把當前控件上的坐標系轉換成子控件上的坐標系
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) { // 尋找到最合適的view
return fitView;
}
}
// 循環(huán)結束,表示沒有比自己更合適的view
return self;
}