iOS 面試題~愿所有移動開發(fā)者熬過寒冬

眾所周知,由于種種原因,導致今年移動開發(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é)議

  1. 由于是可靠連接,所以導致數(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ù)量沒有限制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,582評論 30 472
  • OC的理解與特性 OC作為一門面向?qū)ο蟮恼Z言,自然具有面向?qū)ο蟮恼Z言特性:封裝、繼承、多態(tài)。它既具有靜態(tài)語言的特性...
    克魯?shù)吕?/span>閱讀 501評論 0 0
  • OC的理解與特性OC作為一門面向?qū)ο蟮恼Z言,自然具有面向?qū)ο蟮恼Z言特性:封裝、繼承、多態(tài)。它既具有靜態(tài)語言的特性(...
    LIANMING_LI閱讀 575評論 0 0
  • ——青燈素箋 多線程的底層實現(xiàn) 1.首先搞清楚是什么線程、什么是多線程2.Mach是第一個以多線程方式處理任務的系...
    秭劍執(zhí)一閱讀 1,327評論 0 19
  • 面試題9 1.描述應用程序的啟動順序。 2.為什么很多內(nèi)置類如UITableViewControl的delegat...
    嘖嘖嘖_野獸閱讀 2,316評論 0 26

友情鏈接更多精彩內(nèi)容