眾所周知,由于種種原因,導致今年移動開發(fā)的工作比較難找,本菜鳥也深感找工作的不易,所以總結(jié)了些面試題,希望可以幫助到哪些迷茫的人,愿大家都找到好工作
01:MRC/ARC的內(nèi)存管理機制?
OC的內(nèi)存管理機制分為兩種:一種為MRC手動引用計數(shù),一種為ARC自動引用計數(shù),從iOS5開始都是用ARC開發(fā)。
原理:
在MRC中當使用new,alloc和copy方法創(chuàng)建一個對象時,該對象的引用計數(shù)器的值為1,當不再使用該對象時, 需要手動 向?qū)ο蟀l(fā)送一條release或autorelease消息,這樣對象的引用計數(shù)器會 -1,當retaincount的值為0時,該對象就會被銷毀,從而釋放內(nèi)存。
在ARC中通過是否有強指針指向?qū)ο螅糁辽儆幸粋€強指針指向?qū)ο螅瑒t不銷毀對象,當沒有強指針指向?qū)ο髸r,對象被銷毀,其本質(zhì)是編譯器在編譯的時候,插入了類似內(nèi)存管理的代碼。
缺點:
MRC:
1.模塊化操作的時候,對象可能被多個模塊創(chuàng)建和使用,不能確定最后由誰去釋放;
2.多線程操作的時候,不確定哪個線程最后使用完畢,總而言之,MRC內(nèi)存泄露和壞內(nèi)存的問題。
ARC:
1 循環(huán)引用的時候會導致內(nèi)存泄露,(我們常見的delegate往往是assign方式的屬性而不是retain方式 的屬性;
2 Objective-C對象與CoreFoundation對象進行橋接的時候如果管理不當也會造成內(nèi)存泄露;
3 CoreFoundation中的對象不受ARC管理,需要開發(fā)者手動釋放
02:MVC、單例、觀察者、代理等設計模式?
設計模式:并不是一種新技術(shù),而是一種編碼經(jīng)驗,使用比如:iOS中的協(xié)議,繼承關(guān)系等基本手段,用比較成熟的邏輯去處理某一種類型的事情,總結(jié)為所謂設計模式。
MVC:
M:
數(shù)據(jù)管理者--模型,給ViewController提供數(shù)據(jù);給ViewController存儲數(shù)據(jù)提供接口;提供經(jīng)過抽象的業(yè)務基本組件,供Controller調(diào)度
V:
數(shù)據(jù)展示者--View,界面控件的展示;響應與業(yè)務無關(guān)的事件,加載動畫
C:
數(shù)據(jù)加工者--控制器,管理ViewController的生命周期;負責生成所有的View實例,并放入ViewController中;監(jiān)聽來自View與業(yè)務有關(guān)的事件,通過與模型的合作,完成業(yè)務邏輯處理
單例模式:
說白了就是一個類不通過alloc方式創(chuàng)建對象,而是用一個靜態(tài)方法返回這個類的對象。系統(tǒng)只需要擁有一個的全局對象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為,比如想獲得[UIApplication sharedApplication];任何地方調(diào)用都可以得到 UIApplication的對象,這個對象是全局唯一的。
觀察者模式:
當一個物體發(fā)生變化時,會通知所有觀察這個物體的觀察者讓其做出反應。實現(xiàn)起來無非就是把所有觀察者的對象給這個物體,當這個物體的發(fā)生改變,就會調(diào)用遍歷所有觀察者的對象調(diào)用觀察者的方法從而達到通知觀察者的目的
代理模式:
代理模式給某一個對象提供一個代理對象,并由代理對象控制對源對象的引用.比如一個工廠生產(chǎn)了產(chǎn)品,并不想直接賣給用戶,而是搞了很多代理商,用戶可以直接找代理商買東西,代理商從工廠進貨.常見的如QQ的自動回復就屬于代理攔截,代理模式在iphone中得到廣泛應用.
03:KVO,delegate,block,NSNotification等消息通知機制?
KVO:
KVO,即:Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改后,則對象就會接受到通知。簡單的說就是每次指定的被觀察的對象的屬性被修改后,KVO就會自動通知相應的觀察者了。
KVO的使用步驟:
1:添加監(jiān)聽者
2:移除監(jiān)聽者
3:屬性改變時,通知監(jiān)聽者
KVO的底層實現(xiàn):
KVO是基與runtime機制實現(xiàn)的,當某個類的對象第一次被觀察時,系統(tǒng)就會在運行期動態(tài)的創(chuàng)建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter方法,派生類在重寫的setter方法中實現(xiàn)真正的通知機制
delegate:
代理的使用場景:
1:當A對象想監(jiān)聽B對象的變化時,就可以使用代理,讓A成為B的代理
2:當B對象想通知A對象的時候,就可以使用代理,讓A成為B的代理
代理的使用步驟:
1:遵守代理協(xié)議
2:實現(xiàn)代理方法
3:將遵守了協(xié)議的對象設置為代理
代理的注意點:
代理的屬性一般為id,屬性一般為weak,避免循環(huán)引用
手寫代理:
1:聲明嬰兒代理
#import <Foundation/Foundation.h>
@class Baby;
@protocol BabyProtocol <NSObject>
// 喂嬰兒吃東西
- (void)feedFood:(Baby *)baby;
// 哄嬰兒睡覺
- (void)hongBaby:(Baby *)baby;
@end
2:讓保姆成為嬰兒的代理
// 如果使用id類型來接收保姆, 如果將來換保姆了, 嬰兒類不用修改代碼
@property (nonatomic, strong) id<BabyProtocol> nanny
3:保姆遵守協(xié)議,并實現(xiàn)協(xié)議方法
#import <Foundation/Foundation.h>
#import "BabyProtocol.h"
@class Baby;
@interface Nanny : NSObject <BabyProtocol>
#import "Nanny.h"
#import "Baby.h"
@implementation Nanny
- (void)feedFood:(Baby *)baby
{
baby.hungry -= 10;
NSLog(@"喂嬰兒吃東西 %i", baby.hungry);
}
- (void)hongBaby:(Baby *)baby
{
baby.sleep += 10;
NSLog(@"哄嬰兒睡覺 %i", baby.sleep);
}
@end
block:
block在使用過程中容易產(chǎn)生循環(huán)引用
1.默認情況下, block是放在棧里面的
2.一旦block進行了copy操作, block的內(nèi)存就會放在堆里面
3.堆里面的block(被copy過的block)有以下現(xiàn)象
1> block內(nèi)部如果通過外面聲明的強引用來使用某個對象, 那么block內(nèi)部會自動額外產(chǎn)生一個強引用指向所使用的對象
2> block內(nèi)部如果通過外面聲明的弱引用來使用某個對象, 那么block內(nèi)部會自動額外產(chǎn)生一個弱引用指向所使用的對象
在一個類中調(diào)用另一個類的方法可以用通知來實現(xiàn),
通知的使用步驟:
1.監(jiān)聽通知:
[[NSNotificationCenter defaultCenter] addObserver:p1 selector:@selector(gotNews:) name:@"軍事新聞" object:cmp2]; // 0
[[NSNotificationCenter defaultCenter] addObserver:p2 selector:@selector(gotNews:) name:nil object:nil];
2.創(chuàng)建及發(fā)布通知:
[[NSNotificationCenter defaultCenter] postNotificationName:@"軍事新聞" object:nil userInfo:@{@"title" : @"453543"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"娛樂新聞" object:cmp2 userInfo:@{@"title" : @"453543"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"娛樂新聞" object:cmp1 userInfo:@{@"title" : @"453543"}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"娛樂新聞" object:nil userInfo:@{@"title" : @"453543"}];
3.移除通知:
[[NSNotificationCenter defaultCenter] removeObserver:p1];
[[NSNotificationCenter defaultCenter] removeObserver:p2];
如何選擇合適的消息通知機制?
通知比較靈活(1個通知能被多個對象接收, 1個對象能接收多個通知),
代理比較規(guī)范,但是代碼多(默認是1對1)
KVO性能不好(底層會動態(tài)產(chǎn)生新的類),只能監(jiān)聽某個對象屬性的改變, 不推薦使用(1個對象的屬性能被多個對象監(jiān)聽, 1個對象能監(jiān)聽多個對象的其他屬性)
通知(NSNotificationCenter\NSNotification)
任何對象之間都可以傳遞消息
使用范圍
1個對象可以發(fā)通知給N個對象
1個對象可以接受N個對象發(fā)出的通知
必須得保證通知的名字在發(fā)出和監(jiān)聽時是一致的
KVO
僅僅是能監(jiān)聽對象屬性的改變(靈活度不如通知和代理)
代理
使用范圍
1個對象只能設置一個代理(假設這個對象只有1個代理屬性)
1個對象能成為多個對象的代理
比通知規(guī)范
建議使用代理多于通知
04:GCD,NSOperation多線程?
略
05:屏幕適配?
通過判斷版本來控制,來執(zhí)行響應的代碼
功能適配:保證同一個功能在不同的系統(tǒng)版本上都能用
UI適配:保證各自的顯示風格
06:runtime?
runtime是一套比較底層的純C語言的API, 屬于一個C語言庫,里面包含許多API, 簡稱運行時, 運行時中最重要的是消息機制。
對于C語言來說,函數(shù)的調(diào)用會在編譯的時候就決定調(diào)用哪個函數(shù),而對于OC來說,在編譯的時候并不能決定調(diào)用哪個函數(shù),只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用
runtime有啥用?
1> 能動態(tài)產(chǎn)生一個類、一個成員變量、一個方法 (KVO,,class_addMethod)
2> 能動態(tài)修改一個類、一個成員變量、一個方法
3> 能動態(tài)刪除一個類、一個成員變量、一個方法
消息機制:
import <objc/message.h> : 消息機制
方法調(diào)用的本質(zhì)就是讓對象發(fā)送消息,只有對象才能發(fā)送消息,使用消息機制必須導入#import<objc/message.h>
消息機制原理:對象根據(jù)方法編號SEL去映射表查找對應的方法實現(xiàn)
常見的函數(shù)和應用:
import <objc/runtime.h> : 成員變量、類、方法
Ivar * class_copyIvarList : 獲得某個類內(nèi)部的所有成員變量 (占位符顏色,MJ,歸檔)
Method * class_copyMethodList : 獲得某個類內(nèi)部的所有方法 ()
Method class_getInstanceMethod : 獲得某個實例方法(對象方法,減號-開頭)
Method class_getClassMethod : 獲得某個類方法(加號+開頭)
動態(tài)的添加方法:
當一個類方法非常多,加載類到內(nèi)存的時候會比較消耗資源,此時可以使用運行時給某個類動態(tài)的添加方法,即:performSelector
class_addMethod
攪拌池:
method_exchangeImplementations : 交換2個方法的具體實現(xiàn) (image圖片)
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址,相當于交換實現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
07:KVC字典轉(zhuǎn)模型,模型中的屬性和字典中的Key不一致如何處理?
KVC字典轉(zhuǎn)模型弊端:必須保證,模型中的屬性和字典中的key一一對應。
如果不一致,就會調(diào)用[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
報key找不到的錯。
分析:模型中的屬性和字典的key不一一對應,系統(tǒng)就會調(diào)用setValue:forUndefinedKey:報錯。
解決:重寫對象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋,
就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
08:SDWebImage原理?
略
09:svn和git常見操作?
**SVN: **
svn status 查看當前工作空間內(nèi),所有有變化的文件的狀態(tài)
svn log 查看當前版本的操作日志
個人開發(fā):
1: 初始化項目:svn import path URL - username=用戶民 - password=密碼 - m“注釋”
svn checkout 初始化本地倉庫
2:從服務器下載代碼 svn checkout URL -username -password
3:修改文件提交 svn commit -m“注釋”
4:新建文件提交 svn add filename svn commit -m“注釋”
5:刪除文件提交 一定不要直接手動刪除文件,先從本地代碼倉庫管理中移除,然后再刪除 svn remove filename / svn delete filename
多人開發(fā):
超時:當本地文件的版本號小于服務器文件版本號時,如果要提交本地文件,就會報out of date超時錯誤,原因是svn通過版本號管理每一個文件,如果一個文件被修改并提交到服務器,那么服務器上的版本號就會加1,如果你的版本號小于服務器的版本號,就說明,有人修改了那個文件
超時解決:更新,從服務器下載更新最新版本,保證本地與服務器保持版本號一致
沖突:如果幾個人同時修改了同一個文件的同一行代碼,那么就會產(chǎn)生沖突,此時版本控制器不會智能的識別使用那個版本,只能交給用戶處理,此時會有三個選擇,1:用我的;2:用服務器的;3:延遲處理
版本回退:
修改了未提交:
svn revert
修改了,并且提交了:
a:
svn update
svn merge 文件名 -r 版本1:版本2
svn commit - m
b:
svn update - r 指定版本號 (本地代碼已經(jīng)變化,服務器的沒有變)
svn update (沖突,選擇使用自己的代碼即可)
Git:
svn checkout —— git clone
svn 只下載代碼, git,會連同代碼倉庫一起下載下來
svn commit —— git commit
svn 是提交到服務器,git 中是提交到本地倉庫,需要使用push才能提交到服務器
svn update - git pull
svn update 從服務器下載最新被修改的代碼,
工作區(qū):與.git文件夾同級的其他文件夾或者子文件夾
暫緩區(qū):
個人開發(fā):
創(chuàng)建文件并提交:
touch mian.c
git add.
git commit -m“注釋”
修改文件并提交:
git add.
git commit - m“注釋”
刪除文件并提交:
git rm person.h
git cmmit -m“注釋”
版本回退:
git reset -hard HEAD 重置到當前版本
git reset -hard HEAD^^ 重置到上上個版本
git reset -hard 七位版本號 重置到指定版本
團隊開發(fā):
共享庫:遠程共享庫:Github/OSChina
下載代碼到本地:
git clone
新增文件同步:
touch person.h
git add.
git commit -m“創(chuàng)建person.h”
git push
git pull
修改文件同步:
git add.
git commit -m“注釋”
git push
git pull
刪除文件同步:
git rm filename git commit -m“注釋”
git push
git pull
忽略文件:
touch .gitignore open .gitignore 加入忽略文件名
git add.git commit -m“注釋”
10:JSON、XML的解析?
JSON:
第三方框架:JSONKit,SBJson,TouchJSON(性能逐漸遞減)
系統(tǒng)自帶:NSJSONSerialization(性能最好)
常見方法:
NSJSONSerialization的常見方法
JSON數(shù)據(jù) OC對象
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
OC對象 ? JSON數(shù)據(jù)
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error
DOM:一次將整個XML文檔加載進內(nèi)寸,比較適合解析小的文檔
SAX:從根元素開速,按順序一個元素一個元素往下解析,比較適合解析大的文檔
系統(tǒng)自帶:
NSXMLParser:SAX方式解析,使用簡單
第三方:libxml2:純C語言,默認包含在iOS SDK中,同時支持DOM和SAX
GDataXML:DOM方式解析,基于libxml2
區(qū)別:
(1)可讀性方面:基本相同,xml的可讀性比較好
(2)可擴展性方面:都具有很好的擴展性
(3)編碼難度方面:相對而言:JSON的編碼比較容易 (4)解碼難度:json的解碼難度基本為零,xml需要考慮子節(jié)點和父節(jié)點 (5)數(shù)據(jù)體積方面:json相對于xml來講,數(shù)據(jù)體積小,傳遞的速度跟快些 (6)數(shù)據(jù)交互方面:json與JavaScript的交互更加方面,更容易解析處理,更好的數(shù)據(jù)交互 (7)數(shù)據(jù)描述方面:xml對數(shù)據(jù)描述性比較好
(8)傳輸速度方面:json的速度遠遠快于xml
JSON底層原理:遍歷字符串中的字符,最終根據(jù)格式規(guī)定的特殊字符,比如{}號,[]號, : 號 等進行區(qū)分,{}號是一個字典 的開始,[]號是一個數(shù)組的開始, : 號是字典的鍵和值的分水嶺,最終乃是將json數(shù)據(jù)轉(zhuǎn)化為字典,字典中值可能是字典,數(shù) 組,或字符串而已。
XML底層原理: XML解析常用的解析方法有兩種:DOM解析和SAX解析。DOM 采用建立樹形結(jié)構(gòu)的方式訪問 XML 文檔,而 SAX 采用的事件模型。 。DOM 解析把 XML 文檔轉(zhuǎn)化為一個包含其內(nèi)容的樹,并可以對樹進行遍歷。使用 DOM 解析器的時候需 要處理整個 XML 文檔,所以對性能和內(nèi)存的要求比較高。SAX在解析 XML 文檔的時候可以觸發(fā)一系列的事件,當發(fā)現(xiàn)給定的tag 的時候,它可以激活一個回調(diào)方法,告訴該方法制定的標簽已經(jīng)找到。SAX 對內(nèi)存的要求通常會比較低,因為它讓開發(fā)人員自己來決 定所要處理的tag。特別是當開發(fā)人員只需要處理文檔中所包含的部分數(shù)據(jù)時,SAX 這種擴展能力得到了更好的體現(xiàn)。
延伸:SAX與DOM的區(qū)別: 1、SAX處理的優(yōu)點非常類似于流媒體的優(yōu)點。分析能夠立即開始,而不是等待所有的數(shù)據(jù)被處理。而且由于應用程序只是 在讀取數(shù)據(jù)時檢查數(shù)據(jù),因此不需要將數(shù)據(jù)存儲在內(nèi)存中。這對于大型文檔來說是個巨大的優(yōu)點。事實上,應用程序甚至不 必解析整個文檔;它可以在某個條件得到 滿足時停止解析。一般來說,SAX 還比它的替代者 DOM 快許多。另一方面,由 于應用程序沒有以任何方式存儲數(shù)據(jù),使用 SAX 來更改數(shù)據(jù)或在數(shù)據(jù)流中往后移是不可能的。
2、DOM 以及廣義的基于樹的處理具有幾個優(yōu)點。首先,由于樹在內(nèi)存中是持久的,因此可以修改它以便應用程序能對數(shù) 據(jù)和結(jié)構(gòu)作出更改。它還可以在任何時候在樹中上下 導航,而不是像 SAX 那樣是一次性的處理。DOM 使用起來也要簡單 得多。另一方面,在內(nèi)存中構(gòu)造這樣的樹涉及大量的開銷。大型文件完全占用系統(tǒng)內(nèi)存容量的情況并不鮮見。此外,創(chuàng)建一 棵 DOM 樹可能是一個緩慢的過程。
3、選擇 DOM 還是選擇 SAX,這取決于下面幾個因素:
應用程序的目的:如果打算對數(shù)據(jù)作出更改并將它輸出為 XML,那么在大多數(shù)情況下,DOM 是適當?shù)倪x擇。并不是說使 用 SAX 就不能更改數(shù)據(jù),但是該過程要復雜得多,因為您必須對數(shù)據(jù)的一份拷貝而不是對數(shù)據(jù)本身作出更改。
數(shù)據(jù)容量: 對于大型文件,SAX 是更好的選擇。數(shù)據(jù)將如何使用:如果只有數(shù)據(jù)中的少量部分會被使用,那么使用 SAX 來將該部分數(shù)據(jù)提取到應用程序中可能更好。 另一方面,如果您知道自己以后會回頭引用已處理過的大量信息,那么 SAX 也許不是恰當?shù)倪x擇。
對速度的需要:SAX 實現(xiàn)通常要比 DOM 實現(xiàn)更快。
SAX 和 DOM 不是相互排斥的,記住這點很重要。您可以使用 DOM 來創(chuàng)建 SAX 事件流,也可以使用 SAX 來創(chuàng)建 DOM 樹。事實上,用于創(chuàng)建 DOM 樹的大多數(shù)解析器實際上都使用 SAX 來完成這個任務!
11:不等高Cell的高度計算?
iOS8之前:
面向?qū)ο螅喊褦?shù)據(jù)布局到Cell上,拿到cell最底部控件的最大Y值
面向過程:把每個cell的高度算出來,保存到數(shù)組中,在heightForRow中返回每個cell的高度
iOS8之后:
新特性:估算高度
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 100; // 隨便設個不那么離譜的值
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
cell最下面的控件一定要粘著最底部,且不能實現(xiàn)高度的代理方法
注意:不要再高度的代理方法中計算cell的高度,這樣會由于代理方法的頻繁調(diào)用造成性能降低,最好在cellForRow中計算高度,或者請求到數(shù)據(jù)后直接調(diào)用。
問題:由于iOS8與iOS7上cell的高度計算原理不同,導致相同的代碼在iOS7上順滑,在iOS8上卻莫名卡頓 ?
原因:
不開啟高度估算時,UITableView 上來就要對所有 cell 調(diào)用算高來確定 contentSize
dequeueReusableCellWithIdentifier:forIndexPath: 相比不帶 “forIndexPath” 的版本會多調(diào)用一次高度計算
iOS7 計算高度后有”緩存“機制,不會重復計算;而 iOS8 不論何時都會重新計算 cell 高度
解決辦法:孫源團隊的第三方框架——UITableView+FDTemplateLayoutCell 具體見鏈接--大神博客
原理:
和每個 UITableViewCell ReuseID 一一對應的 template layout cell
這個 cell 只為了參加高度計算,不會真的顯示到屏幕上;它通過 UITableView 的 -dequeueCellForReuseIdentifier: 方法 lazy 創(chuàng)建并保存,所以要求這個 ReuseID 必須已經(jīng)被注冊到了 UITableView 中,也就是說,要么是 Storyboard 中的原型 cell,要么就是使用了 UITableView 的 -registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊方法。
根據(jù) autolayout 約束自動計算高度
使用了系統(tǒng)在 iOS6 就提供的 API:-systemLayoutSizeFittingSize:
根據(jù) index path 的一套高度緩存機制
計算出的高度會自動進行緩存,所以滑動時每個 cell 真正的高度計算只會發(fā)生一次,后面的高度詢問都會命中緩存,減少了非常可觀的多余計算。
自動的緩存失效機制
無須擔心你數(shù)據(jù)源的變化引起的緩存失效,當調(diào)用如-reloadData,-deleteRowsAtIndexPaths:withRowAnimation:等任何一個觸發(fā) UITableView 刷新機制的方法時,已有的高度緩存將以最小的代價執(zhí)行失效。如刪除一個 indexPath 為 [0:5] 的 cell 時,[0:0] ~ [0:4] 的高度緩存不受影響,而 [0:5] 后面所有的緩存值都向前移動一個位置。自動緩存失效機制對 UITableView 的 9 個公有 API 都進行了分別的處理,以保證沒有一次多余的高度計算。
預緩存機制
預緩存機制將在 UITableView 沒有滑動的空閑時刻執(zhí)行,計算和緩存那些還沒有顯示到屏幕中的 cell,整個緩存過程完全沒有感知,這使得完整列表的高度計算既沒有發(fā)生在加載時,又沒有發(fā)生在滑動時,同時保證了加載速度和滑動流暢性,下文會著重講下這塊的實現(xiàn)原理。
利用RunLoop空閑時間執(zhí)行預緩存任務
FDTemplateLayoutCell 的高度預緩存是一個優(yōu)化功能,它要求頁面處于空閑狀態(tài)時才執(zhí)行計算,當用戶正在滑動列表時顯然不應該執(zhí)行計算任務影響滑動體驗。
一般來說,這個功能要耦合 UITableView 的滑動狀態(tài)才行,但這種實現(xiàn)十分不優(yōu)雅且可能破壞外部的 delegate 結(jié)構(gòu),但好在我們還有RunLoop這個工具,了解它的運行機制后,可以用很簡單的代碼實現(xiàn)上面的功能。
空閑RunLoopMode
在曾經(jīng)的 RunLoop 線下分享會中介紹了 RunLoopMode 的概念。
當用戶正在滑動 UIScrollView 時,RunLoop 將切換到 UITrackingRunLoopMode 接受滑動手勢和處理滑動事件(包括減速和彈簧效果),此時,其他 Mode (除 NSRunLoopCommonModes 這個組合 Mode)下的事件將全部暫停執(zhí)行,來保證滑動事件的優(yōu)先處理,這也是 iOS 滑動順暢的重要原因。
當 UI 沒在滑動時,默認的 Mode 是 NSDefaultRunLoopMode(同 CF 中的 kCFRunLoopDefaultMode),同時也是 CF 中定義的 “空閑狀態(tài) Mode”。當用戶啥也不點,此時也沒有什么網(wǎng)絡 IO 時,就是在這個 Mode 下。
用RunLoopObserver找準時機
注冊 RunLoopObserver 可以觀測當前 RunLoop 的運行狀態(tài),并在狀態(tài)機切換時收到通知:
RunLoop開始
RunLoop即將處理Timer
RunLoop即將處理Source
RunLoop即將進入休眠狀態(tài)
RunLoop即將從休眠狀態(tài)被事件喚醒
RunLoop退出
因為“預緩存高度”的任務需要在最無感知的時刻進行,所以應該同時滿足:
RunLoop 處于“空閑”狀態(tài) Mode
當這一次 RunLoop 迭代處理完成了所有事件,馬上要休眠時
使用 CF 的帶 block 版本的注冊函數(shù)可以讓代碼更簡潔:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = kCFRunLoopDefaultMode;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
// TODO here
});
CFRunLoopAddObserver(runLoop, observer, runLoopMode);
在其中的 TODO 位置,就可以開始任務的收集和分發(fā)了,當然,不能忘記適時的移除這個 observer
分解成多個RunLoop Source任務
假設列表有 20 個 cell,加載后展示了前 5 個,那么開啟估算后 table view 只計算了這 5 個的高度,此時剩下 15 個就是“預緩存”的任務,而我們并不希望這 15 個計算任務在同一個 RunLoop 迭代中同步執(zhí)行,這樣會卡頓 UI,所以應該把它們分別分解到 15 個 RunLoop 迭代中執(zhí)行,這時就需要手動向 RunLoop 中添加 Source 任務(由應用發(fā)起和處理的是 Source 0 任務)
Foundation 層沒對 RunLoopSource 提供直接構(gòu)建的 API,但是提供了一個間接的、既熟悉又陌生的 API:
- (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thr
withObject:(id)arg
waitUntilDone:(BOOL)wait
modes:(NSArray *)array;
這個方法將創(chuàng)建一個 Source 0 任務,分發(fā)到指定線程的 RunLoop 中,在給定的 Mode 下執(zhí)行,若指定的 RunLoop 處于休眠狀態(tài),則喚醒它處理事件,簡單來說就是“睡你xx,起來嗨!”
于是,我們用一個可變數(shù)組裝載當前所有需要“預緩存”的 index path,每個 RunLoopObserver 回調(diào)時都把第一個任務拿出來分發(fā):
NSMutableArray *mutableIndexPathsToBePrecached = self.fd_allIndexPathsToBePrecached.mutableCopy;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
if (mutableIndexPathsToBePrecached.count == 0) {
CFRunLoopRemoveObserver(runLoop, observer, runLoopMode);
CFRelease(observer); // 注意釋放,否則會造成內(nèi)存泄露
return;
}
NSIndexPath *indexPath = mutableIndexPathsToBePrecached.firstObject;
[mutableIndexPathsToBePrecached removeObject:indexPath];
[self performSelector:@selector(fd_precacheIndexPathIfNeeded:)
onThread:[NSThread mainThread]
withObject:indexPath
waitUntilDone:NO
modes:@[NSDefaultRunLoopMode]];
});
這樣,每個任務都被分配到下個“空閑” RunLoop 迭代中執(zhí)行,其間但凡有滑動事件開始,Mode 切換成 UITrackingRunLoopMode,所有的“預緩存”任務的分發(fā)和執(zhí)行都會自動暫定,最大程度保證滑動流暢。
12:Socket簡單介紹?
Socket又被成為套接字,當網(wǎng)絡上的兩端通過建立一個雙向的通道來實現(xiàn)數(shù)據(jù)的交換,那么這個端口其實就是Socket。
網(wǎng)絡通信需要滿足一定的條件:
IP地址:(網(wǎng)絡上主機設備的唯一標識)
端口號:用于標示進程的邏輯,不同進程的標識
傳輸協(xié)議:TCP、UDP
TCP:
傳輸數(shù)據(jù)時候,需要建立連接,形成傳輸數(shù)據(jù)的通道
數(shù)據(jù)的大小不受限制,可以進行大數(shù)據(jù)的傳輸
通過三次握手來完成連接,因此是安全的,可靠的協(xié)議
- 由于是可靠連接,所以導致數(shù)據(jù)傳輸?shù)男实?/li>
UDP:
傳輸數(shù)據(jù)是將數(shù)據(jù)以及源和目的封裝到數(shù)據(jù)包中,不需要建立連接
因為不需要建立連接,所以是不可靠的連接
傳輸數(shù)據(jù)的大小被限制在64k以內(nèi)
由于是不可靠連接,所以傳輸數(shù)據(jù)速度快
http與Scoket的不同?
http是客戶端用http協(xié)議進行請求,發(fā)送請求時需要封裝http請求頭,服務端一般會有web端進行配合,其請求方式一般為客戶端主動發(fā)送請求,服務端才能給響應,一次請求后則斷開連接,以便以節(jié)省資源,除了長連接外,服務端不能給客戶端響應。
Scoket是客戶端與服務端用Scoket進行連接,連接后不斷開,雙方都可以發(fā)送數(shù)據(jù),一般用在游戲等對數(shù)據(jù)即時性要求比較高的場合
13:XMPP的簡單介紹?
XMPP是一個即時通訊的協(xié)議,其方便了用于即時通訊在網(wǎng)絡上數(shù)據(jù)傳輸格式,比如登錄,獲取好友等格式。XMPP在網(wǎng)絡中傳輸?shù)膬?nèi)容格式為XML格式
XMPP是一個基于Socket的網(wǎng)絡協(xié)議,這樣其可以實現(xiàn)長連接,以方便實現(xiàn)即時通訊功能
XMPP的客戶端是使用一個XMPPFramework框架來實現(xiàn),服務器是使用一個開源的服務器Openfire
當客戶端獲取到服務器發(fā)送過來的好友消息,客戶端需要對XML進行解析,使用的解析框架KissXML,而不是NSXMLParser
XMPP沒有提供發(fā)送附件的功能,需要自己實現(xiàn),把文件上傳到文件服務器,上傳成功后再把附件的路徑發(fā)送給好友
14:環(huán)信的簡單介紹?
環(huán)信是一個即時通信的服務提供商
環(huán)信使用的是XMPP協(xié)議,它是再XMPP的基礎上進行二次開發(fā),對服務器Openfire和客戶端進行功能模型的添加和客戶端SDK的封裝,環(huán)信的本質(zhì)還是使用XMPP,基本于Socket的網(wǎng)絡通信
環(huán)信內(nèi)部實現(xiàn)了數(shù)據(jù)緩存,會把聊天記錄添加到數(shù)據(jù)庫,把附件(如音頻文件,圖片文件)下載到本地,使程序員更多時間是花到用戶即時體驗上
環(huán)信內(nèi)部已經(jīng)實現(xiàn)了視頻,音頻,圖片,其它附件發(fā)送功能
環(huán)信使用公司可以節(jié)約時間成本,不需要公司內(nèi)部搭建服務器,客戶端的開發(fā),使用環(huán)信SDK比使用XMPPFramework更簡潔方便
15:數(shù)據(jù)存儲?
plist存儲、偏好設置存儲、歸檔、SQLite、Core Data
plist存儲:
plist存儲,就是生成一個plist文件,用來儲存字典或者數(shù)組,其不能儲存自定義對象
偏好設置儲存:
NSUserDefaults,其底層封裝了一個字典,利用字典的方式生成plist,這樣做的好處是不用關(guān)心文件名,快速的進行的鍵值配對
存儲:
偏好設置NSUserDefaults
底層就是封裝了一個字典,利用字典的方式生成plist
好處:不需要關(guān)心文件名,快速進行鍵值對存儲
// name xmg
[[NSUserDefaults standardUserDefaults] setObject:@"xmg" forKey:@"name"];
// age 18
[[NSUserDefaults standardUserDefaults] setInteger:18 forKey:@"age"];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isOn"];
讀取:
NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
BOOL ison = [[NSUserDefaults standardUserDefaults] boolForKey:@"isOn"];
歸檔:
NSKeyedArchiver:專門用來做自定義對象歸檔
// 什么時候調(diào)用:當一個對象要歸檔的時候就會調(diào)用這個方法歸檔
// 作用:告訴蘋果當前對象中哪些屬性需要歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeInt:_age forKey:@"age"];
}
// 什么時候調(diào)用:當一個對象要解檔的時候就會調(diào)用這個方法解檔
// 作用:告訴蘋果當前對象中哪些屬性需要解檔
// initWithCoder什么時候調(diào)用:只要解析一個文件的時候就會調(diào)用
- (id)initWithCoder:(NSCoder *)aDecoder
{
#warning [super initWithCoder]
if (self = [super init]) {
// 解檔
// 注意一定要記得給成員屬性賦值
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
SQLite:
SQlite是一款輕型的嵌入式數(shù)據(jù)庫,其占用的資源非常低,因此十分適合在移動端使用。
基本用法:
增加表字段:
ALTER TABLE 表名 ADD COLUMN 字段名 字段類型;
刪除表字段:
ALTER TABLE 表名 DROP COLUMN 字段名;
修改表字段:
ALTER TABLE 表名 COLUMN 舊字段名 TO 新字段名;
Core Data:
1> CoreData是對SQLite數(shù)據(jù)庫的封裝
2> CoreData中的NSManagedObjectContext在多線程中不安全
3> 如果想要多線程訪問CoreData的話,最好的方法是一個線程一個NSManagedObjectContext
4> 每個NSManagedObjectContext對象實例都可以使用同一個NSPersistentStoreCoordinator實例,這是因為NSManagedObjectContext會在便用NSPersistentStoreCoordinator前上鎖
16:get與post的區(qū)別?
GET和POST的主要區(qū)別表現(xiàn)在數(shù)據(jù)傳遞上
GET:
在請求體URL后面以 ? 的的形式跟上發(fā)送給服務器的參數(shù),多個參數(shù)之間以 & 隔開
由于服務器和瀏覽器對URL長度有限制,所以其后面附帶的參數(shù)是有限制的
POST:
發(fā)送給服務器的所有參數(shù)都放在請求體中
理論上,POST傳遞的數(shù)據(jù)量沒有限制