以大廠面試真題總結,構建自己的iOS開發體系(中)

在百度三面被掛掉之后,沉下心來,整理構建自己的開發體系,方便以后查看。 金三銀四已經降臨,為此提供了找了不少學習方向給大家,也是一些進價方向,希望能幫大家快速提升自己的短板!

持續更新,敬請關注

本章節:

標:不要浪費美好的年華,做自己覺得對的事情!

目錄

  • 四、Block
  • 五、Runtime
  • 六、Runloop
  • 七、KVO
  • 八、KVC
  • 九、Category
  • 十、網絡
  • 十一、UI

感謝觀看下載資料:2021年【最新iOS面試題】附答案

目錄

  • 最新 初級iOS 面試題
  • 最新 中級iOS 面試題
  • 最新 高級iOS 面試題

  • 《BAT面試資料全集》
  • 《BAT大廠常問iOS面試題》
  • 《2021年面試真題》
  • 《iOS開發面試題200道-面試問答篇》
  • 《iOS開發筆試題600道-筆試手寫篇》
  • 《iOS中級到高級面試題完整版》

下載地址:

地址:https://docs.qq.com/doc/DVU5LY1Bsb3dSZ3Nn

四、Block

17、block相關
1、block本質是什么?
- block是將函數及其執行上下文封裝起來的對象

2、關于block的截獲特性,你是否有了解?block的截獲變量特性是怎樣的?
變量捕獲機制分析:
- 對于“基本數據類型”的“局部變量”截獲其值
- 對于“對象”類型的局部變量“連同所有權修飾符”一起截獲
- 以“指針形式”截獲局部靜態變量(指針傳遞)
- 不截獲全局變量、靜態全局變量(直接訪問)

改外部變量必要條件
- 將auto從棧copy到堆
原因:棧中內存管理是由系統管理,出了作用域就會被回收,堆中才是可以由程序員管理

3、對棧上的block進行copy之后,假如在mrc環境下,內存是否回泄漏?
- copy操作之后,堆上的block沒有額外的成員變量指向它,正如我們alloc對象后,沒有進行release,造成內存泄漏

4、面試題:請思考,這段代碼有問題么?
{
    __block MCBlock *blockSelf = self;
    _blk = ^int(int num){
        return num * blockSelf.var;
    }
    _blk(3);
}
- 在MRC下,不會產生循環引用
- 在ARC下,會產生循環引用,造成內存泄漏

5、為什么block會產生循環引用?
- 如果當前block對當前對象的某一成員變量進行截獲,block會對當前對象有一個強引用
- 而當前block由于當前對象對其有一個強引用,產生了一個自循環引用的一個循環引用的問題

6、Block不允許修改外部變量的值
原因:
- block 本質上是一個對象,block 的花括號區域是對象內部的一個函數,變量進入 花括號,實際就是已經進入了另一個函數區域---改變了作用域。
- 在幾個作用域之間進行切換時,如果不加上這樣的限制,變量的可維護性將大大降低。
- 比如想在block內聲明了一個與外部同名的變量,此時是允許呢還是不允許呢?只有加上了這樣的限制,這樣的情景才能實現。

- 所以 Apple 在編譯器層面做了限制,如果在 block 內部試圖修改 auto 變量(無修飾符),那么直接編譯報錯。
- 可以把編譯器的這種行為理解為:對 block 內部捕獲到的 auto 變量設置為只讀屬性---不允許直接修改。

7、如何實現對外部變量的捕獲?
- 將變量設置為全局變量。原理:block內外可直接訪問全局變量
- 加 static (放在靜態存儲區/全局初始化區)。原理是block內部對外部auto變量進行指針捕獲
- 最優解:使用__block 關鍵字

8、__block
- 將auto變量封裝為結構體(對象),在結構體內部新建一個同名的auto變量
- block內截獲該結構體的指針
- 在block中使用自動變量時,使用指針指向的結構體中的自動變量

__block int var = 10;
void(^blk)(void) = ^{
    var = 20;
};
blk();

轉換后的代碼:

struct __Block_byref_var_0 {
    void *__isa;
    __Block_byref_var_0 *__forwarding;
    int __flags;
    int __size;
    int var; // 10 => 20 該結構體持有相當于原來自動變量的成員變量
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_var_0 *var; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

9、block在修改NSMutableArray需不需要添加__block
- 不需要
- 當變量是一個指針的時候,block里只是復制了一份這個指針,兩個指針指向同一個地址。
- 所以,在block里面對指針指向內容做的修改,在block外面也一樣生效。

10、block是如何捕獲局部變量的?
- block捕獲外界變量時,在內部會自動生成同一個屬性來保存

11、UIView動畫中block回調里self要不要弱引用?
- 不需要,它不會造成循環引用,因為它是類方法。
- 之所以需要弱引用本身,是因為怕對象之間產生循環引用,當前控制器不可能強引用一個類,所以循環無法形成。

12、block里面會不會存在self為空的情況(weak strong的原理)?

__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    [strongself.dataSource reloadDataWithCompletion:nil];
}];

- 有時候weakSelf在block里在執行reloadDataWithCompletion還存在
- 但在執行reloadDataWithCompletion前,可能會被釋放了
- 為了保證self在block執行過程里一直存在,對他強引用strongSelf

13、__block與__weak的區別
- _block不管是ARC還是MRC模式下都可以使用,可以修飾對象,還可以修飾基本數據類型
- __weak只能在ARC模式下使用,也只能修飾對象(NSString),不能修飾基本數據類型(int)
- __block對象可以在block中被重新賦值,__weak不可以。 

14、多層block嵌套如何使用weakSelf?
__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    __weak typeof(self) weakSelf2 = strongself;
    [strongself.dataSource reloadDataWithCompletion:^(BOOL result) {
        __strong typeof(self) strongSelf2 = weakSelf2;
    }];
}];

15、Masonry對于block內部引用self會不會造成循環引用?
- 不會
- 這個block沒有copy,是在棧上,使用完直接釋放了,

- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

18、 代理、Block利弊
- 與委托代理模式的代碼相比,用block寫出的代碼更為整潔

代理優點:
- 代理語法清晰,可讀性高,易于維護
- 它減少代碼耦合性,使事件監聽與事件處理分離
- 一個控制器可以實現多個代理,滿足自定義開發需求,靈活性較高

代理缺點:
- 實現代理的過程較繁瑣
- 跨層傳值時加大代碼的耦合性,并且程序的層次結構也變得混亂
- 當多個對象同時傳值時不易區分,導致代理易用性大大降低

block優點:
- 語法簡潔,代碼可讀性和維護性較高
- 配合GCD優秀的解決多線程問題
block缺點:
- Block中得代碼將自動進行一次retain操作,容易造成內存泄漏
- Block內默認引用為強引用,容易造成循環應用

運行成本:
delegate運行成本低,block的運行成本高
- block出棧需要將使用的數據從棧內存拷貝到堆內存,當然對象的話就是假引用技術,使用完block置nil才會消除
- delegate只是保存了一個對象的指針,直接回調,沒有額外的消耗。就像c的函數指針,只多了一個查表動作

19、有哪些情況會出現內存泄漏。
- block循環引用
- delegate循環引用問題
- NSTimer循環引用
- 地圖類處理

- 線程?;顃arget:self

20、__weak來解決block中的循環引用,還有別的方法嗎。
- __block
- 將對象傳進入修改


五、Runtime

21、以下方法打印什么
@implementation Son : Father

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

@end

這兩個都打印出來的是:Son.

- [self class] 會先在當前類的方法列表中查找class這個方法
- [super class] 會先到父類中去查找class方法
- 兩者在找不到的時候,都會繼續向祖先類查詢class方法,最終到NSObject類

- NSObject中class的實現
- (Class)class {
    return object_getClass(self);
}

22、Runtime如何通過selector找到對應的IMP地址?IMP和SEL關系是?
- SEL:類方法的指針,相當于一種編號,區別與IMP!
- IMP:函數指針,保存了方法的地址!

關系:SEL是通過表取對應關系的IMP,進行方法的調用!

struct objc_method {
    SEL method_name                                      
    char *method_types                                       
    IMP method_imp                                           
}

23、Runtime的相關術語
SEL、id、Class、Method、IMP、Cache、Property

- 介紹下runtime的內存模型(isa、對象、類、metaclass、結構體的存儲信息等)
- 為什么要設計metaclass
- class_copyIvarList & class_copyPropertyList區別
- class_rw_t 和 class_ro_t 的區別

23、交互兩個方法的現實有什么風險?
- class_replaceMethod
- method_exchangeImplementations
- class_getInstanceMethod

個人經驗總結:
- 當我們寫的類沒有繼承的關系的時候,倆種方法都沒什么問題
- 當有繼承關系又不確定方法實現沒實現,最好用class_replaceMethod方法

補充:在美圖秀秀面試時,一個面試官問到方法交互,我說就是交換兩個放的IMP指針指向,
他問還有么?不知道還有什么,現在想起來,他應該是想問從isa指針到方法查找,再到根據SEL查找IMP過程吧

- 多次hook方法會存在什么問題?

TODO(待填充);??????????

24、對象關聯底層數據結構
通過 runtime 的源碼得知:
- 關聯屬性并沒有添加到 category_t(分類)里邊
- 運行時也不會合并到元類對象里邊
- 而是存儲在一個全局的AssociationsManager里邊

#import <objc/runtime.h>
// 添加
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 獲取
objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id  _Nonnull object#>)

- 關聯對象的應用?系統如何實現關聯對象的
- 關聯對象的如何進行內存管理的?關聯對象如何實現weak屬性
- 關聯的對象,需要在主對象dealloc的時候釋放么?

被關聯的對象的生命周期內要比對象本身釋放晚很多, 它們會在被 NSObject -dealloc 調用的 object_dispose() 方法中釋放。

image
25、消息轉發流程,向一個nil對象發送消息會怎樣
轉發過程(一定要回答出從緩存中查找)
- 消息發送
- 動態方法解析
- 消息轉發

1、消息發送過程 objc_msgSend(receiver, selector)
- 向一個對象發送消息時,runtime會根據對象的isa指針找到所屬類
- 在該類的方法列表及父類方法列表中尋找方法(緩存)
- 如果在最頂層父類中依然找不到對應方法,會報 unrecognized selector send to xxx

2、向一個nil對象發送消息會怎樣?
- objc_msgSend會通過判斷self來決定是否發送消息
- 如果self為nil,那么selector也會為空,直接返回,不會出現問題
- 但對于[NSNull null]對象發送消息時,是會crash的,因為NSNull類只有一個null方法

在崩潰前有三次拯救程序崩潰的機會,就是接下來的消息轉發

3、消息轉發流程
TODO(待填充);??????????

26、performSelector:withObject:afterDelay: 內部大概是怎么實現的,有什么注意事項么?
內部實現:
- 創建一個定時器, 時間結束后系統會使用runtime通過方法名稱去方法列表中找到對應的方法實現并調用方法
- Selector本質就是方法名稱

注意事項:
- 調用performSelector:withObject:afterDelay:方法時,先判斷希望調用的方法是否存在respondsToSelector:
- 這個方法是異步方法,必須在主線程調用,在子線程調用永遠不會調用到想調用的方法


六、Runloop

27、RunLoop相關
什么是RunLoop?
- RunLoop 實際上是一個對象
- 這個對象在循環中用來處理程序運行過程中出現的各種事件(比如說觸摸事件、UI刷新事件、定時器事件、Selector事件)
- 從而保持程序的持續運行
- 在沒有事件處理的時候,會使線程進入睡眠模式,從而節省 CPU 資源,提高程序性能

// 簡單的理解為如下代碼
int main(int argc, char * argv[]) {        
    BOOL running = YES;
    do {
        // 執行各種任務,處理各種事件
        // ......
    } while (running);  // 判斷是否需要退出

    return 0;
}

- 講講runloop,項目中有用到么?
- runloop內部實現邏輯?
- timer與runloop的關系?
- 程序中添加每3秒響應一次的NSTimer,當拖動tableview時timer可能無法響應要怎么解決?
- runloop是怎么響應用戶操作的,具體流程是什么樣的?
- 說說runloop的幾種狀態?
- runloop的mode作用是什么

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            // 睡眠中等待消息
            int message = sleep_and_wait;
            // 處理消息
            retVal = process_message(message);
        } while (retVal == 0)
        return 0;
    }
}

  • runloop內部實現邏輯

    image
  • 應用范疇

- 定時器(Timer)、PerformSelect
- GCD Async Main Queue
- 事件響應、手勢識別、界面刷新
- 網絡請求
- AutoreleasePool

  • 基本應用
- 保持程序的持續運行
- 處理App中的各種事件(比如觸摸事件、定時器事件等)
- 節省CPU資源,提高程序性能:該做事時做事,該休息時休息

  • runloop和線程的關系
- 每條線程都有唯一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 線程剛創建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創建
- RunLoop會在線程結束時銷毀
- 主線程的RunLoop已經自動獲?。▌摻ǎ?,子線程默認沒有開啟RunLoop

/*
 * 從字典中獲取,如果沒有則直接創建
 */
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }

    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
}

28、 NSTimer相關
1、NSTimer準嗎?如果不準的話原因是什么?如何解決?
原因:
- NSTimer的觸發時間到的時候,會在RunLoop中被檢測一次;
- 如果在這一次的RunLoop中做了耗時的操作,會處于阻塞狀態
- 時間超過了定時器的間隔時間,觸發時間就會推遲到下一個runloop周期

解決方法:
- 在子線程中創建timer,在子線程中進行定時任務的操作,需要UI操作時切換回主線程進行操作
- 使用CADisplayLink(時鐘???)
- 使用GCD定時器

2、使用NSTimer是如何處理循環引用的?
- 使用類方法
TODO(待填充);??????????


拓展、如何利用runloop監控卡頓

七、KVO

29、KVO相關

KVO 的 全稱Key-Value Observing,俗稱“鍵值監聽”,可以用于某個對象屬性值的改變

1、iOS用什么方式實現對一個對象的KVO?(KVO的本質是什么)
- 利用runtimeAPI動態生成一個子類(NSKVONotifying_XXXX),并且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時,會動用Foundation的_NSSetXXXValueAndNotify函數
- willChangeValueForKey
- 父類原來的setter方法
- didChangeValueForKey
- 內部觸發監聽器(ObserveValueForKeyPath:ofObject:change:context)

2、如何手動觸發KVO
- 手動調用willChangeValueForKey
- 修改成員變量值
- 手動調用didChangeValueForKey

3、直接修改成員變量會觸發KVO么
- 不會觸發KVO(原因看KVO的本質)

4、object_getClass(self.person) 和 [self.person class];分別打印什么?為什么?
- object_getClass(self.person); -> NSKVONotifying_MJPerson
- [self.person class];          -> MJPerson

- 原因:NSKVONotifying_MJPerson重寫底層實現,目的:隱藏動態創建的類,不讓用戶感知
- (Class)class {
    return [MJPerson class];
}

// 偽代碼 Function框架
void _NSSetIntValueForKey(){
    [self willChangeValueForKey:@"age"];
    [self setAge:age];
    [self didChangeValueForKey:@"age"];
}

// 通知監聽器
- (void)didChangeValueForKey:(NSString *)key {
   [obser observeValueForKeyPath:key ofObject:self change:nil content:nil];
}

其他:
根據地址打印方法:p (IMP)0X1065....
類對象:  object_getClass(self.person);
原類對象:object_getClass(object_getClass(self.person));

  • 使用kvo什么時候移除監聽(dealloc不能移除的情況)?

八、KVC

30、KVC相關

KVC的全稱是Key-Value Coding,俗稱“鍵值編碼”,可以通過一個key來訪問某個屬性

1、通過KVC修改屬性會出發KVO么?
- 能觸發KVO()
- KVC在修改屬性時,會調用willChangeValueForKey:和didChangeValueForKey:方法;

2、KVC的賦值和取值過程是怎樣的?原理是什么?
- 見下圖

3、使用場景
- 單層字典模型轉化:[self.model setValuesForKeysWithDictionary:dict];

- 通過KVC修改未暴露的屬性:
UILabel *placeholderLabel=[self.userTextField valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor redColor];

- 使用valueForKeyPath可以獲取數組中的最小值、最大值、平均值、求和
CGFloat sum = [[array valueForKeyPath:@"@sum.floatValue"] floatValue];
CGFloat avg = [[array valueForKeyPath:@"@avg.floatValue"] floatValue];
CGFloat max =[[array valueForKeyPath:@"@max.floatValue"] floatValue];
CGFloat min =[[array valueForKeyPath:@"@min.floatValue"] floatValue];

- 數組內部去重
[dataArray valueForKeyPath:@"@distinctUnionOfObjects.self"]

- 數組合并(去重合并:distinctUnionOfArrays.self、直接合并:unionOfArrays.self)
NSArray *temp1 = @[@3, @2, @2, @1];
NSArray *temp2 = @[@3, @4, @5];

NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@distinctUnionOfArrays.self"]);
NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@unionOfArrays.self"]);

輸出兩個數組:( 5, 1, 2, 3, 4 ), ( 3, 2, 2, 1, 3, 4, 5 )。

- 大小寫轉換(uppercaseString)及 打印字符串長度同樣適用(length)
NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [array valueForKeyPath:@"uppercaseString"]);
打?。?(NAME,W,AA,JIMSA)

image
- 首先會按照setKey、_setKey的順序查找方法,找到方法,直接調用方法并賦值;
- 未找到方法,則調用+ (BOOL)accessInstanceVariablesDirectly;
- 若accessInstanceVariablesDirectly方法返回YES,則按照_key、_isKey、key、isKey的順序查找成員變量,找到直接賦值,找不到則拋出異常;
- 若accessInstanceVariablesDirectly方法返回NO,則直接拋出異常;

image
- 首先會按照getKey、key、isKey、_key的順序查找方法,找到直接調用取值
- 若未找到,則查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,則直接拋出異常;
- 若返回的YES,則按照_key、_isKey、key、isKey的順序查找成員變量,找到則取值;
- 找不到則拋出異常;


九、Category

31、Category相關
1、Category的使用場合是什么?
- 將一個類拆成很多模塊(其實就是解耦,將相關的功能放到一起)

2、說說Category的實現原理
- 通過runtime動態將分類的方法合并到類對象、元類對象中
- Category編譯之后的底層結構是 struct_category_t , 里面存儲著分類的對象方法、類方法、屬性、協議信息
- 在程序運行的時候,runtime會將 Category 的數據,合并到類信息中(類對象、元類對象)

3、category和extension區別
- Extension在編譯時,就把信息合并到類信息中
- Category是在運行時,才會將分類信息合并到類信息中
- 分類聲明的屬性,只會生成getter/setter方法聲明,不會自動生成成員變量和getter/setter方法實現,而擴展會
- 分類不可用為類添加實例變量,而擴展可以

分類的局限性:
- 無法為類添加實例變量,但可通過關聯對象進行實現
- 分類的方法如果和類重名,會覆蓋原來方法的實現
- 多個分類的方法重名,會調用最后編譯的那個分類的實現

4、為什么category不能添加屬性?使用Runtime就可以了?
- 分類沒有自己的isa指針
- 類最開始生成了很多基本屬性,比如IvarList,MethodList
- 分類只會將自己的method attach到主類,并不會影響到主類的IvarList
- 實例變量沒有setter和getter方法。也沒有自己的isa指針

- 關聯對象都由AssociationsManager管理
- AssociationsManager里面是由一個靜態AssociationsHashMap來存儲所有的關聯對象的。
- 相當于把所有對象的關聯對象都存在一個全局map里面。而map的的key是這個對象的指針地址
- 而這個map的value又是另外一個AssAssociationsHashMap,里面保存了關聯對象的kv對

5、Category中有load方法么?load方法什么時候調用的?load方法能繼承么?
- 有
- +load方法會在runtime加載類、分類時調用;
- 每個類、分類的+load,在程序運行過程中只調用一次

- 調用順序
- 先調用類的+load,(按照編譯先后順序,先編譯,先調用),調用子類的+load之前會調用父類的+load
- 再調用分類的+load按照編譯先后順序調用(先編譯,先調用)

6、test方法和load方法的本質區別?(+load方法為什么不會被覆蓋)
- test方法是通過消息機制調用 objc_msgSend([MJPerson class], @selector(test))
- +load方法調用,直接找到內存中的地址,進行方法調用

7、load調用順序
- +load方法會在runtime加載類、分類時調用
- 每個類、分類的+load,在程序運行過程中只調用一次

調用順序
- 先調用類的+load方法,之后按照編譯先后順序調用(先編譯,先調用,調用子類的+load之前會先調用父類的+load)
- 再調用分類的+load,之后按照編譯先后順序調用(先編譯,先調用)

8、不同Category中存在同一個方法,會執行哪個方法?如果是兩個都執行,執行順序是什么樣的?
- 根據Build Phases->Compile Sources中添加的文件順序,后面的會覆蓋前面的

9、load、initialize方法的區別是什么?它們在category中的調用順序?以及出現繼承時他們之間的調用過程?
區別:
調用方式不同
- load是根據函數地址直接調用
- initialize是榮光objc_msgSend調用

調用時刻
- load是runtime加載 類/分類 的時候調用(只會調用1次)
- initialize是類第一次接收消息時調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次)

調用順序
- load:先調用類的load。先編譯的類,優先調用load(調用子類的load之前,會先調用父類的load)
- 再調用分類的load(先編譯的分類,優先調用load)
- initialize:先初始化父類,  再初始化子類(可能最終調用的是父類的initialize方法)

10、??:
- category的方法沒有“完全替換掉”原來類已經有的方法,也就是說如果category和原來類都有methodA,那么category附加完成之后,類的方法列表里會有兩個methodA
- category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面
- 這也就是我們平常所說的category的方法會“覆蓋”掉原來類的同名方法,這是因為運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法,就會罷休_,殊不知后面可能還有一樣名字的方法。

11、為什么不能動態添加成員變量?
- 方法和屬性并不“屬于”類實例,而成員變量“屬于”類實例
- “類實例”概念,指的是一塊內存區域,包含了isa指針和所有的成員變量。
- 假如允許動態修改類成員變量布局,已經創建出的類實例就不符合類定義了,變成了無效對象。但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響類實例的內存布局,已經創建出的類實例仍然可正常使用


十、網絡

32、TCP、UDP各自的優缺點及區別
TCP優點:( 可靠,穩定)
- 在傳遞數據之前,會有三次握手來建立連接,
- 在數據傳遞時,有確認、窗口、重傳、擁塞控制機制,
- 在數據傳完后,還會斷開連接用來節約系統資源

TCP缺點:(慢,效率低,占用系統資源高)
- TCP在傳遞數據之前,要先建連接,這會消耗時間
- 在數據傳遞時(確認機制、重傳機制、擁塞控制機制)等都會消耗大量的時間
- 因為TCP有確認機制、三次握手機制,這些也導致TCP容易被人利用,實現DOS、DDOS、CC等攻擊

UDP的優點:(快)
- UDP沒有TCP的握手、確認、窗口、重傳、擁塞控制等機制
- UDP是一個無狀態的傳輸協議,所以它在傳遞數據時非常快

UDP的缺點:(不可靠,不穩定)
- 因為UDP沒有TCP那些可靠的機制,在數據傳遞時,如果網絡質量不好,就會很容易丟包

小結TCP與UDP的區別:
- TCP面向連接(如打電話要先撥號建立連接); UDP是無連接的,即發送數據
- TCP提供可靠的服務。通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付
- TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流; UDP是面向報文的
- 每一條TCP連接只能是點到點的; UDP支持一對一,一對多,多對一和多對多的交互通信

33、Scoket連接和HTTP連接的區別
- HTTP協議是基于TCP連接的,是應用層協議,主要解決如何包裝數據。Socket是對TCP/IP協議的封裝,Socket本身并不是協議,而是一個調用接口(API),通過Socket,我們才能使用TCP/IP協議。
- HTTP連接:短連接,客戶端向服務器發送一次請求,服務器響應后連接斷開,節省資源。服務器不能主動給客戶端響應,iPhone主要使用類NSURLConnection
- Socket連接:長連接,客戶端跟服務器端直接使用Socket進行連接,沒有規定連接后斷開,因此客戶端和服務器段保持連接通道,雙方可以主動發送數據

34、HTTP協議的特點,關于HTTP請求GET和POST的區別
特點:
- HTTP超文本傳輸協議,是短連接,是客戶端主動發送請求,服務器做出響應,服務器響應之后,鏈接斷開
- HTTP是一個屬于應用層面向對象的協議,HTTP有兩類報文:請求報文和響應報文
- HTTP請求報文:一個HTTP請求報文由請求行、請求頭部、空行和請求數據4部分組成
- HTTP響應報文:由三部分組成:狀態行、消息報頭、響應正文

GET請求
- 參數在地址后拼接,不安全(因為所有參數都拼接在地址后面)
- 不適合傳輸大量數據(長度有限制,為1024個字節)

POST請求
- 參數在請求數據區放著,相對GET請求更安全
- 數據大小理論上沒有限制
- 提交的數據放置在HTTP包的包體中

35、斷點續傳怎么實現的?
- 斷點續傳主要依賴于 HTTP 頭部定義的 Range 來完成
- 有了 Range,應用可以通過 HTTP 請求獲取失敗的資源,從而來恢復下載該資源
- 當然并不是所有的服務器都支持 Range,但大多數服務器是可以的。Range 是以字節計算的,請求的時候不必給出結尾字節數,因為請求方并不一定知道資源的大小

36、網絡層相關面試

- 網絡七層協議

- Charles原理

- HTTP和HTTPS的區別?Https為什么更加安全?

- HTTPS的連接建立流程

- 解釋一下三次握手和四次揮手

- TCP分片 和 IP分片

- Cookie和Session
[網絡相關之Cookie和Session](http://www.lxweimin.com/p/5f250c621e81?utm_campaign=hugo)

37、DNS是什么?DNS解析過程

域名系統(Domain Name System,DNS)
因特網上的主機,可以使用多種方式標識:

1、區別:
- 主機名:方便人們記憶和接受,但長度不一、沒有規律的字符串,路由器并不方便處理
- IP地址:路由器方便處理,不便于人們記憶

為了折衷這兩種方式,需要一種能進行主機名到IP地址轉換的目錄服務,就是 域名系統(Domain Name System,DNS)

2、作用:
- 將用戶提供的主機名解析為IP地址

3、DNS解析過程(以www.163.com為例:)
- 打開瀏覽器,輸入一個域名(www.163.com)??蛻舳藭l出一個DNS請求到本地DNS服務器(本地DNS服務器一般都是你的網絡接入服務器商提供,比如中國電信,中國移動)
- 本地DNS服務器會首先查詢它的緩存記錄,如果緩存中有此條記錄,直接返回結果。如果沒有,向DNS根服務器進行查詢。
- 根DNS服務器沒有記錄具體的域名和IP地址的對應關系,而是給出域服務器的地址,告訴他可以到域服務器上去繼續查詢
- 本地DNS服務器繼續向域服務器發出請求,在這個例子中,請求的對象是.com域服務器。
- .com域服務器收到請求之后,也不會直接返回域名和IP地址的對應關系,而是告訴本地DNS服務器,你的域名的解析服務器的地址。
- 最后,本地DNS服務器向域名的解析服務器發出請求,這時就能收到一個域名和IP地址對應關系,
- 本地DNS服務器不僅要把IP地址返回給用戶電腦,還要把這個對應關系保存在緩存中,以備下次別的用戶查詢時,可以直接返回結果,加快網絡訪問。

過程:本地服務器->根服務器->域服務器->域名解析服務器
- 整合成流程圖
TODO(待填充);??????????

十一、UI

41、Storyboard/Xib和純代碼UI相比,有哪些優缺點?
storyboard/xib優點
- 簡單直接。直接通過拖拽和點選即可完成配置。
- 跳轉關系清楚

缺點:
- 協作沖突(多人提交代碼)
- 很難做到頁面繼承和重用
- 不便于進行模塊化管理
- 影響性能(多圖層渲染)

42、自動布局AutoLayout原理,性能如何
戴明-iOS開發高手課 - 03

43、說明比較方法:layoutifNeeded、layoutSubviews、setNeedsLayout
44、如果頁面 A 跳轉到 頁面 B,A 的 viewDidDisappear 方法和 B 的 viewDidAppear 方法哪個先調用?
- A -->viewWillDisappear
- B-->viewWillAppear
- A-->viewDidDisappear
- B-->viewDidAppear

45、離屏渲染,隱式動畫和顯式動畫相關
??經??吹剑瑘A角會觸發離屏渲染。但其實這個說法是不準確的,因為圓角觸發離屏渲染也是有條件的!

1、離屏渲染觸發條件:
- 背景色、邊框、背景色+邊框,再加上圓角+裁剪,因為 contents = nil 沒有需要裁剪處理的內容,所以不會造成離屏渲染。
- 一旦為contents設置了內容,無論是圖片、繪制內容、有圖像信息的子視圖等,再加上圓角+裁剪,就會觸發離屏渲染。

2、在一個表內有很多cell,每個cell上有很多個視圖,如何解決卡頓問題?
3、切圓角一定會觸發離屏渲染嗎?

4、iOS 9及之后的系統版本,蘋果進行了一些優化
- 只設置contents或者UIImageView的image,并加上圓角+裁剪,是不會產生離屏渲染的。
- 但如果加上了背景色、邊框或其他有圖像內容的圖層,還是會產生離屏渲染。
- 使用類似于UIButton的視圖的時候需要注意

46、frame和bouns的區別。什么時候frame和bouns的高寬不相等
旋轉后怎么樣

47、事件響應過程(響應鏈)
1、事件的傳遞 (尋找最合適的view的過程)
- 當一個事件發生后,事件會從父控件傳給子控件 (UIApplication->UIWindow->UIView->initial view)

2、事件的響應
- 首先看initial view能否處理這個事件,如果不能則會將事件傳遞給其上級視圖(inital view的superView)
- 如果上級視圖仍然無法處理則會繼續往上傳遞;一直傳遞到視圖控制器view controller,首先判斷視圖控制器的根視圖view是否能處理此事件
- 如果不能則接著判斷該視圖控制器能否處理此事件,如果還是不能則繼續向上傳遞
- 一直到window,如果window還是不能處理此事件則繼續交給application處理,如果最后application還是不能處理此事件則將其丟棄

3、??注意
- 事件的傳遞是從上到下(父控件到子控件)
- 事件的響應是從下到上(順著響應者鏈條向上傳遞:子控件到父控件)

4、重要方法:
4.1、hitTest:withEvent:
- 只要事件一傳遞給一個控件,這個控件就會調用他自己的hitTest:withEvent:方法
- 尋找并返回最合適的view(能夠響應事件的那個最合適的view)

- (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]) {
        return nil;
    }
    // 3.從后往前遍歷自己的子控件,看是否有子控件更適合響應此事件
    for(NSInteger i = self.subviews.count; i >= 0; i --) {
        UIView *childView = self.subviews[i];
        CGPoint childPoint = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childPoint withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    // 沒有找到比自己更合適的view
    return self;
}

4.2、pointInside:withEvent:
- 判斷點在不在當前view上(方法調用者的坐標系上)
- 如果返回YES,代表點在方法調用者的坐標系上;
- 返回NO代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件。

5、穿透
- 假設有一個黃色控件和白色控件,白色空間覆蓋在黃色控件上
- 點擊白色view想要黃色view來響應該事件,就是所謂的穿透

方法一、
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint yellowPoint = [self convertPoint:point toView:_yellowView];
    if ([_yellowView pointInside:yellowPoint withEvent:event]) {
        return _yellowView;
    }
    return [super hitTest:point withEvent:event];
}

方法二、
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint yellowPoint =[_yellowView convertPoint:point fromView:self];
    if ([_yellowView pointInside:yellowPoint withEvent:event]){
        return NO;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

下一章:百度三面被掛掉之后,沉下心來總結,構建自己的iOS開發體系(下)

  • 最新 初級iOS 面試題
  • 最新 中級iOS 面試題
  • 最新 高級iOS 面試題

  • 《BAT面試資料全集》
  • 《BAT大廠常問iOS面試題》
  • 《2021年面試真題》
  • 《iOS開發面試題200道-面試問答篇》
  • 《iOS開發筆試題600道-筆試手寫篇》
  • 《iOS中級到高級面試題完整版》

下載地址:

地址:https://docs.qq.com/doc/DVU5LY1Bsb3dSZ3Nn

小編文章請觀看合集

文章來源作者:強子ly

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容