iOS底層原理總結(jié) -- iOS面試題

總結(jié)一些iOS的底層面試題。鞏固一下iOS的相關(guān)基礎(chǔ)知識(shí)。

如有出入,還望各位大神指出。

OC對(duì)象

1. NSObject對(duì)象的本質(zhì)是什么?

  • NSObject對(duì)象的本質(zhì)就是結(jié)構(gòu)體

2. 一個(gè)NSObject對(duì)象占用多少內(nèi)存?

  • NSObject對(duì)象創(chuàng)建實(shí)例對(duì)象的時(shí)候系統(tǒng)分配了16個(gè)內(nèi)存(通過malloc_size函數(shù)可獲得)

  • 但是 NSObject只使用了8個(gè)字節(jié) 使用(class_getinstanceSize可獲得)

3. 對(duì)象的isa指針指向哪里?

  • instance對(duì)象的isa指針指向class對(duì)象

  • class對(duì)象的isa指針指向 meta-class對(duì)象

  • meta-class對(duì)象的isa指針基類的meta-class對(duì)象

  • isa的優(yōu)化

    • isa在arm64構(gòu)架之前 isa的值就類對(duì)象的地址值。

    • isa在arm64構(gòu)架開始的時(shí)候 采用了 isa優(yōu)化的策略, 使用了共用體的技術(shù)。將64位的內(nèi)存地址存儲(chǔ)了很多東西,其中33位存儲(chǔ)的是isa具體的地址值的。因?yàn)楣灿皿w中 前三位有存儲(chǔ)的東西(),所以在&isa_mask出來的類對(duì)象地址值的二進(jìn)制后面三位永遠(yuǎn)都是000, 十六進(jìn)制就是8 或者0結(jié)尾的地址值

       union isa_t 
      {
          isa_t() { }
          isa_t(uintptr_t value) : bits(value) { }
          Class cls;
          uintptr_t bits;   ///>  typedef unsigned long 
            struct {
                ///> 0 代表普通指針,存儲(chǔ)著Class、MetaClass對(duì)象的內(nèi)存地址
              ///> 1 代表優(yōu)化過,使用位域存儲(chǔ)更多信息
              uintptr_t nonpointer        : 1; 
                ///> 是否有設(shè)置過關(guān)聯(lián)對(duì)象,如果沒有,釋放時(shí)會(huì)更快
              uintptr_t has_assoc         : 1;
                ///> 是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放會(huì)更快
              uintptr_t has_cxx_dtor      : 1;
              ///> 存儲(chǔ)著Class、MetaClass的內(nèi)存地址
              uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
              ///> 用于在調(diào)試時(shí)分辨率是否未完成初始化
              uintptr_t magic             : 6;
              ///> 是否被弱指針指向過? 如果沒有,釋放會(huì)更快
              uintptr_t weakly_referenced : 1;
              ///> 對(duì)象是否正在釋放
              uintptr_t deallocating      : 1;
                    ///> 引用計(jì)數(shù)器是否過大?無法存儲(chǔ)在isa中
                ///> 如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫 side table的類屬性中   
              uintptr_t has_sidetable_rc  : 1;
                      ///> 里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
              uintptr_t extra_rc          : 19;
          };
         ...
       }
      

4. OC類的信息存儲(chǔ)在哪里?

  • meta-class存儲(chǔ):類方法
  • class對(duì)象存儲(chǔ): 對(duì)象方法,屬性,成員變量,協(xié)議信息
  • instance存儲(chǔ): 成員變量具體的值

5. 說說你對(duì)函數(shù)調(diào)用的理解吧。

  • 函數(shù)調(diào)用 實(shí)際實(shí)際上就是 給對(duì)象發(fā)送一條消息
  • objc_msgSend(對(duì)象, @selectir(對(duì)象方法))
  • 尋找順序(對(duì)象方法) instance的isa指針找到類對(duì)象 在類對(duì)象中尋找方法,若沒有向superClass中查找。
  • 尋找順序(類方法) instance的isa指針找到類對(duì)象 --> 類對(duì)象的isa找到meta-calss --> 在meta-class對(duì)象中尋找類方法,若沒有向superClass中查找。

KVO

1. iOS用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?(KVO的本質(zhì)是什么?)

  • 利用Runtime API 動(dòng)態(tài)生成了一個(gè)新的類, 并且instance對(duì)象的isa指針指向這個(gè)生成的新子類。

  • 當(dāng)修改instance對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的_NSSetXXXValueForKey函數(shù)

    • willChangeValueForKey
    • 父類原來的set方法
    • didChangeValueForKey
    • didChangeValueForKey 內(nèi)部會(huì)觸發(fā)observerValueForKeyPath方法 實(shí)現(xiàn)監(jiān)聽。
  • 輕量級(jí)KVO框架:GitHub - facebook/KVOController

KVC

1. 使用KVC會(huì)不會(huì)調(diào)用KVO?

  • 會(huì)調(diào)用KVO, 因?yàn)樗膬?nèi)部使用了:
    • willChangeValueForKey
    • 直接去_方式去更改
    • didChangeValueForKey
    • didChangeValueForKey 內(nèi)部會(huì)觸發(fā)

2. KVC的賦值和取值過程是怎么樣的?原理是什么?

  • 賦值
    • setKey、_setKey 順序查找方法
      • 有: 傳遞參數(shù)調(diào)用防范
      • 沒有: 查看accessInstanceVariablesDirectly 方法返回值 yes 繼續(xù)查找
    • _key、_iskey、key、iskey 順序查找

Category:

1. Category的使用場合是什么?

2. Category中的屬性是否也存在類對(duì)象中?如果存在是怎么生成和存在的?如果不存在,它存在的位置在哪里?

  • 一個(gè)類永遠(yuǎn)只有一個(gè)類對(duì)象
  • 在運(yùn)行起來之后 最重都會(huì)合并在 類對(duì)象中去。

3. Category的使用原理是什么?實(shí)現(xiàn)過程

  • 原理:底層結(jié)構(gòu)是結(jié)構(gòu)體 categoty_t 創(chuàng)建好分類之后分兩個(gè)階段:

    1. 編譯階段:

      將每一個(gè)分類都生成所對(duì)應(yīng)的 category_t結(jié)構(gòu)體, 結(jié)構(gòu)體中存放 分類的所屬類name、class、對(duì)象方法列表、類方法列表、協(xié)議列表、屬性列表。

    2. Runtime運(yùn)行時(shí)階段:

      將生成的分類數(shù)據(jù)合并到原始的類中去,某個(gè)類的分類數(shù)據(jù)會(huì)在合并到一個(gè)大的數(shù)組當(dāng)中(后參與編譯的分類會(huì)在數(shù)組的前面),分類的方法列表,屬性列表,協(xié)議列表等都放在二維數(shù)組當(dāng)中,然后重新組織類中的方法,將每一個(gè)分類對(duì)應(yīng)的列表的合并到原始類的列表中。(合并前會(huì)根據(jù)二維數(shù)組的數(shù)量擴(kuò)充原始類的列表,然后將分類的列表放入前面)

  • 調(diào)用順序

    • 為什么Category的中的方法會(huì)優(yōu)先調(diào)用?

      如上所述, 在擴(kuò)充數(shù)組的時(shí)候 會(huì)將原始類中擁有的方法列表移動(dòng)到后面, 將分類的方法列表數(shù)據(jù)放在前面,所以分類的數(shù)據(jù)會(huì)優(yōu)先調(diào)用

    • 延伸問題 - 如果多個(gè)分類中都實(shí)現(xiàn)了同一個(gè)方法,那么在調(diào)用該方法的時(shí)候會(huì)優(yōu)先調(diào)用哪一個(gè)方法?

      在多個(gè)分類中擁有相同的方法的時(shí)候, 會(huì)根據(jù)編譯的先后順序 來添加分類方法列表, 后編譯的分類方法在最前面,所以要看 Build Phases --> compile Sources中的順序。 后參加編譯的在前面。

4. Category和Extension的區(qū)別是什么?

  • Category 在運(yùn)行的時(shí)候才將數(shù)據(jù)合并到類信息中
  • Extension 在編譯的時(shí)候就將數(shù)據(jù)包含在類信息中了 @interface Xxxx() 也叫做匿名分類

5. Category中有l(wèi)oad方法嗎?load是什么時(shí)候調(diào)用的?

  • 有l(wèi)oad方法
  • load什么時(shí)候調(diào)用
    • load方法在runtime加載類、分類的時(shí)候調(diào)用

6. load、initialize方法的區(qū)別是什么?它們?cè)贑ategory中的調(diào)用順序?以及出現(xiàn)繼承時(shí)他們之間的調(diào)用過程?當(dāng)一個(gè)類有分類的時(shí)候?yàn)槭裁?load能多次調(diào)用兒initialize值調(diào)用了一次?

  • 調(diào)用方式:

    • load 根據(jù)函數(shù)地址直接調(diào)用
    • initialize 是通過 objc_msgSend調(diào)用
  • 調(diào)用時(shí)刻:

    • load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次)
    • initialize 是類第一次接收到消息的時(shí)候調(diào)用objc_msgsend()方法 如alloc、每一個(gè)類只會(huì)調(diào)用1次(但是父類的initialize方法可能會(huì)調(diào)用多次) 有些子類沒有initialize方法所以調(diào)用父類的。
  • 調(diào)用順序:

    • load:
      • 先調(diào)用類的+load方法
        • 按照編譯的先后順序調(diào)用(先編譯、先調(diào)用)
        • 調(diào)用子類的+load方法之前會(huì)先調(diào)用父類的+load
      • 再調(diào)用分類的+load方法
        • 按照編譯的先后順序調(diào)用(先編譯、先調(diào)用)
    • initialize
      • 初始化父類
      • 在初始化子類(可能最終調(diào)用的是父類的initialize方法)
  • load方法可以繼承 我們?cè)谧宇悰]有實(shí)現(xiàn)的時(shí)候可以調(diào)用,但是一般都是類自動(dòng)去調(diào)用,我們不會(huì)主動(dòng)調(diào)用,當(dāng)子類沒有實(shí)現(xiàn)+load方法的時(shí)候不會(huì)不會(huì)自動(dòng)調(diào)用了就

    <img src="/Users/yuangonmg/Library/Application Support/typora-user-images/image-20191112191237086.png" alt="image-20191112191237086" style="zoom:25%;" />

  • 當(dāng)一個(gè)類有分類的時(shí)候?yàn)槭裁?load能多次調(diào)用兒initialize值調(diào)用了一次?

    • 根據(jù)源碼看出來,+load 直接通過函數(shù)指針指向函數(shù),拿到函數(shù)地址,找到函數(shù)代碼,直接調(diào)用 分開來直接調(diào)用的 不是通過objc_msgsend調(diào)用的
    • 而 initialize是通過消息發(fā)送機(jī)制,isa找到類對(duì)象找到方法調(diào)用的 所以只調(diào)用一次

7. Category能否添加成員變量?如果可以,如何給Category添加成員變量?

  • 不能直接給Category添加成員變量,但是可以間接添加。

    • 使用一個(gè)全局的字典 (缺點(diǎn): 每一個(gè)屬相都需要一套相同的代碼)
     ///> DLPerson+Test.h
     @interface DLPerson (Test)
     ///> 如果直接使用 @property 只會(huì)生成方法的聲名 不會(huì)生成成員變量和set、get方法的實(shí)現(xiàn)。
     @property (nonatomic, assign) int weigjt;
     @end
     
     
     ///> DLPerson+Test.m
    #import "DLPerson+Test.h"
    @implemention DLPerson (Test)
    NSMutableDictionary weights_;
    + (void)load{
    weights_ = [NSMutableDictionary alloc]init];
    }
    
    - (void)setWeight:(int)weight{
       NSString *key = [NSString stringWithFormat:@"%p",self];
       weights_[key] = @(weight);
    
    }
    - (int)weight{
       NSString *key = [NSString stringWithFormat:@"%p",self];
       return [weights_[key] intValue] 
    }
    
    @end
    
    
  • 使用runtime機(jī)制給分類添加屬性

```objc
#import<objc/runtime.h>

const void *DLNameKey = &DLNameKey
///> 添加關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(
id object,          ///>  給哪一個(gè)對(duì)象添加關(guān)聯(lián)對(duì)象
const void * key,   ///>   指針(賦值取值的key)  &DLNameKey
id value,           ///>  關(guān)聯(lián)的值
objc_AssociationPolicy policy ///>  關(guān)聯(lián)策略 下方表格
)

eg : objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);


///> 獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(
id object,           ///>  哪一個(gè)對(duì)象的關(guān)聯(lián)對(duì)象
const void * key     ///>   指針(賦值取值的key) 
)
eg:
objc_getAssociatedObject(self,@selector(name));
/// _cmd  == @selector(name); 
objc_getAssociatedObject(self,_cmd);

///> 移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(
id object       ///>
)
  - objc_AssociationPolicy(關(guān)聯(lián)策略)

|objc_AssociationPolicy(關(guān)聯(lián)策略) |對(duì)應(yīng)的修飾符|
|:---|:---|:---|:---|
|OBJC_ASSOCIATION_ASSIGN | assign  |
|OBJC_ASSOCIATION_RETAIN_NONATOMIC |  strong, nonatomic | 
|OBJC_ASSOCIATION_COPY_NONATOMIC |  copy, nonatomic | 
|OBJC_ASSOCIATION_RETAIN |  strong, atomic | 
|OBJC_ASSOCIATION_COPY |  copy, atomic | 


Block

1. block的原理是怎樣的?本質(zhì)是什么

  • block的本質(zhì)就是一個(gè)oc對(duì)象 內(nèi)部也有isa指針, 封裝了函數(shù)及調(diào)用環(huán)境的OC對(duì)象,

2. 看代碼解釋原因

  int main(int argc, const char *argv[]){
    @autoreleasepool{
      int age = 10;
      void  (^block)(void) = ^{
          NSLog(@" age is %d ",age);
      };
      age = 20;
      block();
    }
  }
  /*
  輸出結(jié)果為? 為什么?
  輸出結(jié)果是: 10
  如果沒有修飾符  默認(rèn)是auto
  為了能訪問外部的變量 
  block有一個(gè)變量捕獲的機(jī)制 
  因?yàn)樗蔷植孔兞?并且沒有用static修飾 
  所以它被捕獲到block中是 一個(gè)值,外部再次改變時(shí) block中的age不會(huì)改變。
  */
變量類型 捕獲到Block內(nèi)部 訪問方式
局部變量 auto ? 值傳遞
局部變量 static ? 指針傳遞
全局變量 ? 直接訪問
int main(int argc, const char *argv[]){
  @autoreleasepool{
    int age = 10;
    static int height = 10;
    void  (^block)(void) = ^{
        NSLog(@" age is %d, height is %d",age, height);
    };
    age = 20;
    height = 20;
    block();
  }
}
/*
輸出結(jié)果為? 為什么?
age is 10, height is 20
局部變量用static 修飾之后 捕獲到block中的是 height的指針,
因此修改通過指針修改變量之后 外部的變量也被修改了
*/
int age = 10;
static int height = 10;
int main(int argc, const char *argv[]){
  @autoreleasepool{
    
    void  (^block)(void) = ^{
        NSLog(@" age is %d, height is %d",age, height);
    };
    age = 20;
    height = 20;
    block();
  }
}
/*
輸出結(jié)果為? 為什么?
age is 20, height is 20
 因?yàn)?age 和 height是全局變量不需要捕獲直接就可以修改
 
 全局變量 對(duì)應(yīng)該就可以訪問,
 局部變量 需要跨函數(shù)訪問,所以需要捕獲
因此修改通過指針修改變量之后 外部的變量也被修改了
*/

int main(int argc, const char *argv[]){
  @autoreleasepool{
    
    void  (^block)(void) = ^{
        NSLog(@" self %p",self);
    };
    block();
  }
}
/*
self 會(huì)不會(huì)被捕獲?
因?yàn)楹瘮?shù)默認(rèn)會(huì)有兩個(gè)參數(shù) void test(DLPerson *self, SEL _cmd)
所以self 也是一個(gè)局部變量

訪問@property(nonatmic, copy) NSString *name;
因?yàn)樗浅蓡T變量, 訪問的時(shí)候 用self.name 或者 self->_name 訪問  所以 block在內(nèi)部會(huì)捕獲self。
*/

3. 既然block是一個(gè)OC對(duì)象,那么block的對(duì)象類型是什么?

  • ios內(nèi)存分為5發(fā)區(qū)域
    • 堆: 動(dòng)態(tài)分配內(nèi)存,需要程序員申請(qǐng),也需要程序員自己管理(alloc、malloc等...)
    • 棧: 放一些局部變量,臨時(shí)變量 系統(tǒng)自己管理
    • 靜態(tài)區(qū)(全局區(qū)): 存放全局的靜態(tài)對(duì)象。(編譯時(shí)分配,APP結(jié)束由系統(tǒng)釋放)
    • 常量區(qū): 常量。(編譯時(shí)分配,APP結(jié)束由系統(tǒng)釋放)
    • 代碼區(qū): 程序區(qū)
  • block有三種類型,最終都繼承自NSBlock類型

    • superClass
      NSGlobalBlock : __NSGlobalBlock : NSBlock : NSObject
    block類型 環(huán)境 存放位置
    NSGlobalBlock 沒有訪問auto變量 靜態(tài)區(qū)
    NSStackBlock 訪問了auto變量
    NSMallocBlock NSStackBlock調(diào)用了copy
  • NSStackBlock調(diào)用了copy 代碼實(shí)例

    void (^block)(void);
    void (^block1)(void);
    void test2(){
        int age = 10;
        block = ^{
            NSLog("age is %d",age);
          /*
          因?yàn)?block訪問了 auto變量 
          所以目前block的類型為NSStackBlock類型,
          存放的位置在  棧  上 
          在 main訪問是  這個(gè)block已經(jīng)被釋放了。
          */
        };
        
       [block1 = ^{
            NSLog("age is %d",age);
            /*  
            因?yàn)?block訪問了 auto變量 
            并且 進(jìn)行了copy操作
            所以目前block的類型為 NSMallocBlock 類型 
            存放的位置在  堆  上 
            在 main訪問是  這個(gè)block是可以被訪問的。
          */
        } copy];
    }
    
    int main(int argc, const char *argv[]){
    
        @autoreleasepool{
            test2();
            
            block(); /// 輸出的值為 很大不是想要的值
            
            block1();/// 輸出的值是10
        }
    }
    
  • 每一種類型的block調(diào)用了copy之后結(jié)果如下所示

    block的類型 副本源的配置存儲(chǔ)域 復(fù)制后的區(qū)域
    NSGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么都不做
    NSStackBlock 從棧復(fù)制到堆
    NSMallocBlock 引用計(jì)數(shù)器+1

4. 在什么情況下 ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上?

  • block作為函數(shù)的返回值的時(shí)候

  • 將block賦值給__strong指針時(shí)

  • block作為 Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)

    NSArray *arr = @[];
    /// 遍歷數(shù)組中包含  usingBlock方法的參數(shù)
    [arr enumerateObjectUsingBlock:^(id _Nonnullobj, NSUInteger idx, Bool _Nonnull stop){
    
    }] ;
    
  • block作為GCD屬性的建議寫法

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
    });
    
    disPatch_after(disPatch_time(IDSPATCH_TIME_NOW, (int64_t)(delayInSecounds *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    });
    
  • MRC下block屬性建議寫法

    • @property (copy, nonatomic) void (^block)(void);
  • ARC下block屬性建議寫法

    • @property (strong, nonatomic) void (^block)(void);
    • @property (copy, nonatomic) void (^block)(void);

5. __weak的作用是什么?有什么使用注意點(diǎn)?

  • __weak 是一個(gè)修飾符
  • 當(dāng)block內(nèi)部訪問的對(duì)象類型的auto變量
    • 如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用
    • 如果Block被拷貝到堆上
      • 會(huì)調(diào)用block內(nèi)部的copy函數(shù)
      • copy函數(shù)會(huì)調(diào)用源碼中的_Block_object_assign函數(shù)
      • _Block_object_assign函數(shù)會(huì)根據(jù)修飾 auto 變量的修飾符(__strong、__weak 、__unsafe_unretained)來決定作出相應(yīng)的操作,形成強(qiáng)引用或者弱引用
    • block從對(duì)上移除
      • 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
      • dispose函數(shù)會(huì)調(diào)用源碼中的 _Block_object_dispose函數(shù)
      • _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放auto變量(release)

6. __block的作用是什么?有什么使用注意點(diǎn)?

  • block為什么不能修改外部變量的值?

    • 因?yàn)閎lock也是一個(gè)函數(shù)調(diào)用(可以說他們是兩個(gè)函數(shù), block調(diào)用另一個(gè)函數(shù)的變量是不能調(diào)的)
    • 在C++內(nèi)部 block是調(diào)用的另一個(gè)函數(shù) 實(shí)現(xiàn)。
  • __block修飾之后會(huì)將變量包裝成一個(gè)對(duì)象 可以解決block內(nèi)部無法修改auto變量的問題

  • 包裝秤對(duì)象之后就可以通過指針修改 外部的變量了

  • 使用注意點(diǎn): 在MRC環(huán)境下不會(huì)對(duì)指向的對(duì)象產(chǎn)生強(qiáng)引用的

7. __block的屬性修飾詞是什么?為什么?使用block有哪些注意點(diǎn)?

  • 修飾詞是copy
  • block 如果沒有進(jìn)行copy操作就不會(huì)再堆上, 在堆上才能控制它的生命周期
  • 注意循環(huán)引用的問題
  • 在ARC環(huán)境下 使用呢strong和copy都可以沒有區(qū)別 在MRC環(huán)境下有區(qū)別
  • block是一個(gè)對(duì)象, 所以block理論上是可以retain/release的. 但是block在創(chuàng)建的時(shí)候它的內(nèi)存是默認(rèn)是分配在棧(stack)上, 而不是堆(heap)上的. 所以它的作用域僅限創(chuàng)建時(shí)候的當(dāng)前上下文(函數(shù), 方法...), 當(dāng)你在該作用域外調(diào)用該block時(shí), 程序就會(huì)崩潰.

8. block在修飾NSMutableArray,需不需要添加__block?

  • 不需要

    NSMutableArray *array = [[NSMutableView alloc]init]
    void (^block)(void) = ^{
      array = nil;   ///>  這樣操作是需要__block的。  ///> 
        
      ///> 下面這個(gè)是不需要 __block修飾的,因?yàn)檫@個(gè)只是使用它的指針而不是修改它的值
      [array addObject:@"aa"];
      [array addObject:@"aa"];
    }
    
    
  • 在修改NSMutableArray的數(shù)組的時(shí)候 并不是在修改這個(gè)數(shù)據(jù) 而是在使用這個(gè)指針去

Runtime

22. 講一下OC的消息機(jī)制

  • OC中的方法調(diào)用最后都是 objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
  • objc_msgSend底層有三大模塊
  • 消息發(fā)送(當(dāng)前類、父類中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)

23. 消息轉(zhuǎn)發(fā)機(jī)制流程

? 在objc_msgSend有三大階段

  • 消息發(fā)送階段
    • 給當(dāng)前類發(fā)送一條消息,
    • 會(huì)先從當(dāng)前的類中的緩存查找
    • 如果沒有去遍歷 class_rw_t 方法列表查找
    • 如果沒有再去父類的緩存查找
    • 如果沒有在去父類的class_rw_t方法列表中查找
    • 循環(huán)父類 如果找到調(diào)用方法, 并且將方法緩存到 方法調(diào)用者的方法緩存中
    • 如果一直沒有到下一個(gè)階段 動(dòng)態(tài)解析階段
  • 動(dòng)態(tài)解析階段
    • 動(dòng)態(tài)解析會(huì)調(diào)用-resolveInstanceMethod \ +resolveClassMethod 方法 在方法中手動(dòng)添加class_addMethod方法的調(diào)用。
    • 只會(huì)解析一次 會(huì)將是否解析過的參數(shù)置位YES
    • 然后在次 調(diào)用消息發(fā)送的階段
    • 如果我們實(shí)現(xiàn)了 方法的添加 則在消息發(fā)送階段可以找到這個(gè)方法
    • 調(diào)用方法并 將方法緩存到 方法調(diào)用者的緩存中
    • 如果沒有實(shí)現(xiàn), 在第二次走到動(dòng)態(tài)解析階段,不會(huì)進(jìn)入動(dòng)態(tài)解析,因?yàn)樯弦淮我呀?jīng)解析過了
    • 我們將動(dòng)態(tài)解析過的參數(shù)設(shè)置為YES,所以會(huì)走到下一個(gè)階段 消息轉(zhuǎn)發(fā)階段
  • 消息轉(zhuǎn)發(fā)階段
    • 第一種: 實(shí)現(xiàn)了forwardingTargetForSelector方法
      • 調(diào)用forwardingTargetForSelector 方法(返回一個(gè)類對(duì)象), 直接使用我們?cè)O(shè)置的類去發(fā)送消息。
    • 第二種: 沒有實(shí)現(xiàn)forwardingTargetForSelector
      • 回去調(diào)用 methodSignatureForSelector 方法,在這個(gè)方法添加方法簽名
      • 之后會(huì)調(diào)用forwardInvocation 方法, 在這個(gè)方法中我們 [anInvocation invokeWithTarget:類對(duì)象];
      • 或者其他操作都可以 這里沒有什么限制。

24. 什么是runtime? 平時(shí)項(xiàng)目中有用過嗎?

  • OC是一門動(dòng)態(tài)性比較強(qiáng)的語言,允許很多操作推遲到程序運(yùn)行時(shí)才進(jìn)行
  • OC的動(dòng)態(tài)性是由runtime來支撐實(shí)現(xiàn)的,runtime是一套C語言的API,封裝了許多動(dòng)態(tài)性相關(guān)的函數(shù)
  • 平時(shí)寫的代碼 底層都是轉(zhuǎn)換成了 runtime的API進(jìn)行調(diào)用的
  • 具體應(yīng)用
    • 關(guān)聯(lián)對(duì)象,給分類添加屬性,set和get的實(shí)現(xiàn)
    • 遍歷類的成員變量 歸檔解檔、字典轉(zhuǎn)模型
    • 交換方法(系統(tǒng)的交換方法)

26. iskindOfClass 和 isMemberOfClass的區(qū)別?

  • isMemberOfClass源碼:

      ///  返回的直接是 是否是當(dāng)前的類, 當(dāng) 象
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    ///  返回的直接是 是否是當(dāng)前的類, 
    /// 當(dāng)前元類對(duì)象
    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    } 
    
    
    
  • iskindOfClass源碼:

    /// for循環(huán)查找 , 會(huì)根據(jù)當(dāng)前類和 當(dāng)前類的父類去逐級(jí)查找 ,
    - (BOOL)isKindOfClass:(Class)cls {
      for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
          if (tcls == cls) return YES;
      }
      return NO;
    }
    
    ///   for循環(huán)查找 , 會(huì)根據(jù)當(dāng)前類和 當(dāng)前類的額父類去逐級(jí)查找 ,
    /// 當(dāng)前元類對(duì)象
     + (BOOL)isKindOfClass:(Class)cls {
      for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
          if (tcls == cls) return YES;
      }
      return NO;
    } 
    
  • 相關(guān)面試題:

    //        NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
    //        NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
    //        NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]);
    //        NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]);
          /// 上面的寫法 與 下面的寫法 相同
          
          // 這句代碼的方法調(diào)用者不管是哪個(gè)類(只要是NSObject體系下的、繼承于NSObject),都返回YES
          NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
          NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
          NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
          NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
    //        輸出的結(jié)果是什么?
    
    

Runloop

1. 講講Runloop片在項(xiàng)目中的應(yīng)用

Runloop

多線程

1. iOS中常見的多線程方案

技術(shù)方案 簡介 語言 線程生命周期 使用頻率
pthread 1. 一套通用的多線程API
2. 適用于Unix/Linux/Windows等系統(tǒng)
3. 跨平臺(tái)、可移植
4. 使用難度大
C 程序員管理 幾乎不用
NSThread 1. 使用更加面向?qū)ο?
2. 簡單易用,可直接操作線程對(duì)象
OC 程序員管理 偶爾使用
GCD 1. 旨在代替NSThread等線程技術(shù)
2. 充分利用設(shè)備的多核
C 自動(dòng)管理 經(jīng)常使用
NSOperation 1. 基于GCD(底層是GCD)
2. 比GCD多了一些簡單使用的功能
3. 使用更加面向?qū)ο?/td>
OC 自動(dòng)管理 經(jīng)常使用

2. GCD的常用函數(shù)

  • GCD中有2個(gè)用來執(zhí)行任務(wù)的函數(shù)
    • 用同步的方式執(zhí)行任務(wù)
      • dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
      • queue: 隊(duì)列
      • block: 任務(wù)
    • 用異步的方式執(zhí)行任務(wù)
    • dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

3. GCD的隊(duì)列

  • GCD的隊(duì)列可以分為2大類型
    • 并發(fā)隊(duì)列
      • 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
      • 并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
    • 串行隊(duì)列
      • 讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))

4. 組合隊(duì)列執(zhí)行表

并發(fā)隊(duì)列 手動(dòng)創(chuàng)建串行隊(duì)列 主隊(duì)列
同步 沒有開啟新線程
串行執(zhí)行任務(wù)
沒有開啟新線程
串行執(zhí)行任務(wù)
沒有開啟新線程
串行執(zhí)行任務(wù)
異步 開啟新線程
并行執(zhí)行任務(wù)
開啟新線程
串行執(zhí)行任務(wù)
沒有開啟新線程
串行執(zhí)行任務(wù)

5. GCD的線程鎖

- (void)interview01{
    ///> 會(huì)發(fā)生死鎖,
    NSLog(@"任務(wù)1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任務(wù)2");
    });
    NSLog(@"任務(wù)3");
    ///      dispatch_sync 需要立馬在當(dāng)前線程 同步執(zhí)行任務(wù)  當(dāng)前在主線程中
    ///      而主隊(duì)列需要等 主線程的東西執(zhí)行完之后才會(huì)執(zhí)行。 所以造成了死鎖
}

- (void)interview02{
    ///> 不會(huì)發(fā)生死鎖
    NSLog(@"任務(wù)1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
    });
    NSLog(@"任務(wù)3");
    //  dispatch_sync 不需要需要立馬在當(dāng)前線程 同步執(zhí)行任務(wù) 所以等待主線程執(zhí)行結(jié)束之后才執(zhí)行的
}

- (void)interview03{
    ///> 會(huì)產(chǎn)生死鎖
    NSLog(@"任務(wù)1");
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 同步;
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        dispatch_sync(queue, ^{  //死鎖
            NSLog(@"任務(wù)3");
        });
        NSLog(@"任務(wù)4");
    });
    NSLog(@"任務(wù)5");   
}

- (void)interview04{
    ///> 不會(huì)產(chǎn)生死鎖
    NSLog(@"任務(wù)1");
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 串行;
    //    dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 并發(fā);
    dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_SERIAL); /// 串行; 也不會(huì)
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2");
        dispatch_sync(queue2, ^{  //死鎖
            NSLog(@"任務(wù)3");
        });
        NSLog(@"任務(wù)4");
    });
    NSLog(@"任務(wù)5");
    //不會(huì)產(chǎn)生死鎖   因?yàn)閮蓚€(gè)任務(wù)不在同一個(gè)隊(duì)列之中, 所以不存在互相等待的問題。
}

- (void)interview05{
    ///> 不會(huì)產(chǎn)生死鎖
    NSLog(@"任務(wù)1  thread:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 并發(fā);
    dispatch_async(queue, ^{
        NSLog(@"任務(wù)2 thread:%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"任務(wù)3  thread:%@",[NSThread currentThread]);
        });
        NSLog(@"任務(wù)4  thread:%@",[NSThread currentThread]);
    });
    NSLog(@"任務(wù)5  thread:%@",[NSThread currentThread]);
    //不會(huì)產(chǎn)生死鎖   因?yàn)閮蓚€(gè)任務(wù)不在同一個(gè)隊(duì)列之中, 所以不存在互相等待的問題。
}

6. GCD的線程鎖-- runloop有關(guān)的鎖

- (void)test{
    NSLog(@"2");
}

- (void)touchesBegan03{
//    NSThread *thread = [[NSThread alloc]initWithBlock:^{
//        NSLog(@"1");
//
//    }];
//    [thread start];
//    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
//    運(yùn)行后會(huì)崩潰  因?yàn)樽泳€程 performSelector方法 沒有開啟runloop, 當(dāng)執(zhí)行test的時(shí)候這個(gè)線程已經(jīng)沒有了。
    
    NSThread *thread = [[NSThread alloc]initWithBlock:^{
        NSLog(@"1");
        [[NSRunLoop  currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
    /// r添加開啟runloop后  在線程中有runloop存在線程就不會(huì)死掉, 之后調(diào)用performSelect就沒有問題了
}

- (void)touchesBegan02{
    /// 創(chuàng)建全局并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");

        //[self performSelector:@selector(test) withObject:nil];///  打印結(jié)果  1  2  3   等價(jià)于[self test]
        /// 這句代碼點(diǎn)進(jìn)去發(fā)現(xiàn)是在Runloop中的方法
        /// 本質(zhì)就是向Runloop中添加了一個(gè)定時(shí)器。  子線程默認(rèn)是沒有啟動(dòng) Runloop的
        
        [self performSelector:@selector(test) withObject:nil afterDelay:.0]; ///  打印結(jié)果  1  3
        NSLog(@"3");
        /// 啟動(dòng)runloop
        [[NSRunLoop  currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
- (void)touchesBegan01{
    /// 創(chuàng)建全局并發(fā)隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");

//        [self performSelector:@selector(test) withObject:nil];///  打印結(jié)果  1  2  3   等價(jià)于[self test]
        /// 這句代碼點(diǎn)進(jìn)去發(fā)現(xiàn)是在Runloop中的方法
//     本質(zhì)就是向Runloop中添加了一個(gè)定時(shí)器。  子線程默認(rèn)是沒有啟動(dòng) Runloop的

        [self performSelector:@selector(test) withObject:nil afterDelay:.0]; ///  打印結(jié)果  1  3
        NSLog(@"3");
    });
}

7. GCD組隊(duì)列的使用

  • 異步并發(fā)執(zhí)行任務(wù)1、任務(wù)2
  • 等任務(wù)1、任務(wù)2都執(zhí)行完畢后,再回到主線程執(zhí)行任務(wù)3
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_CONCURRENT);// 并發(fā)隊(duì)列
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)1   thread  --- %@",[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)2   thread  --- %@",[NSThread currentThread]);
        }
    });
    
    ///> 回到主線程執(zhí)行 任務(wù)3
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 5; i++) {
//            NSLog(@"任務(wù)3   thread  --- %@",[NSThread currentThread]);
//        }
//    });
    
    ///> 執(zhí)行完任務(wù)1、2之后再執(zhí)行任務(wù)3、4 
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)3   thread  --- %@",[NSThread currentThread]);
        }
    });
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務(wù)4   thread  --- %@",[NSThread currentThread]);
        }
    });
}

8. 多線程安全隱患的解決方案

  • 隱患造成, 多個(gè)線程同時(shí)訪問一個(gè)數(shù)據(jù)然后對(duì)數(shù)據(jù)進(jìn)行操作
  • 解決方案:使用線程同步技術(shù),
  • 常見線程同步技術(shù): 加鎖
  • iOS線程同步方案:
    • OSSpinLock
    • os_unfair_lock
    • pthread_mutex
    • dispatch_semaphore
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • NSLock
    • NSRecursiveLock
    • NSCondition
    • NSConditionLock
    • @synchronized

內(nèi)存管理

性能優(yōu)化

1. 什么是CPU和GPU

  • CPU (Central Processing Unit,中央處理器 )
    • 對(duì)象的創(chuàng)建和銷毀、對(duì)象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制 (Core Graphics)
  • GPU (Graphics Processing Unit,圖形處理器 )
    • 紋理的渲染


2. 卡頓原因

  • cpu處理后GPU處理 若垂直同步信號(hào)早于GPU處理的速度那么會(huì)形成掉幀問題


  • 在 iOS中有雙緩存機(jī)制,有前幀緩存、后幀緩存

2. 卡頓優(yōu)化 - CPU

  • 盡量使用輕量級(jí)的對(duì)象,比如用不到事件處理的地方,可以考慮使用CALayer 替代 UIView
    • UIView和CALayer的區(qū)別?
      • CALayer是UIView的一個(gè)成員
      • CALayer是專門用來顯示東西的
      • UIView是用來負(fù)責(zé)監(jiān)聽點(diǎn)擊事件等
  • 不要頻繁的調(diào)用UIView的相關(guān)屬性,比如frame、bounds、transform等屬性,盡量減少不必要的修改。
  • 盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整好對(duì)應(yīng)的屬性,不要多次修改屬性。
  • Autolayout會(huì)比直接設(shè)置frame消耗更多的CPU資源
  • 圖片的size最好跟UIImageView的size保持一致
    • 因?yàn)槿绻龌蛘遀IImageView會(huì)對(duì)圖片進(jìn)行伸縮的處理
  • 控制線程的最大并發(fā)數(shù)量
  • 盡量把一些耗時(shí)的操作放到子線程
    • 文本處理(儲(chǔ)存計(jì)算和繪制)
    • 圖片處理(解碼、繪制)

3. 卡頓優(yōu)化 - GPU

  • 盡量減少視圖數(shù)量和層次
  • 盡量避免短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進(jìn)行顯示
  • GPU能處理的最大紋理尺寸是4096x4096,一旦超過這個(gè)尺寸,機(jī)會(huì)占用CPU資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸。
  • 減少透明視圖,不透明的就設(shè)置opaque為YES
  • 盡量避免離屏渲染

4. 離屏渲染

  • 在OpenGL中,GPU有2中渲染方式

    • On-Screen Rendering: 當(dāng)前屏幕渲染,在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行渲染操作。
    • Off-Screen Rendering: 離屏渲染,在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作
  • 離屏渲染消耗性能原因:

    • 需要?jiǎng)?chuàng)建新的緩沖區(qū)
    • 離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境,先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,有需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。
  • 那些操作會(huì)出發(fā)離屏渲染?

    • 光柵化 layer.shouldRasterize = YES;

    • 遮罩,layer.mask =

    • 圓角,同事設(shè)置layer.maskToBounds = YES、layer.cornerRadius大于0

      • 考慮通過CoreGraphics繪制圓角,或者美工直接提供。
      • 第一種方法:通過設(shè)置layer的屬性
      imageView.layer.masksToBounds = YES;
      [self.view addSubview:imageView];
      
      • 第二種方法:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角
      UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
      imageView.image = [UIImage imageNamed:@"1"];
      //開始對(duì)imageView進(jìn)行畫圖
      UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
      //使用貝塞爾曲線畫出一個(gè)圓形圖
      [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
      [imageView drawRect:imageView.bounds];
      
      imageView.image = UIGraphicsGetImageFromCurrentImageContext();
       //結(jié)束畫圖
      UIGraphicsEndImageContext();
      [self.view addSubview:imageView];
      
      • 第三種方法:使用CAShapeLayer和UIBezierPath設(shè)置圓角
      #import "ViewController.h"
      
      @interface ViewController ()
      
      @end
      
      @implementation ViewController
      
      - (void)viewDidLoad {
       [super viewDidLoad];
      UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
      imageView.image = [UIImage imageNamed:@"1"];
      UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
      
      CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
      //設(shè)置大小
      maskLayer.frame = imageView.bounds;
      //設(shè)置圖形樣子
      maskLayer.path = maskPath.CGPath;
      imageView.layer.mask = maskLayer;
      [self.view addSubview:imageView];
      }
      
      
      • 推薦三種方式,對(duì)內(nèi)存的消耗最少啊,渲染速度快
    • 陰影, layer.shadowXXX

      • 如果設(shè)置了layer.shadowPath就不會(huì)產(chǎn)生

4. 卡頓檢測

  • 平時(shí)所說的“卡頓”主要是因?yàn)樵谥骶€程執(zhí)行了比較耗時(shí)的操作

  • 可以添加Observer到主線程RunLoop中,通過監(jiān)聽RunLoop狀態(tài)切換的耗時(shí),以達(dá)到監(jiān)控卡頓的目的

  • 參考代碼:GitHub - UIControl/LXDAppFluecyMonitor

5. 耗電優(yōu)化

  • 耗電來源
    • CPU處理 Processing
    • 網(wǎng)絡(luò) Networking
    • 定位 Location
    • 圖像 Graphice
  • 盡可能降低CPU、GPU的功耗
  • 少用定時(shí)器
  • 優(yōu)化I/O操作 文件讀寫
    • 盡量不要頻繁的寫入小數(shù)據(jù),最好批量一次性寫入
    • 讀寫大量重要的數(shù)據(jù)的時(shí)候,考慮使用dispatch_io,其提供了基于GCD的異步操作文件I/O的API。用dispatch_io系統(tǒng)會(huì)優(yōu)化磁盤訪問
    • 數(shù)據(jù)量比較大的,建議使用數(shù)據(jù)庫(比如SQList、CoreData)
  • 網(wǎng)絡(luò)優(yōu)化
    • 減少,壓縮網(wǎng)絡(luò)數(shù)據(jù)
    • 多次請(qǐng)求結(jié)果相同,盡量使用緩存
    • 使用斷點(diǎn)續(xù)傳,否則網(wǎng)絡(luò)不穩(wěn)定時(shí)可能多次傳輸相同的內(nèi)容
    • 網(wǎng)絡(luò)不可用時(shí)盡量不要嘗試執(zhí)行網(wǎng)絡(luò)請(qǐng)求
    • 讓用戶可以取消長時(shí)間運(yùn)行或者網(wǎng)絡(luò)速度很慢的網(wǎng)絡(luò)操作,設(shè)置合理的超時(shí)時(shí)間
    • 批量傳輸,比如:下載視頻流時(shí),不要傳輸很小的數(shù)據(jù)包,直接下載整個(gè)文件或者一大塊一大塊的下載,如果下載廣告,一次性多下載一些,然后慢慢展示。如果下載電子郵件,一次下載多封不要,一封一封的下載。
  • 定位優(yōu)化
    • 如果只需要快速確定用戶位置,最好用CLLocationManager的requestLocation方法。定位完成之后,會(huì)自動(dòng)讓定位硬件斷電。
    • 如果不是導(dǎo)航應(yīng)用,盡量不要實(shí)時(shí)更新位置,定位完畢之后就關(guān)掉定位服務(wù)
    • 盡量降低定位的精準(zhǔn)度,如果沒有需求的話盡量使用低精準(zhǔn)度的定位。隨軟件自身要求
    • 如果需要后臺(tái)定位,盡量設(shè)置pausesLocationUpdatasAutomatically為YES,如果用戶不太可能移動(dòng)的時(shí)候系統(tǒng)會(huì)自動(dòng)暫停位置更新
  • App啟動(dòng)
    • App啟動(dòng)分為兩種
      • 冷啟動(dòng)(Cold Launch):從零開始啟動(dòng)App
      • 熱啟動(dòng)(Warm Launch):App已經(jīng)在內(nèi)存中,在后臺(tái)存活,再次點(diǎn)擊圖標(biāo)啟動(dòng)App
    • App啟動(dòng)時(shí)間的優(yōu)化,主要針對(duì)于冷啟動(dòng)
      • 通過添加環(huán)境變量可以打印app啟動(dòng)的時(shí)間分析(Edit scheme -> Run -> Arguments)
        • DYLD_PRINT_STATISTICS設(shè)置為1
        • 如果想要看更詳細(xì)的內(nèi)容,那就將DYLD_PRINTP_STATISTICS_DETAILS設(shè)置為1
    • 冷起訂分為三大階段
      • dyld(dynamic link editor),Apple的動(dòng)態(tài)連接器,可用來裝在Mach-O文件(可執(zhí)行文件,動(dòng)態(tài)庫等)
        • 啟動(dòng)時(shí)做的事情
          • 裝載App的可執(zhí)行文件,同時(shí)會(huì)遞歸加載所有依賴的動(dòng)態(tài)庫
          • 當(dāng)dyld把所有的可執(zhí)行文件和動(dòng)態(tài)庫都裝載完畢之后,會(huì)通知runtime進(jìn)行下一步處理
      • runtime
        • 啟動(dòng)時(shí)runtime所做的事情
          • 調(diào)用map_images進(jìn)行可執(zhí)行文件內(nèi)容的解析和處理
          • 在load_images中調(diào)用call_load_methods,調(diào)用所有Class和Catrgory的+load方法
          • 進(jìn)行各種Objc結(jié)構(gòu)的初始化,(注冊(cè)O(shè)bjc類、初始化類對(duì)象等等)
          • 調(diào)用C++靜態(tài)初始化器和attribute((constructor))修飾的函數(shù)
        • 到此為止可執(zhí)行文件的動(dòng)態(tài)庫和所有的符號(hào)(Class、protocols、Selector、IMP...)都已經(jīng)按格式成功加載到內(nèi)存中,被runtime所管理
      • main
        • 總結(jié)一下
          • APP的啟動(dòng)有dyld主導(dǎo),將可執(zhí)行文件加載到內(nèi)存、順便加載所有依賴的動(dòng)態(tài)庫
          • 并有Runtime負(fù)責(zé)加載成objc定義的結(jié)構(gòu)
          • 所有初始化工作結(jié)束后,dyld就會(huì)調(diào)用main函數(shù)
          • 接下來就是UIApplicationMain函數(shù),AppDelegate的Application:didFinishLaunchingWithOptions:方法


            image
  • 如何優(yōu)化啟動(dòng)時(shí)間
    • dyld
      • 減少動(dòng)態(tài)庫、合并一些動(dòng)態(tài)庫(定期清理不必要的動(dòng)態(tài)庫)
      • 減少Objc類、分類的數(shù)量、減少Selector數(shù)量(定期清理不必要的類、分類)
      • 減少C++虛函數(shù)數(shù)量
      • swift盡量使用struct
    • runtime
      • 用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++靜態(tài)構(gòu)造器、Objc的+load
    • main
      • 在不影響用戶體驗(yàn)的前提下,盡可能將一些操作延遲,不要全部都放在finishLaunching方法中
      • 按需求加載

6. 安裝包瘦身

  • 安裝包(IPA)主要由可執(zhí)行文件、資源組成

  • 資源(圖片、音頻、視頻等)

  • 可執(zhí)行文件瘦身

    • 編譯器優(yōu)化

      • Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default設(shè)置為YES
      • 去掉異常支持,Enable C++ Exceptions、Enable Objective-C Exceptions設(shè)置為NO,Other C Flags添加-fno-exceptions
    • 利用AppCode(AppCode_下載鏈接)檢測未使用的代碼:

      • 菜單欄 -> Code -> inspect Code
    • 編寫LLVM插件檢測出重復(fù)代碼、未被調(diào)用的代碼

    • 生成LinkMap文件,可以查看可執(zhí)行文件的具體組成

      image

構(gòu)架設(shè)計(jì)

1. 設(shè)計(jì)模式

  • 設(shè)計(jì)模式(Design Pattern)

    • 是一套被反復(fù)使用、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)
    • 使用設(shè)計(jì)模式的好處是:可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性
    • 一般與編程語言無關(guān),是一套比較成熟的編程思想
  • 設(shè)計(jì)模式可以分為三大類

    • 創(chuàng)建型模式:對(duì)象實(shí)例化的模式,用于解耦對(duì)象的實(shí)例化過程
      • 單例模式、工廠方法模式,等等
    • 結(jié)構(gòu)型模式:把類或?qū)ο蠼Y(jié)合在一起形成一個(gè)更大的結(jié)構(gòu)
      • 代理模式、適配器模式、組合模式、裝飾模式,等等
    • 行為型模式:類或?qū)ο笾g如何交互,及劃分責(zé)任和算法
      • 觀察者模式、命令模式、責(zé)任鏈模式,等等
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計(jì)數(shù)器來控制對(duì)象的生命周期。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,216評(píng)論 0 10
  • 1.設(shè)計(jì)模式是什么? 你知道哪些設(shè)計(jì)模式,并簡要敘述?設(shè)計(jì)模式是一種編碼經(jīng)驗(yàn),就是用比較成熟的邏輯去處理某一種類型...
    龍飝閱讀 2,294評(píng)論 0 12
  • 面試題的答案都是拋磚引玉,具體細(xì)節(jié)還得深入了解iOS底層原理復(fù)制代碼 1、一個(gè)NSObject對(duì)象占用多少內(nèi)存? ...
    大豆豆_小豆豆閱讀 439評(píng)論 0 0
  • 今天,聽歌! ----==《在路上》==---- 詞:王利芬、張瑞敏、馬云、牛根生等 曲:王曉鋒 演唱:劉歡 那一...
    8c8392d58a4a閱讀 449評(píng)論 1 0

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