守望寒冬,多喝燙水。

總結一些iOS的底層面試題。鞏固一下iOS的相關基礎知識。ios

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

OC對象

1. NSObject對象的本質是什麼?

  • NSObject對象的本質就是結構體

2. 一個NSObject對象佔用多少內存?

  • NSObject對象建立實例對象的時候系統分配了16個內存(經過malloc_size函數可得到)程序員

  • 可是 NSObject只使用了8個字節 使用(class_getinstanceSize可得到)github

3. 對象的isa指針指向哪裏?

  • instance對象的isa指針指向class對象
  • class對象的isa指針指向 meta-class對象
  • meta-class對象的isa指針基類的meta-class對象

4. OC類的信息存儲在哪裏?

  • meta-class存儲:類方法
  • class對象存儲: 對象方法,屬性,成員變量,協議信息
  • instance存儲: 成員變量具體的值

5. 說說你對函數調用的理解吧。

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

KVO

1. iOS用什麼方式實現對一個對象的KVO?(KVO的本質是什麼?)

  • 利用Runtime API 動態生成了一個新的類, 而且instance對象的isa指針指向這個生成的新子類。面試

  • 當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueForKey函數算法

    • willChangeValueForKey
    • 父類原來的set方法
    • didChangeValueForKey
    • didChangeValueForKey 內部會觸發observerValueForKeyPath方法 實現監聽。
  • 輕量級KVO框架:GitHub - facebook/KVOController數據庫

KVC

1. 使用KVC會不會調用KVO?

  • 會調用KVO, 由於他的內部使用了:
    • willChangeValueForKey
    • 直接去_方式去更改
    • didChangeValueForKey
    • didChangeValueForKey 內部會觸發

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

  • 賦值
    • setKey、_setKey 順序查找方法
      • 有: 傳遞參數調用防範
      • 沒有: 查看accessInstanceVariablesDirectly 方法返回值 yes 繼續查找
    • _key、_iskey、key、iskey 順序查找

Category:

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

2. Category中的屬性是否也存在類對象中?若是存在是怎麼生成和存在的?若是不存在,它存在的位置在哪裏?

  • 一個類永遠只有一個類對象
  • 在運行起來以後 最重都會合並在 類對象中去。

3. Category的使用原理是什麼?實現過程

  • Category在編譯的時候會將 方法、屬性、協議的數據合併到一個大數組中,
    • 後參加編譯一的數據會放在數組的前面
  • 在運行時的時候,runtime會將Category的數據合併到類的信息中(類對象、元類對象中)
  • 分類的調用順序

4. Category和Extension的區別是什麼?

  • Category 在運行的時候纔將數據合併到類信息中
  • Extension 在編譯的時候就將數據包含在類信息中了 @interface Xxxx() 也叫作匿名分類

5. Category中有load方法嗎?load是何時調用的?load方法能繼承碼?

  • 有load方法
  • load方法在runtime加載類、分類的時候調用
  • load方法能夠繼承,可是通常狀況下不會主動去調用load方法,都是讓系統自動調用

6. load、initialize方法的區別是什麼?它們在Category中的調用順序?以及出現繼承時他們之間的調用過程?

  • 調用方式:
    • load 根據函數地址直接調用
    • initialize 是經過 objc_msgSend調用
  • 調用時刻:
    • load是runtime加載類、分類的時候調用(只會調用1次)
    • initialize 是類第一次接收到消息的時候調用、每個類只會調用1次(可是父類的initialize方法可能會調用屢次) 有些子類沒有initialize方法因此調用父類的。
  • 調用順序:
    • load:
      • 先調用類的load
        • 先編譯的類,優先調用load
        • 調用子類的load以前會先調用父類的load
      • 在調用分類的load
        • 先編譯的分類,優先調用load
    • initialize
      • 初始化父類
      • 在初始化子類(可能最終調用的是父類的initialize方法)

7. Category可否添加成員變量?若是能夠,如何給Category添加成員變量?

  • 不能直接給Category添加成員變量,可是能夠間接添加。編程

    • 使用一個全局的字典 (缺點: 每個屬相都須要一套相同的代碼)
    ///> DLPerson+Test.h
     @interface DLPerson (Test)
     ///> 若是直接使用 @property 只會生成方法的聲名 不會生成成員變量和set、get方法的實現。
     @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機制給分類添加屬性swift

    #import<objc/runtime.h>
    
    const void *DLNameKey = &DLNameKey
    ///> 添加關聯對象
    void objc_setAssociatedObject(
    id object,          ///> 給哪個對象添加關聯對象
    const void * key,   ///> 指針(賦值取值的key) &DLNameKey
    id value,           ///> 關聯的值
    objc_AssociationPolicy policy ///> 關聯策略 下方表格
    )
    
    eg : objc_setAssociatedObject(self,@selector(name),name,OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    
    ///> 得到關聯對象
    id objc_getAssociatedObject(
    id object,           ///> 哪個對象的關聯對象
    const void * key     ///> 指針(賦值取值的key) 
    )
    eg:
    objc_getAssociatedObject(self,@selector(name));
    /// _cmd == @selector(name); 
    objc_getAssociatedObject(self,_cmd);
    
    ///> 移除全部的關聯對象
    void objc_removeAssociatedObjects(
    id object       ///>
    )
    
    複製代碼
    • objc_AssociationPolicy(關聯策略)
    objc_AssociationPolicy(關聯策略) 對應的修飾符
    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的原理是怎樣的?本質是什麼

  • block的本質就是一個oc對象 內部也有isa指針, 封裝了函數及調用環境的OC對象,

2. 看代碼解釋緣由

int main(int argc, const char *argv[]){
    @autoreleasepool{
      int age = 10void  (^block)(void) = ^{
          NSLog(@" age is %d ",age);
      };
      age = 20;
      block();
    }
  }
  /* 輸出結果爲? 爲何? 輸出結果是: 10 若是沒有修飾符 默認是auto 爲了能訪問外部的變量 block有一個變量捕獲的機制 由於他是局部變量 而且沒有用static修飾 因此它被捕獲到block中是 一個值,外部再次改變時 block中的age不會改變。 */
複製代碼
變量類型 捕獲到Block內部 訪問方式
局部變量 auto 值傳遞
局部變量 static 指針傳遞
全局變量 直接訪問
int main(int argc, const char *argv[]){
  @autoreleasepool{
    int age = 10static int height = 10;
    void  (^block)(void) = ^{
        NSLog(@" age is %d, height is %d",age, height);
    };
    age = 20;
    height = 20;
    block();
  }
}
/* 輸出結果爲? 爲何? age is 10, height is 20 局部變量用static 修飾以後 捕獲到block中的是 height的指針, 所以修改經過指針修改變量以後 外部的變量也被修改了 */
複製代碼
int age = 10static 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();
  }
}
/* 輸出結果爲? 爲何? age is 20, height is 20 由於 age 和 height是全局變量不須要捕獲直接就能夠修改 全局變量 對應該就能夠訪問, 局部變量 須要跨函數訪問,因此須要捕獲 所以修改經過指針修改變量以後 外部的變量也被修改了 */

複製代碼
int main(int argc, const char *argv[]){
  @autoreleasepool{
    
    void  (^block)(void) = ^{
        NSLog(@" self %p",self);
    };
    block();
  }
}
/* self 會不會被捕獲? 由於函數默認會有兩個參數 void test(DLPerson *self, SEL _cmd) 因此self 也是一個局部變量 訪問@property(nonatmic, copy) NSString *name; 由於他是成員變量, 訪問的時候 用self.name 或者 self->_name 訪問 因此 block在內部會捕獲self。 */

複製代碼

3. 既然block是一個OC對象,那麼block的對象類型是什麼?

  • ios內存分爲5發區域設計模式

    • 堆: 動態分配內存,須要程序員申請,也須要程序員本身管理(alloc、malloc等...)
    • 棧: 放一些局部變量,臨時變量 系統本身管理
    • 靜態區(全局區): 存放全局的靜態對象。(編譯時分配,APP結束由系統釋放)
    • 常量區: 常量。(編譯時分配,APP結束由系統釋放)
    • 代碼區: 程序區
  • block有三種類型,最終都繼承自NSBlock類型

    • superClass NSGlobalBlock : __NSGlobalBlock : NSBlock : NSObject
    block類型 環境 存放位置
    NSGlobalBlock 沒有訪問auto變量 靜態區
    NSStackBlock 訪問了auto變量
    NSMallocBlock __NSStackBlock__調用了copy
  • __NSStackBlock__調用了copy 代碼實例

    void (^block)(void);
    void (^block1)(void);
    void test2(){
        int age = 10;
        block = ^{
            NSLog("age is %d",age);
          /* 由於 block訪問了 auto變量 因此目前block的類型爲NSStackBlock類型, 存放的位置在 棧 上 在 main訪問是 這個block已經被釋放了。 */
        };
        
       [block1 = ^{
            NSLog("age is %d",age);
            /* 由於 block訪問了 auto變量 而且 進行了copy操做 因此目前block的類型爲 NSMallocBlock 類型 存放的位置在 堆 上 在 main訪問是 這個block是能夠被訪問的。 */
        } copy];
    }
    
    int main(int argc, const char *argv[]){
    
        @autoreleasepool{
            test2();
            
            block(); /// 輸出的值爲 很大不是想要的值
            
            block1();/// 輸出的值是10
        }
    }
    複製代碼
  • 每一種類型的block調用了copy以後結果以下所示

    block的類型 副本源的配置存儲域 複製後的區域
    NSGlobalBlock 程序的數據區域 什麼都不作
    NSStackBlock 從棧複製到堆
    NSMallocBlock 引用計數器+1

4. 在什麼狀況下 ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上?

  • block做爲函數的返回值的時候

  • 將block賦值給__strong指針時

  • block做爲 Cocoa API中方法名含有usingBlock的方法參數時

    NSArray *arr = @[];
    /// 遍歷數組中包含 usingBlock方法的參數
    [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的做用是什麼?有什麼使用注意點?

  • __weak 是一個修飾符
  • 當block內部訪問的對象類型的auto變量
    • 若是block是在棧上,將不會對auto變量產生強引用
    • 若是Block被拷貝到堆上
      • 會調用block內部的copy函數
      • copy函數會調用源碼中的_Block_object_assign函數
      • _Block_object_assign函數會根據修飾 auto 變量的修飾符(__strong、__weak 、__unsafe_unretained)來決定做出相應的操做,造成強引用或者弱引用
    • block從對上移除
      • 會調用block內部的dispose函數
      • dispose函數會調用源碼中的 _Block_object_dispose函數
      • _Block_object_dispose函數會自動釋放auto變量(release)

6. __block的做用是什麼?有什麼使用注意點?

  • __block修飾以後會將變量包裝成一個對象 能夠解決block內部沒法修改auto變量的問題
  • 包裝秤對象以後就能夠經過指針修改 外部的變量了

7. __block的屬性修飾詞是什麼?爲何?使用block有哪些注意點?

  • 修飾詞是copy
  • block 若是沒有進行copy操做就不會再堆上, 在堆上才能控制它的生命週期
  • 注意循環引用的問題
  • 在ARC環境下 使用呢strong和copy均可以沒有區別 在MRC環境下有區別

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

  • 不須要

Runtime

22. 講一下OC的消息機制

  • OC中的方法調用最後都是 objc_msgSend函數的調用,給receiver(方法調用者)發送了一條消息(selector方法名)
  • objc_msgSend底層有三大模塊
  • 消息發送(當前類、父類中查找)、動態方法解析、消息轉發

23. 消息轉發機制流程

24. 什麼是runtime? 平時項目中有用過嗎?

  • OC是一門動態性比較強的語言,容許不少操做推遲到程序運行時才進行
  • OC的動態性是由runtime來支撐實現的,runtime是一套C語言的API,封裝了許多動態性相關的函數
  • 平時寫的代碼 底層都是轉換成了 runtime的API進行調用的
  • 具體應用
    • 關聯對象,給分類添加屬性,set和get的實現
    • 遍歷類的成員變量 歸檔解檔、字典轉模型
    • 交換方法(系統的交換方法)

26. iskindOfClass 和 isMemberOfClass的區別?

  • isMemberOfClass源碼:

    /// 返回的直接是 是不是當前的類, 當前類對象
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    /// 返回的直接是 是不是當前的類, 
    /// 當前元類對象
    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    } 
    
    
    複製代碼
  • iskindOfClass源碼:

    /// for循環查找 , 會根據當前類和 當前類的額父類去逐級查找 ,
    - (BOOL)isKindOfClass:(Class)cls {
      for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
          if (tcls == cls) return YES;
      }
      return NO;
    }
    
    /// for循環查找 , 會根據當前類和 當前類的額父類去逐級查找 ,
    /// 當前元類對象
     + (BOOL)isKindOfClass:(Class)cls {
      for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
          if (tcls == cls) return YES;
      }
      return NO;
    } 
    複製代碼
  • 相關面試題:

    // 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]]);
          /// 上面的寫法 與 下面的寫法 相同
          
          // 這句代碼的方法調用者不論是哪一個類(只要是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
    // 輸出的結果是什麼?
    
    複製代碼

Runloop

1. 講講Runloop片在項目中的應用

Runloop

多線程

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

技術方案 簡介 語言 線程生命週期 使用頻率
pthread 1. 一套通用的多線程API
2. 適用於Unix/Linux/Windows等系統
3. 跨平臺、可移植
4. 使用難度大
C 程序員管理 幾乎不用
NSThread 1. 使用更加面向對象
2. 簡單易用,可直接操做線程對象
OC 程序員管理 偶爾使用
GCD 1. 旨在代替NSThread等線程技術
2. 充分利用設備的多核
C 自動管理 常用
NSOperation 1. 基於GCD(底層是GCD)
2. 比GCD多了一些簡單使用的功能
3. 使用更加面向對象
OC 自動管理 常用

2. GCD的經常使用函數

  • GCD中有2個用來執行任務的函數
    • 用同步的方式執行任務
      • dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
      • queue: 隊列
      • block: 任務
    • 用異步的方式執行任務
    • dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

3. GCD的隊列

  • GCD的隊列能夠分爲2大類型
    • 併發隊列
      • 可讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
      • 併發功能只有在異步(dispatch_async)函數下才有效
    • 串行隊列
      • 讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)

4. 組合隊列執行表

併發隊列 手動建立串行隊列 主隊列
同步 沒有開啓新線程
串行執行任務
沒有開啓新線程
串行執行任務
沒有開啓新線程
串行執行任務
異步 開啓新線程
並行執行任務
開啓新線程
串行執行任務
沒有開啓新線程
串行執行任務

5. GCD的線程鎖

- (void)interview01{
    ///> 會發生死鎖,
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"任務2");
    });
    NSLog(@"任務3");
    /// dispatch_sync 須要立馬在當前線程 同步執行任務 當前在主線程中
    /// 而主隊列須要等 主線程的東西執行完以後纔會執行。 因此形成了死鎖
}

- (void)interview02{
    ///> 不會發生死鎖
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        NSLog(@"任務2");
    });
    NSLog(@"任務3");
    // dispatch_sync 不須要須要立馬在當前線程 同步執行任務 因此等待主線程執行結束以後才執行的
}

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

- (void)interview04{
    ///> 不會產生死鎖
    NSLog(@"任務1");
    dispatch_queue_t queue = dispatch_queue_create("muqueue", DISPATCH_QUEUE_SERIAL); /// 串行;
    // dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 併發;
    dispatch_queue_t queue2 = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_SERIAL); /// 串行; 也不會
    dispatch_async(queue, ^{
        NSLog(@"任務2");
        dispatch_sync(queue2, ^{  //死鎖
            NSLog(@"任務3");
        });
        NSLog(@"任務4");
    });
    NSLog(@"任務5");
    //不會產生死鎖 由於兩個任務不在同一個隊列之中, 因此不存在互相等待的問題。
}

- (void)interview05{
    ///> 不會產生死鎖
    NSLog(@"任務1 thread:%@",[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("muqueue2", DISPATCH_QUEUE_CONCURRENT); /// 併發;
    dispatch_async(queue, ^{
        NSLog(@"任務2 thread:%@",[NSThread currentThread]);
        dispatch_sync(queue, ^{
            NSLog(@"任務3 thread:%@",[NSThread currentThread]);
        });
        NSLog(@"任務4 thread:%@",[NSThread currentThread]);
    });
    NSLog(@"任務5 thread:%@",[NSThread currentThread]);
    //不會產生死鎖 由於兩個任務不在同一個隊列之中, 因此不存在互相等待的問題。
}
複製代碼

6. GCD的線程鎖-- runloop有關的鎖

- (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];
// 運行後會崩潰 由於子線程 performSelector方法 沒有開啓runloop, 當執行test的時候這個線程已經沒有了。
    
    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存在線程就不會死掉, 以後調用performSelect就沒有問題了
}

- (void)touchesBegan02{
    /// 建立全局併發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");

        //[self performSelector:@selector(test) withObject:nil];/// 打印結果 1 2 3 等價於[self test]
        /// 這句代碼點進去發現是在Runloop中的方法
        /// 本質就是向Runloop中添加了一個定時器。 子線程默認是沒有啓動 Runloop的
        
        [self performSelector:@selector(test) withObject:nil afterDelay:.0]; /// 打印結果 1 3
        NSLog(@"3");
        /// 啓動runloop
        [[NSRunLoop  currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
- (void)touchesBegan01{
    /// 建立全局併發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"1");

// [self performSelector:@selector(test) withObject:nil];/// 打印結果 1 2 3 等價於[self test]
        /// 這句代碼點進去發現是在Runloop中的方法
// 本質就是向Runloop中添加了一個定時器。 子線程默認是沒有啓動 Runloop的

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

7. GCD組隊列的使用

  • 異步併發執行任務一、任務2
  • 等任務一、任務2都執行完畢後,再回到主線程執行任務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);// 併發隊列
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務1 thread --- %@",[NSThread currentThread]);
        }
    });
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務2 thread --- %@",[NSThread currentThread]);
        }
    });
    
    ///> 回到主線程執行 任務3
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// for (int i = 0; i < 5; i++) {
// NSLog(@"任務3 thread --- %@",[NSThread currentThread]);
// }
// });
    
    ///> 執行完任務一、2以後再執行任務三、4 
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務3 thread --- %@",[NSThread currentThread]);
        }
    });
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任務4 thread --- %@",[NSThread currentThread]);
        }
    });
}
複製代碼

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

  • 隱患形成, 多個線程同時訪問一個數據而後對數據進行操做
  • 解決方案:使用線程同步技術,
  • 常見線程同步技術: 加鎖
  • iOS線程同步方案:
    • OSSpinLock
    • os_unfair_lock
    • pthread_mutex
    • dispatch_semaphore
    • dispatch_queue(DISPATCH_QUEUE_SERIAL)
    • NSLock
    • NSRecursiveLock
    • NSCondition
    • NSConditionLock
    • @synchronized

內存管理

性能優化

1. 什麼是CPU和GPU

  • CPU (Central Processing Unit,中央處理器 )
    • 對象的建立和銷燬、對象屬性的調整、佈局計算、文本的計算和排版、圖片的格式轉換和解碼、圖像的繪製 (Core Graphics)
  • GPU (Graphics Processing Unit,圖形處理器 )
    • 紋理的渲染

2. 卡頓緣由

  • cpu處理後GPU處理 若垂直同步信號早於GPU處理的速度那麼會造成掉幀問題
  • 在 iOS中有雙緩存機制,有前幀緩存、後幀緩存

2. 卡頓優化 - CPU

  • 儘可能使用輕量級的對象,好比用不到事件處理的地方,能夠考慮使用CALayer 替代 UIView
    • UIView和CALayer的區別?
      • CALayer是UIView的一個成員
      • CALayer是專門用來顯示東西的
      • UIView是用來負責監聽點擊事件等
  • 不要頻繁的調用UIView的相關屬性,好比frame、bounds、transform等屬性,儘可能減小沒必要要的修改。
  • 儘可能提早計算好佈局,在有須要時一次性調整好對應的屬性,不要屢次修改屬性。
  • Autolayout會比直接設置frame消耗更多的CPU資源
  • 圖片的size最好跟UIImageView的size保持一致
    • 由於若是超出或者UIImageView會對圖片進行伸縮的處理
  • 控制線程的最大併發數量
  • 儘可能把一些耗時的操做放到子線程
    • 文本處理(儲存計算和繪製)
    • 圖片處理(解碼、繪製)

3. 卡頓優化 - GPU

  • 儘可能減小視圖數量和層次
  • 儘可能避免短期內大量圖片的顯示,儘量將多張圖片合成一張進行顯示
  • GPU能處理的最大紋理尺寸是4096x4096,一旦超過這個尺寸,機會佔用CPU資源進行處理,因此紋理儘可能不要超過這個尺寸。
  • 減小透明視圖,不透明的就設置opaque爲YES
  • 儘可能避免離屏渲染

4. 離屏渲染

  • 在OpenGL中,GPU有2中渲染方式
    • On-Screen Rendering: 當前屏幕渲染,在當前用於顯示的屏幕緩衝區進行渲染操做。
    • Off-Screen Rendering: 離屏渲染,在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做
  • 離屏渲染消耗性能緣由:
    • 須要建立新的緩衝區
    • 離屏渲染的整個過程,須要屢次切換上下文環境,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束後,將離屏緩衝區的渲染結果顯示到屏幕上,有須要將上下文環境從離屏切換到當前屏幕。
  • 那些操做會出發離屏渲染?
    • 光柵化 layer.shouldRasterize = YES;

    • 遮罩,layer.mask =

    • 圓角,同事設置layer.maskToBounds = YES、layer.cornerRadius大於0

      • 考慮經過CoreGraphics繪製圓角,或者美工直接提供。
      • 第一種方法:經過設置layer的屬性
      imageView.layer.masksToBounds = YES;
      [self.view addSubview:imageView];
      複製代碼
      • 第二種方法:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角
      UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
      imageView.image = [UIImage imageNamed:@"1"];
      //開始對imageView進行畫圖
      UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
      //使用貝塞爾曲線畫出一個圓形圖
      [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
      [imageView drawRect:imageView.bounds];
      
      imageView.image = UIGraphicsGetImageFromCurrentImageContext();
       //結束畫圖
      UIGraphicsEndImageContext();
      [self.view addSubview:imageView];
      複製代碼
      • 第三種方法:使用CAShapeLayer和UIBezierPath設置圓角
      #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];
      //設置大小
      maskLayer.frame = imageView.bounds;
      //設置圖形樣子
      maskLayer.path = maskPath.CGPath;
      imageView.layer.mask = maskLayer;
      [self.view addSubview:imageView];
      }
      
      複製代碼
      • 推薦三種方式,對內存的消耗最少啊,渲染速度快
    • 陰影, layer.shadowXXX

      • 若是設置了layer.shadowPath就不會產生

4. 卡頓檢測

  • 平時所說的「卡頓」主要是由於在主線程執行了比較耗時的操做

  • 能夠添加Observer到主線程RunLoop中,經過監聽RunLoop狀態切換的耗時,以達到監控卡頓的目的

  • 參考代碼:GitHub - UIControl/LXDAppFluecyMonitor

5. 耗電優化

  • 耗電來源
    • CPU處理 Processing
    • 網絡 Networking
    • 定位 Location
    • 圖像 Graphice
  • 儘量下降CPU、GPU的功耗
  • 少用定時器
  • 優化I/O操做 文件讀寫
    • 儘可能不要頻繁的寫入小數據,最好批量一次性寫入
    • 讀寫大量重要的數據的時候,考慮使用dispatch_io,其提供了基於GCD的異步操做文件I/O的API。用dispatch_io系統會優化磁盤訪問
    • 數據量比較大的,建議使用數據庫(好比SQList、CoreData)
  • 網絡優化
    • 減小,壓縮網絡數據
    • 屢次請求結果相同,儘可能使用緩存
    • 使用斷點續傳,不然網絡不穩定時可能屢次傳輸相同的內容
    • 網絡不可用時儘可能不要嘗試執行網絡請求
    • 讓用戶能夠取消長時間運行或者網絡速度很慢的網絡操做,設置合理的超時時間
    • 批量傳輸,好比:下載視頻流時,不要傳輸很小的數據包,直接下載整個文件或者一大塊一大塊的下載,若是下載廣告,一次性多下載一些,而後慢慢展現。若是下載電子郵件,一次下載多封不要,一封一封的下載。
  • 定位優化
    • 若是隻須要快速肯定用戶位置,最好用CLLocationManager的requestLocation方法。定位完成以後,會自動讓定位硬件斷電。
    • 若是不是導航應用,儘可能不要實時更新位置,定位完畢以後就關掉定位服務
    • 儘可能下降定位的精準度,若是沒有需求的話儘可能使用低精準度的定位。隨軟件自身要求
    • 若是須要後臺定位,儘可能設置pausesLocationUpdatasAutomatically爲YES,若是用戶不太可能移動的時候系統會自動暫停位置更新
  • App啓動
    • App啓動分爲兩種
      • 冷啓動(Cold Launch):從零開始啓動App
      • 熱啓動(Warm Launch):App已經在內存中,在後臺存活,再次點擊圖標啓動App
    • App啓動時間的優化,主要針對於冷啓動
      • 經過添加環境變量能夠打印app啓動的時間分析(Edit scheme -> Run -> Arguments)
        • DYLD_PRINT_STATISTICS設置爲1
        • 若是想要看更詳細的內容,那就將DYLD_PRINTP_STATISTICS_DETAILS設置爲1
    • 冷起訂分爲三大階段
      • dyld(dynamic link editor),Apple的動態鏈接器,可用來裝在Mach-O文件(可執行文件,動態庫等)
        • 啓動時作的事情
          • 裝載App的可執行文件,同時會遞歸加載全部依賴的動態庫
          • 當dyld把全部的可執行文件和動態庫都裝載完畢以後,會通知runtime進行下一步處理
      • runtime
        • 啓動時runtime所作的事情
          • 調用map_images進行可執行文件內容的解析和處理
          • 在load_images中調用call_load_methods,調用全部Class和Catrgory的+load方法
          • 進行各類Objc結構的初始化,(註冊Objc類、初始化類對象等等)
          • 調用C++靜態初始化器和__attribute__((constructor))修飾的函數
        • 到此爲止可執行文件的動態庫和全部的符號(Class、protocols、Selector、IMP...)都已經按格式成功加載到內存中,被runtime所管理
      • main
        • 總結一下
          • APP的啓動有dyld主導,將可執行文件加載到內存、順便加載全部依賴的動態庫
          • 並有Runtime負責加載成objc定義的結構
          • 全部初始化工做結束後,dyld就會調用main函數
          • 接下來就是UIApplicationMain函數,AppDelegate的Application:didFinishLaunchingWithOptions:方法
  • 如何優化啓動時間
    • dyld
      • 減小動態庫、合併一些動態庫(按期清理沒必要要的動態庫)
      • 減小Objc類、分類的數量、減小Selector數量(按期清理沒必要要的類、分類)
      • 減小C++虛函數數量
      • swift儘可能使用struct
    • runtime
      • 用+initialize方法和dispatch_once取代全部的__attribute__((constructor))、C++靜態構造器、Objc的+load
    • main
      • 在不影響用戶體驗的前提下,儘量將一些操做延遲,不要所有都放在finishLaunching方法中
      • 按需求加載

6. 安裝包瘦身

  • 安裝包(IPA)主要由可執行文件、資源組成
  • 資源(圖片、音頻、視頻等)
  • 可執行文件瘦身
    • 編譯器優化

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

      • 菜單欄 -> Code -> inspect Code
    • 編寫LLVM插件檢測出重複代碼、未被調用的代碼

    • 生成LinkMap文件,能夠查看可執行文件的具體組成

構架設計

1. 設計模式

  • 設計模式(Design Pattern)

    • 是一套被反覆使用、代碼設計經驗的總結
    • 使用設計模式的好處是:可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性
    • 通常與編程語言無關,是一套比較成熟的編程思想
  • 設計模式能夠分爲三大類

    • 建立型模式:對象實例化的模式,用於解耦對象的實例化過程
      • 單例模式、工廠方法模式,等等
    • 結構型模式:把類或對象結合在一塊兒造成一個更大的結構
      • 代理模式、適配器模式、組合模式、裝飾模式,等等
    • 行爲型模式:類或對象之間如何交互,及劃分責任和算法
      • 觀察者模式、命令模式、責任鏈模式,等等
相關文章
相關標籤/搜索