iOS 面試總結
UI相關面試題
系統(tǒng)的UI事件傳遞機制是怎樣的?
hidtest 和hidpoint 內(nèi)部實現(xiàn)
先判斷自己是否隱藏或者支持用戶響應或者是否具有透明度,然后從頂部倒序開始尋找相應視圖
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.userInteractionEnabled ||
[self isHidden] ||
self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
//遍歷當前對象的子視圖
__block UIView *hit = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 坐標轉(zhuǎn)換
CGPoint vonvertPoint = [self convertPoint:point toView:obj];
//調(diào)用子視圖的hittest方法
hit = [obj hitTest:vonvertPoint withEvent:event];
// 如果找到了接受事件的對象,則停止遍歷
if (hit) {
*stop = YES;
}
}];
if (hit) {
return hit;
}
else{
return self;
}
}
else{
return nil;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat x1 = point.x;
CGFloat y1 = point.y;
CGFloat x2 = self.frame.size.width / 2;
CGFloat y2 = self.frame.size.height / 2;
double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
// 67.923
if (dis <= self.frame.size.width / 2) {
return YES;
}
else{
return NO;
}
}
使UITableView滾動更流暢得方案和思路都有哪些?
cpu+GPU
cpu 采用子線程進行對象創(chuàng)建銷毀,包括進行預排版,以及圖片的解碼,
Gpu 和異步繪制方案
kvo實現(xiàn)原理,kvc實現(xiàn)原理,通知實現(xiàn)原理
用分類做什么
1聲明私有方法
2分解體積龐大的類文件
3把framework的私有方法進行公開
分類的特點
運行時決議
可以為系統(tǒng)類添加分類
分類可以添加哪些內(nèi)容
實例方法
類方法
協(xié)議
屬性
成員變量(使用關聯(lián)對象)
擴展
編譯時決議
只以聲明的形式存在,多數(shù)情況下寄生于宿主類的.m中
不能為系統(tǒng)添加擴展
代理
代理是一種軟件設計模式
代理是用代理模式實現(xiàn)
傳遞一對一
通知
使用觀察者模式來實現(xiàn)的用于跨層傳遞消息的機制
傳遞方式為一對多
kvo
kvo是Objiective-C對觀察者設計模式的又一種實現(xiàn)
Apple 使用了isa混寫(is-swizzling)來實現(xiàn)kvo
runtime

blcok
對象、函數(shù)封裝、執(zhí)行上下文
不需要__blcok 的情況靜態(tài)局部變量、全局變量、靜態(tài)全局變量




該解決循環(huán)引用有一個問題,就是,如果不調(diào)用該方法,那么這個環(huán)會一直存在
什么是blcok
是一個對象、對函數(shù)封裝、執(zhí)行上下文
為什么block會產(chǎn)生循環(huán)引用
自循環(huán)引用,多循環(huán)引用
1、如果說當前blcok對當前某一成員變量進行截獲,block會對對應變量,有一個強引用,當前block由于當前對象有一個強引用,產(chǎn)生一個自循環(huán)引用,可以通過聲明為__weak 變量來進行循環(huán)引用的消除
2、如果說我們定義一個__blcok說明符,也會產(chǎn)生循環(huán)引用,但是是區(qū)分場景的,一種是在arc下面是會產(chǎn)生循環(huán)引用的,而在mrc下面不會.在arc下面可以通過斷環(huán)的方式去解除對應的循環(huán)引用,但是有一個弊端、如果block一直得不到調(diào)用,這個循環(huán)引用是沒有辦法解除的
怎么理解截獲變量的特性
1對于基本類型的局部變量,是對其值進行截獲,
2對于對象類型的局部變量,是對其進行強引用,連通全所有修飾符共同進行截獲
3對于靜態(tài)的局部變量,是對其指針進行截獲
4對于全局變量、局部、靜態(tài)全局變量不產(chǎn)生截獲
多線程
iOS 提供哪幾種多線程
1 GCD
2 NSOperation 網(wǎng)絡請求,圖片下載 ,可以進行狀態(tài)改變
3 NSThread 可以實現(xiàn)常駐線程
GCD
同步/異步 和串行/并發(fā)
dispatch_barrier_async 異步柵欄調(diào)用 解決多讀單寫的問題

dispathc_group



performSelector 方法調(diào)用必須保證當前線程有runLoop,否則該方法會失效;

NSOperation 優(yōu)勢
添加任務依賴
任務執(zhí)行狀態(tài)控制
控制最大并發(fā)量




iOS 當中都有哪些鎖
@synchronized 一般在創(chuàng)建單利對象的時候使用,保證在多線程環(huán)境中是唯一的
atomic 修飾屬性的關鍵字,對被修飾的對象進行原子操作
OSSpinLock 自旋鎖,循環(huán)等待詢問,不釋放當前資源,用于輕量級數(shù)據(jù)訪問,簡單的int值 +1/-1操作
NSRecursiveLock 遞歸鎖 特點可以重入

NSLock 用于解決細密度的線程同步問題,用于解決線程互斥和進入自己的臨界區(qū)

dispatch_semaphore_t 信號量
runLoop
什么是runloop
RunLoop是通過內(nèi)部維護的事件循環(huán)來對事件/消息進行管理的對象
沒有消息處理時,休眠以避免資源占用
有消息處理時立刻被喚醒
在main函數(shù)中


滑動tableview的時候我們的定時器還會生效嗎?
kcfRunLoopDefaultModel 滑動tableView 會切換 UITrackingRunLoopMode 就不會生效了
解決方案,將Nstimer加入到CommonMode 中
RunLoop與線程關系
RunLoop與線程是一一對應的
實現(xiàn)常駐線程
為當前線程開啟一個RunLoop
向該RunLoop中添加一個Port/Source等維持RunLoop的事件循環(huán)
啟動該RunLoop
代碼實現(xiàn)

怎樣保證子線程數(shù)據(jù)來回更新UI的時候不打斷用戶滑動操作?
用戶進行滑動的時候,runLoop在model 會被切換為 UITrackingRunLoopMode
網(wǎng)絡請求時,是在子線程進行操作,請求完成之后回來在主線程更新UI
在主線程邏輯進行包裝,將他包裝在DefautModel下執(zhí)行.
HTTP 協(xié)議
get用于獲取資源,安全的,冪等的,可緩存
post 處理資源,非安全的,非冪等的,不可緩存
Http 無連接的,無狀態(tài)的
UDP 復用、分用,差錯檢驗

DNS
域名到IP地址的映射,DNS解析請求采用UDP數(shù)據(jù)報,且明文
DNS解析查詢方式,遞歸查詢,迭代查詢
DNS解析存在哪些常見問題?
DNS劫持問題:
DNS解析轉(zhuǎn)發(fā)問題
DNS劫持與HTTP的關系是怎么樣的?
兩者之間是沒有關系的,第一解析是在客戶端本地的,是發(fā)生在與HTTP建立連接之前進行的,2DNS解析請求使用UDP數(shù)據(jù)報,端口號為53
DNS解析轉(zhuǎn)發(fā)
設計模式:6種
責任鏈:事件響應機制
僑界模式
適配器模式
對象適配器和被適配器,
單例模式:
命令模式:行為參數(shù)化,降低代碼重合度
設計原則

單一職責原則:一個類只負責一件事,如:view 負責視圖展示,clayer 負責動畫
開關原則: 對修改關閉、對擴展開放
接口隔離原則:使用多個專門的協(xié)議、而不是一個龐大臃腫的協(xié)議.例如:tableView ,數(shù)據(jù)返回和事件處理
依賴倒置原則:抽象不應該依賴具體實現(xiàn),具體實現(xiàn)依賴抽象
里氏替換原則:父類可以被子類無縫替換,切原有的功能不受任何影響 例如:kvo
迪米特法制:一個對象應當對其他對象盡可能少的了解
高內(nèi)聚、低耦合
文件緩存:
淘汰原則:1先進先出,
LRU算法進行淘汰,時間訪問優(yōu)先選擇,訪問頻率算法
網(wǎng)絡設計
網(wǎng)絡部分設計需要考慮哪些問題?
圖片請求最大并發(fā)量,
請求超時策略:對一個數(shù)據(jù)進行2次或者3次請求,失敗后,
請求優(yōu)先級




算法
鏈表反轉(zhuǎn)
//
// ReversList.h
// GCD
//
// Created by 龔魁華 on 2020/4/23.
// Copyright ? 2020 龔魁華. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//定義一個鏈表
struct Node {
int data;
struct Node *next;
};
@interface ReversList : NSObject
//鏈表反轉(zhuǎn)
struct Node* reverseList(struct Node *head);
//構造一個鏈表
struct Node* contructList(void);
//打印鏈表中的數(shù)據(jù)
void printList(struct Node *head);
@end
NS_ASSUME_NONNULL_END
//
// ReversList.m
// GCD
//
// Created by 龔魁華 on 2020/4/23.
// Copyright ? 2020 龔魁華. All rights reserved.
//
#import "ReversList.h"
@implementation ReversList
//鏈表反轉(zhuǎn)
struct Node* reverseList(struct Node *head){
//定義遍歷指針,初始化為頭節(jié)點
struct Node *p = head;
//反轉(zhuǎn)后的鏈表頭部
struct Node *newH = NULL;
//遍歷鏈表
while (p != NULL) {
//記錄下一個結點
struct Node *temp = p->next;
//當前結點的next指向鏈表頭部
p->next = newH;
//更改新鏈表頭部為當前結點
newH = p;
//移動p指針
p = temp;
}
//返回反轉(zhuǎn)后的鏈表頭結點
return newH;
}
//構造一個鏈表
struct Node* contructList(void){
//頭結點定義
struct Node *head = NULL;
//記錄當前尾結點
struct Node *cur = NULL;
for (int i = 1; i < 5; i++) {
struct Node *node = malloc(sizeof(struct Node));
node->data = i;
//頭結點為空,新結點即為頭結點
if (head == NULL) {
head = node;
}
//當前結點的next為新結點
else{
cur->next = node;
}
//設置當前結點為新結點
cur = node;
}
return head;
}
//打印鏈表中的數(shù)據(jù)
void printList(struct Node *head){
struct Node *temp = head;
while (temp != NULL) {
printf("node is %d\n",temp->data);
temp = temp->next;
}
}
@end
字符串交換
//字符串交換
void char_reverse(char *cha){
//指向第一個字符
char *begin = cha;
char *end = cha + strlen(cha) -1;
while (begin<end) {
char temp = *begin;
*(begin ++) = *end;
*(end--) = temp;
}
}
有序數(shù)組合并
/// 有序數(shù)組合并
/// @param a 第一個數(shù)組
/// @param aLen 數(shù)組長度
/// @param b 第二個數(shù)組
/// @param bLen <#bLen description#>
/// @param result 合并后結果
void mergeList(int a[],int aLen,int b[],int bLen,int result[]){
int p = 0;//遍歷數(shù)組a的指針
int q = 0;//遍歷數(shù)組b的指針
int i = 0;//記錄當前存儲位置
//任意數(shù)組沒有達到邊界則進行遍歷
while (p<aLen&&q<bLen) {
//如果啊數(shù)組對應位置的值小于b數(shù)組對應位置的值
if (a[p] <= b[q]) {
//存儲a數(shù)組的值
result[i] = a[p];
//移動a數(shù)組的遍歷指針
p++;
}else{
//存儲b數(shù)組的值
result[i] = b[q];
q++;
}
//指向合并結果的下一個存儲位置
i++;
}
//如果a數(shù)組有剩余
while (p < aLen) {
//將a數(shù)組剩余部分拼接到合并結果的后面
result[i] = a[p++];
i++;
}
//如果b數(shù)組有剩余
while (q < bLen) {
//將b數(shù)組剩余部分拼接到合并結果的后面
result[i] = b[q++];
i++;
}
}
尋找2個view的共同父視圖
- (NSArray <UIView *> *)finCommonSuperView:(UIView *)viewOne other:(UIView *)viewOther{
NSMutableArray *result = [NSMutableArray array];
//查找第一個父視圖的所有父視圖
NSArray *arrayOne = [self findSuperViews:viewOne];
//查找第二個父視圖的所有父視圖
NSArray *arrayOther = [self findSuperViews:viewOther];
int i = 0;
//越界限制條件
while (i<MIN(arrayOne.count, arrayOther.count)) {
//倒序方式獲取各個視圖的父視圖
UIView *superOne = [arrayOne objectAtIndex:arrayOne.count -i -1];
UIView *superOther = [arrayOther objectAtIndex:arrayOther.count -i -1];
//比較如果相等,則為共同父視圖
if (superOne == superOther) {
[result addObject:superOne];
i++;
}
//如果不相等,則結束遍歷
else{
break;
}
}
return result;
}
- (NSArray <UIView *> *)findSuperViews:(UIView *)view{
//初始化為第一父視圖
UIView *temp = view.superview;
//保存結果數(shù)組
NSMutableArray *result = [NSMutableArray array];
while (temp) {
[result addObject:temp];
//順著superview指針一直向上查找
temp = temp.superview;
}
return result;
}
無序數(shù)組中位數(shù)查找
int findMedian(int a[],int aLen){
int low = 0;
int high = aLen -1;
int mid = (aLen - 1)/2;
int div = PartSort(a,low,high);
while (div != mid) {
if (mid < div) {
//左半?yún)^(qū)間查找
div = PartSort(a,low,div - 1);
}else{
//右半?yún)^(qū)間查找
div = PartSort(a,div+1,high);
}
}
//找到了
return a[mid];
}
int PartSort(int a[], int start,int end){
int low = start;
int high = end;
//選取關鍵字
int key = a[end];
while (low<high) {
//左邊找比key大的值
while (low<high && a[low] <= key) {
++low;
}
//右邊著比key小的值
while (low<high&&a[high]>=key) {
--high;
}
if (low<high) {
//找到之后交換左右的值
int temp = a[low];
a[low] = a[high];
a[high] = temp;
}
}
int temp = a[high];
a[high] = a[end];
a[end] = temp;
return low;
}