iOS面試總結(jié)

iOS面試總結(jié)

1. 網(wǎng)絡

  1. HTTP協(xié)議(HyperText Transfer Protocol)的請求和響應

    • 請求: 請求頭, 請求行, 請求體
      • 請求行: 指定請求方法, 請求路徑 協(xié)議版本等信息
      • 請求頭: 描述客戶端環(huán)境, 例如: host要請求的主機地址, UserAgent客戶端類型, Accept 可接受數(shù)據(jù)類型, Accept_language可接受語言
      • 請求體: 客戶端要發(fā)送的具體數(shù)據(jù), 例如上傳時的上傳數(shù)據(jù)
    • 響應: 狀態(tài)行,響應頭, 響應數(shù)據(jù)
      • 狀態(tài)行: 包含http協(xié)議版本, 狀態(tài)碼
      • 響應頭: 服務器描述, 數(shù)據(jù)類型描述例如 Sever, Content-Type
      • 響應數(shù)據(jù): 請求所獲得的后臺數(shù)據(jù)
  1. HTTP的特點

    • 無狀態(tài): 服務器不會保留客戶端狀態(tài), 不會記憶上次的狀態(tài), 不受前面請求的影響, 客戶端每次請求獨立, 每次請求需帶上自己的狀態(tài)

    • 持久鏈接: 每個鏈接可以處理多個事務, 特殊強調(diào)http1.0及其之前版本都是非持久鏈接(每個鏈接只能處理一個事務)

      持久鏈接如何結(jié)束?

      • 如果有Content-length字段, 可以判斷接受數(shù)據(jù)是否完成,
      • 如果是分塊傳輸, 響應頭中不包含Content-length, 服務器傳輸完成萬發(fā)送一個空數(shù)據(jù)塊, 當客戶端收到空數(shù)據(jù)塊時算作數(shù)據(jù)接受完成
  2. HTTP的請求方式

    GET仑撞、POST谒出、PUT、DELETE成榜、HEAD胰坟、OPTIONS等

  3. GET和和POST的區(qū)別:

    • get請求參數(shù)拼接在url后面, post的參數(shù)拼接在請求體里面,相對安全(在http抓包情況下依然不安全)

    • get參數(shù)長度有限制2048, post無限制

    • get獲取資源是安全的(不會修改服務器資源), 冪等的(多次執(zhí)行結(jié)果相同), 可緩存的(可以直接有CDN緩存, 減輕服務器負擔), post相反

  1. HTTP常用狀態(tài)碼含義 參考文檔點擊 HTTP狀態(tài)碼總結(jié)

    • 1xx 表示消息

      • 100 初始的請求已經(jīng)接受奶镶,客戶應當繼續(xù)發(fā)送請求的其余部分, 例如post請求(兩段)的第一段請求收到的就是100
      • 101 服務器將遵從客戶的請求轉(zhuǎn)換到另外一種協(xié)議
    • 2xx 表示成功

      • 200: OK, 完全正常
    • 3xx 表示重定向

    • 4xx 表示請求錯誤

      • 400 請求出現(xiàn)語法錯誤
      • 401 訪問收到密碼保護的頁面
      • 403 服務器拒絕執(zhí)行,
      • 404 服務器資源未找到
      • 405 請求方法(get, post等)對指定資源不適用,
    • 5xx 表示服務器錯誤

      • 500 服務器遇到意料不到的錯誤, 不能完成客戶端請求
  1. TCP和UDP的特點, 參考鏈接 簡單理解TCP/IP協(xié)議

    • TCP協(xié)議(Transport Control Protocol诉位,傳輸控制協(xié)議): 是一種面向連接胁孙、可靠的唠倦、基于字節(jié)流的傳輸層協(xié)議,采用了確認機制涮较、超時重傳機制稠鼻,還會對接收到的TCP報文段進行重新排列整理。(TCP報頭含20字節(jié)定長狂票、選項和填充<選項和填充小于等于40字節(jié)>)(TCP是一種面向連接的傳輸層協(xié)議候齿。它可以保證兩端通信主機之間的通信可達。TCP能夠正確處理傳輸過程中丟包闺属、傳輸順序亂掉等異常情況慌盯。)

      TCP能保證可靠性、穩(wěn)定性, 適用于可靠性較高的服務

    • UDP協(xié)議:(User Datagram Protocol掂器,用戶數(shù)據(jù)報協(xié)議)是一種不可靠無連接的傳輸層協(xié)議亚皂,不考慮流控制、錯誤控制国瓮,沒有重傳機制灭必,不會對分組進行順序檢查和排序狞谱。

      UDP控制選項少,無須建立連接禁漓,從而使得數(shù)據(jù)傳輸過程中的延遲小跟衅、數(shù)據(jù)傳輸效率高, 適用于實時性要求高的程序

  2. 網(wǎng)絡七層模型

    應用層, 表示層, 會話層, 傳輸層, 網(wǎng)絡層, 數(shù)據(jù)鏈路層, 物理層

    • 應用層: 網(wǎng)絡應用如: http ftp, pop, smtp等應用協(xié)議
    • 表示層: 定義數(shù)據(jù)格式及加密, 例如,F(xiàn)TP允許你選擇以二進制或ASCII格式傳輸璃饱。如果選擇二進制与斤,那么發(fā)送方和接收方不改變文件的內(nèi)容。如果選擇ASCII格式荚恶,發(fā)送方將把文本從發(fā)送方的字符集轉(zhuǎn)換成標準的ASCII后發(fā)送數(shù)據(jù)撩穿。在接收方將標準的ASCII轉(zhuǎn)換成接收方計算機的字符集。示例:加密谒撼,ASCII等食寡。對應網(wǎng)絡協(xié)議: Telnet, Rlogin, SNMP, Copher等
    • 會話層: 它定義了如何開始、控制和結(jié)束一個會話廓潜,包括對多個雙向消息的控制和管理抵皱,以便在只完成連續(xù)消息的一部分時可以通知應用,從而使表示層看到的數(shù)據(jù)是連續(xù)的辩蛋,在某些情況下呻畸,如果表示層收到了所有的數(shù)據(jù),則用數(shù)據(jù)代表表示層悼院。示例:RPC伤为,SQL等
    • 傳輸層: 這層的功能包括是否選擇差錯恢復協(xié)議還是無差錯恢復協(xié)議,及在同一主機上對不同應用的數(shù)據(jù)流的輸入進行復用据途,還包括對收到的順序不對的數(shù)據(jù)包的重新排序功能绞愚。示例:TCPUDP颖医,SPX位衩。
    • 網(wǎng)絡層: 這層對端到端的包傳輸進行定義,它定義了能夠標識所有結(jié)點的邏輯地址熔萧,還定義了路由實現(xiàn)的方式和學習的方式糖驴。為了適應最大傳輸單元長度小于包長度的傳輸介質(zhì),網(wǎng)絡層還定義了如何將一個包分解成更小的包的分段方法佛致。示例:IP遂赠,IPX, UUCP等。
    • 數(shù)據(jù)鏈路層: 它定義了在單個鏈路上如何傳輸數(shù)據(jù)晌杰。這些協(xié)議與被討論的各種介質(zhì)有關(guān)。示例:ATM筷弦,FDDI等肋演。
    • 物理層: OSI的物理層規(guī)范是有關(guān)傳輸介質(zhì)的特性抑诸,這些規(guī)范通常也參考了其他組織制定的標準。連接頭爹殊、幀蜕乡、幀的使用、電流梗夸、編碼及光調(diào)制等都屬于各種物理層規(guī)范中的內(nèi)容层玲。物理層常用多個規(guī)范完成對所有細節(jié)的定義。示例:Rj45反症,IEEE 802.3 等辛块。
  3. Socket

    客戶端: socket()->connect()->write()/read()->close()

    服務端: socket()->bind()->listen()->accept()->read()/write()->close()

  4. 數(shù)據(jù)解析:

    • JSON: 使用NSJSONSerialization解析
    • XML: SAX解析, DOM解析
      • SAX: NSXMLParser 實現(xiàn)代理方法解析, 特點從上往下依次解析
      • DOM: 特點一次性讀取解析
  5. 網(wǎng)絡安全

    • Base64加密, 可逆加密

    • MD5/SHA1/SHA256等 不可逆加密

    • 對稱加密和非對稱加密

      • 對稱加密: 加解密使用同一個秘鑰, 代表: DES, AES等, 有點效率高, 缺點, 秘鑰交換時安全不能保障

      • 非對稱加密: 加解密雙方使用不同的秘鑰, 加密使用公鑰加密, 解密是用私鑰解密, 公鑰是公開的 代表: RSA, ECC, DSA(數(shù)字簽名用)

  1. HTTPS

    • HTTPS中的S含義

      S代表的是SSL/TLS協(xié)議, 即: Secure Sockets layer(安全套接層)和Transport Layer Security(安全傳輸層)

    • HTTPS的建立流程

      HTTPS為了兼顧安全與效率,同時采用了對稱加密算法和非對稱加密算法, 通信過程主要會設計三個秘鑰: 服務器端的公鑰和私鑰, 用來進行非對稱加密, 客戶端隨機生成的隨機秘鑰, 用來進行對稱加密

      1. 客戶端訪問HTTPS鏈接, 告訴服務器客戶端支持的加密算法列表, 和隨機數(shù)C
      2. 服務器選擇一種對稱算法(如AES), 一種非對稱算法, 一種MAC算法發(fā)送給客戶端, 同時把數(shù)字證書和隨機數(shù)S發(fā)給客戶端
      3. 客戶端驗證服務器的數(shù)字證書, 客戶端生成前主秘鑰, 并使用服務器的公鑰加密發(fā)送個服務器, 并使用前主秘鑰/隨機數(shù)C/隨機數(shù)S, 生成會話秘鑰
      4. 服務器解密得到前主秘鑰, 通過前主秘鑰/隨機數(shù)C/隨機數(shù)S生成會話秘鑰
      5. 數(shù)據(jù)加密傳輸
  1. NSURLSession

    • NSURLSession的優(yōu)勢

      • 支持后臺上傳下載
      • 可以暫停, 停止, 重啟網(wǎng)絡任務
      • 可以對緩存策略,session類型铅碍、任務類型(比如上傳润绵、下載等任務)進行單獨的配置
      • 支持block回調(diào)使用方便
      • 支持IPV6網(wǎng)絡
    • NSURLsession的使用

      NSURLsession 是一個管理類, 可以通過NSURLSessionConfiguration進行配置

      URLSessionTask是任務的父類, 包含兩個子類URLSessionDataTask和URLSessionDownloadTask, 其中URLSessionDataTask有一個子類URLSessionUploadTask, URLSessionDataTask用來處理一般網(wǎng)絡請求

      URLSessionDownloadTask處理下載任務, URLSessionUploadTask上傳任務

2. 多線程

  1. iOS開發(fā)常用的多線程方式

    • pthread: C語言實現(xiàn), 可以跨平臺, 線程生命周期需要手動管理
    • NSThread: OC實現(xiàn), 線程生命周期需要手動管理
    • GCD: 蘋果對多核性能優(yōu)化, C語言, 線程生命周期自動管理
    • NSOperation: 對GCD封裝, 線程生命周期自動管理

    ?

  2. NSThread簡單介紹

    • 創(chuàng)建線程對象: 顯示創(chuàng)建(alloc init)和隱式創(chuàng)建(performSelector.....)

    • 線程狀態(tài):新建, 就緒(start), 阻塞(sleep), 運行, 死亡(exit)

    • 常用屬性: name(當前線程名字), threadPriority(線程優(yōu)先級), isMainThread等

  1. GCD和NSOperation對比

    • GCD執(zhí)行效率更高,而且由于隊列中執(zhí)行的是由block構(gòu)成的任務胞谈,這是一個輕量級的數(shù)據(jù)結(jié)構(gòu)尘盼,寫起來更方便
    • GCD只支持FIFO的隊列,而NSOperationQueue可以通過設置最大并發(fā)數(shù)烦绳,設置優(yōu)先級卿捎,添加依賴關(guān)系等調(diào)整執(zhí)行順序
    • NSOperationQueue甚至可以跨隊列設置依賴關(guān)系,但是GCD只能通過設置串行隊列径密,或者在隊列內(nèi)添加barrier(dispatch_barrier_async)任務午阵,才能控制執(zhí)行順序,較為復雜
    • NSOperationQueue因為面向?qū)ο螅灾С諯VO睹晒,可以監(jiān)測operation是否正在執(zhí)行(isExecuted)趟庄、是否結(jié)束(isFinished)、是否取消(isCanceled)
  2. 什么情況下會出現(xiàn)死鎖

    • 在主線程中將同步任務添加到主隊列會導致死鎖

      - (void)viewDidLoad {
          [super viewDidLoad];
          
          dispatch_sync(dispatch_get_main_queue(), ^{
             
              NSLog(@"deallock");
          });
          // Do any additional setup after loading the view, typically from a nib.
      }
      
    • 在一個串行隊列任務中將 同步任務添加到當前的隊列中

      dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
      
      dispatch_async(serialQueue, ^{
         dispatch_sync(serialQueue, ^{
             NSLog(@"deadlock");
        });
      });
      
  1. GCD中的線程柵欄

    dispatch_barrier_async/dispatch_barrier_sync 分別表示同步柵欄和異步柵欄

    柵欄的作用是可以將任務分塊執(zhí)行, 條件是任務必須在同一個隊列上, 否則無效

    dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
        
        for (NSInteger i = 0; i < 10; i++) {
            
            dispatch_async(concurrentQueue, ^{
                NSLog(@"%zd",i);
            });
        }
        
        dispatch_barrier_async(concurrentQueue, ^{
            NSLog(@"barrier");
        });
        NSLog(@"哈哈哈");
        for (NSInteger i = 10; i < 20; i++) {
            
            dispatch_async(concurrentQueue, ^{
                
                NSLog(@"%zd",i);
            });
        }
     執(zhí)行結(jié)果: 
     哈哈哈
     0-9 亂序打印
     barrier
     10-19亂序打印
     
    

    由于采用的是異步柵欄, 所以柵欄后的任務會"哈哈哈"會提起執(zhí)行, 如果將柵欄換成同步柵欄則 "哈哈哈"一定是在"barrier"之后執(zhí)行

通過線程柵欄可以實現(xiàn)多度單寫, 即允許多個地方同時讀取數(shù)據(jù), 但是在寫入數(shù)據(jù)時只允許一個地方寫入

- (id)readDataForKey:(NSString *)key {
    __block id result;
    
    dispatch_sync(_concurrentQueue, ^{
        result = [self valueForKey:key];
    });
    
    return result;
}

- (void)writeData:(id)data forKey:(NSString *)key {
    dispatch_barrier_async(_concurrentQueue, ^{
        [self setValue:data forKey:key];
    });
}

  1. GCD線程組的使用

    需求多個網(wǎng)絡請求完成之后一并刷新UI

    - (void)testGCDGroup {
        __block NSInteger number = 0;
        
        dispatch_group_t group = dispatch_group_create();
        
        //A耗時操作
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(3);
            number += 2222;
            NSLog(@"A:%zd", number);
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self sendRequestWithCompletion:^(id response) {
                number += [response integerValue];
                NSLog(@"B:%zd", number);
            }];
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self sendRequestWithCompletion:^(id response) {
                number += [response integerValue];
                NSLog(@"C:%zd", number);
            }];
        });
        
        //        //B網(wǎng)絡請求
        //        dispatch_group_enter(group);
        //        [self sendRequestWithCompletion:^(id response) {
        //            number += [response integerValue];
        //            NSLog(@"B:%zd", number);
        //            dispatch_group_leave(group);
        //
        //        }];
        //
        //        //C網(wǎng)絡請求
        //        dispatch_group_enter(group);
        //        [self sendRequestWithCompletion:^(id response) {
        //            number += [response integerValue];
        //            NSLog(@"C:%zd", number);
        //            dispatch_group_leave(group);
        //        }];
        //
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"finish: %zd", number);
        });
        
    }
    
    - (void)sendRequestWithCompletion:(void (^)(id response))completion {
        //模擬一個網(wǎng)絡請求
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            sleep(2);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (completion) completion(@1111);
            });
        });
    }
    
    調(diào)用結(jié)果:
    B:1111
    C:2222
    A:4444
    finish: 4444
    
    

    使用方式如代碼所示, 先創(chuàng)建線程組, 然后使用dispatch_group_async, 將任務綁定到線程組, 最后使用dispatch_group_notify接受線程組任務完成的回調(diào). 其中也可以使用dispatch_group_enter(group)這種方式將任務添加到線程組, 不過任務結(jié)束之后要配合使用 dispatch_group_leave(group);

  2. Dispatch Semaphore 信號量

    Dispatch Semaphore 提供了三個函數(shù)

    • dispatch_semaphore_create: 創(chuàng)建一個Semaphore并初始化總量
    • dispatch_semaphore_signal: 發(fā)送信號, 讓信號總量加1
    • dispatch_semaphore_wait: 可以是信號總量減1, 信號總量為0是進入等待

    Dispatch Semaphore 在實際開發(fā)中主要用于:

    • 保持線程同步伪很,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務

      - (void)testSemaphore {
          dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
          
          __block NSInteger number = 0;
          
          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
              number = 100;
              NSLog(@"執(zhí)行異步任務");
              dispatch_semaphore_signal(semaphore);
          });
          
          dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
          NSLog(@"semaphore---end,number = %zd",number);
      }
      //打印結(jié)果如下: 
      //執(zhí)行異步任務
      //semaphore---end,number = 100
      
      

      創(chuàng)建信號量初始化總量為0, 由于是異步執(zhí)行,所以直接執(zhí)行到dispatch_semaphore_wait, 如果信號量為0則一直等待阻斷線程, 當異步任務執(zhí)行完之后將信號量加1, 此時信號量等待函數(shù)終結(jié), 繼續(xù)執(zhí)行后面的任務

    • 保證線程安全戚啥,為線程加鎖

      - (void)testSemaphore2 {
          _semaphore = dispatch_semaphore_create(1);
       
          for (NSInteger i = 0; i < 100; i++) {
              
              dispatch_async(dispatch_get_global_queue(0, 0), ^{
                  
                  [self asyncTask];
              });
          }
      }
      
      - (void)asyncTask {
          dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
          _count++;
          sleep(1);
          NSLog(@"執(zhí)行任務:%zd",_count);
          dispatch_semaphore_signal(_semaphore);
      }
      
      ///執(zhí)行結(jié)果發(fā)現(xiàn), 是順序從1-100執(zhí)行
      

      原因: 在子線程執(zhí)行并發(fā)任務時, 由于第一次執(zhí)行任務將信號量減1, 信號總量變?yōu)?, 當?shù)诙€任務進來時需要由于信號總量為0所以進入等待狀態(tài), 任務一執(zhí)行完之后將信號量加1, 任務二開始執(zhí)行, 并立即將信號總量減1變?yōu)?, 任務三繼續(xù)等待, 依次類推, 實現(xiàn)了線程加鎖目的

  1. 使用dispatch_once實現(xiàn)單例

    //手寫單例
    - (id)sharedInstance {
         static id instance = nil;
         static dispatch_once_t onceToken;
         dispatch_once(&onceToken, ^{
                 instance = [[self alloc] init];
         })
         return instance;
    }
    
  1. NSOperationQueue/NSOperation簡介

    • 優(yōu)勢

      • 可以添加任務依賴,方便控制執(zhí)行順序

      • 可以設定操作執(zhí)行的優(yōu)先級

      • 可以設置最大并發(fā)量

      • 任務執(zhí)行狀態(tài)控制:isReady,isExecuting,isFinished,isCancelled

        如果只是重寫NSOperation的main方法锉试,由底層控制變更任務執(zhí)行及完成狀態(tài)猫十,以及任務退出
        如果重寫了NSOperation的start方法,自行控制任務狀態(tài)
        系統(tǒng)通過KVO的方式移除isFinished==YES的NSOperation

    • 基本操作

      NSOperation是抽象類, 主要使用兩個子類: NSBlockOperation和NSInvocationOperation

      可以直接添加Block任務NSOperation到隊列中執(zhí)行, 默認在當前線程執(zhí)行, NSBlockOperation添加多任務時會自動開啟新線程執(zhí)行

      也可以將NSOperation添加到NSOperationQueue中執(zhí)行操作, 當maxConcurrentOperationCount = 1時為串行隊列, 大于1時為并發(fā)隊列

    • 示例代碼

      /**
       * 設置 MaxConcurrentOperationCount(最大并發(fā)操作數(shù))
       */
      - (void)setMaxConcurrentOperationCount {
      
          // 1.創(chuàng)建隊列
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      
          // 2.設置最大并發(fā)操作數(shù)
          queue.maxConcurrentOperationCount = 1; // 串行隊列
      // queue.maxConcurrentOperationCount = 2; // 并發(fā)隊列
      // queue.maxConcurrentOperationCount = 8; // 并發(fā)隊列
      
          // 3.添加操作
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
          [queue addOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
      }
      

      執(zhí)行結(jié)果為順序執(zhí)行1,1,2,2,3,3,4,4

      如果將maxConcurrentOperationCount改為2, 則并并發(fā)執(zhí)行, 這里就不再驗證

    • NSOperation的操作依賴設置

      /**
       * 操作依賴, 操作2依賴操作1
       * 使用方法:addDependency:
       */
      - (void)addDependency {
      
          // 1.創(chuàng)建隊列
          NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      
          // 2.創(chuàng)建操作
          NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
          NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
              }
          }];
      
          // 3.添加依賴
          [op2 addDependency:op1]; // 讓op2 依賴于 op1呆盖,則先執(zhí)行op1拖云,在執(zhí)行op2
      
          // 4.添加操作到隊列中
          [queue addOperation:op1];
          [queue addOperation:op2];
      }
      
    • NSOperation 提供了queuePriority屬性, 新建操作的默認優(yōu)先級是Normal

      // 優(yōu)先級的取值
      typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
          NSOperationQueuePriorityVeryLow = -8L,
          NSOperationQueuePriorityLow = -4L,
          NSOperationQueuePriorityNormal = 0,
          NSOperationQueuePriorityHigh = 4,
          NSOperationQueuePriorityVeryHigh = 8
      };
      
    • 線程間通信示例

      /**
       * 線程間通信
       */
      - (void)communication {
      
          // 1.創(chuàng)建隊列
          NSOperationQueue *queue = [[NSOperationQueue alloc]init];
      
          // 2.添加操作
          [queue addOperationWithBlock:^{
              // 異步進行耗時操作
              for (int i = 0; i < 2; i++) {
                  [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                  NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
              }
      
              // 回到主線程
              [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  // 進行一些 UI 刷新等操作
                  for (int i = 0; i < 2; i++) {
                      [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                      NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
                  }
              }];
          }];
      }
      
  1. RunLoop

    • 什么是RunLoop

      RunLoop是通過內(nèi)部維護的事件循環(huán)來對事件進行管理的一個對象

    • 為什么main函數(shù)不會退出

      UIApplicationMain函數(shù)內(nèi)部默認開啟了主線程的RunLoop,并執(zhí)行了一段無限循環(huán)的代碼

    • RunLoop的數(shù)據(jù)結(jié)構(gòu)

      NSRunLoop(Foundation)CFRunLoop(CoreFoundation)的封裝应又,提供了面向?qū)ο蟮腁PI
      RunLoop 相關(guān)的主要涉及五個類:

      • CFRunLoop:RunLoop對象

      • CFRunLoopMode:運行模式

        1. kCFRunLoopDefaultMode:默認模式宙项,主線程是在這個運行模式下運行
        2. UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響)
        3. UIInitializationRunLoopMode:在剛啟動App時第進入的第一個 Mode株扛,啟動完成后就不再使用
        4. GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件尤筐,通常用不到
        5. kCFRunLoopCommonModes:偽模式汇荐,不是一種真正的運行模式,是同步Source/Timer/Observer到多個Mode中的一種解決方案

        蘋果對外開放的主要有kCFRunLoopDefaultModekCFRunLoopCommonModes

        一個比較常見的問題:滑動tableView時盆繁,定時器還會生效嗎掀淘?
        默認情況下RunLoop運行在kCFRunLoopDefaultMode下,而當滑動tableView時油昂,RunLoop切換到UITrackingRunLoopMode革娄,而Timer是在kCFRunLoopDefaultMode下的,就無法接受處理Timer的事件冕碟。 怎么去解決這個問題呢拦惋?把Timer添加到UITrackingRunLoopMode上并不能解決問題,因為這樣在默認情況下就無法接受定時器事件了鸣哀。
        所以我們需要把Timer同時添加到UITrackingRunLoopModekCFRunLoopDefaultMode上架忌。
        那么如何把timer同時添加到多個mode上呢?就要用到NSRunLoopCommonModes

        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
        
      • CFRunLoopSource:輸入源/事件源

      • CFRunLoopTimer:定時源

      • CFRunLoopObserver:觀察者

    • RunLoop常見問題

      - (void)test {
        NSLog(@"1");
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
              
              NSLog(@"2");
              
              [[NSRunLoop currentRunLoop] run];
              
              [self performSelector:@selector(test) withObject:nil afterDelay:10];
        
              NSLog(@"3");
        });
        NSLog(@"4");
      }
      
      - (void)test {
        NSLog(@"5");
      }
      

      輸出順序? 答案是: 1423, 5不會執(zhí)行, 原因是runloop開啟時應該有任務執(zhí)行mode中應該有item才行, 否則會退出, 所以應調(diào)整成如下代碼

      dispatch_async(dispatch_get_global_queue(0, 0), ^{
              
              NSLog(@"2");
              //先添加任務
               [self performSelector:@selector(test) withObject:nil afterDelay:10];
               //再開啟循環(huán)
              [[NSRunLoop currentRunLoop] run];
              NSLog(@"3");
        });
      
* 如何創(chuàng)建一個常駐線程

  ```objc
     @autoreleasepool {
          
          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          
          [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          
          [runLoop run];
          
      }
  ```

  

* 怎樣保證子線程數(shù)據(jù)更新回到主線程更新UI時不影響滑動操作

  答: 將更新UI的任務添加到主線程的**NSDefaultRunLoopMode** 上執(zhí)行即可, 這樣主線程將會在用戶停止滑動之后由UITrackingRunLoopMode切換到NSDefaultRunLoopMode之后更新UI

  ```objc
  [self performSelectorOnMainThread:@selector(reloadData) 
                                             withObject:nil waitUntilDone:NO
                              modes:@[NSDefaultRunLoopMode]];
  ```
  1. 自旋鎖與互斥鎖

    • 自旋鎖

      當任務被另一個線程鎖定是, 嘗試執(zhí)行的線程會進入等待(不會休眠), 等上一個線程解除鎖定時, 立即執(zhí)行下一個線程任務,

      優(yōu)點: 因為自旋鎖不會引起調(diào)用者睡眠我衬,所以不會進行線程調(diào)度叹放,CPU時間片輪轉(zhuǎn)等耗時操作。所有如果能在很短的時間內(nèi)獲得鎖挠羔,自旋鎖的效率遠高于互斥鎖

      缺點: 自旋鎖一直占用CPU井仰,他在未獲得鎖的情況下,一直運行--自旋破加,所以占用著CPU俱恶,如果不能在很短的時 間內(nèi)獲得鎖,這無疑會使CPU效率降低范舀。自旋鎖不能實現(xiàn)遞歸調(diào)用

      常用自旋鎖: atomic, dispatch_semaphore_t

    • 互斥鎖

      當上一個線程鎖定時, 下一個嘗試執(zhí)行的線程會進入休眠, 等上一個線程解除鎖定, 下一個線程自動喚醒然后執(zhí)行任務

      常用互斥鎖: NSLock, NSCondition, @ synchronized

3. UI

  1. UIView與CALayer的關(guān)系

    UIView為CALayer提供內(nèi)容合是,以及負責處理觸摸等事件,參與響應鏈
    CALayer負責顯示內(nèi)容contents

  2. 事件傳遞與響應者鏈

    • 事件傳遞過程

      • 觸摸事件發(fā)生后, 系統(tǒng)會將事件加入到UIApplication管理的事件隊列

      • UIApplication會從事件隊列中取出最前面的事件锭环,并將事件分發(fā)下去以便處理聪全,通常 先發(fā)送事件給應 用的主窗口 keyWindow,主窗口再把事件發(fā)送給rootViewController

      • rootViewController再把事件發(fā)送給他的根View辅辩,然后會在View的層次結(jié)構(gòu)中找到一個最合適的視圖來處 理觸摸事件, 找到合適的視圖控件后难礼,就會調(diào)用視圖控件的touches方法來做具體的事件處理, 注意如果

      • 找到合適的視圖控件后,就會調(diào)用視圖控件的touches方法來做具體的事件處理

            touchesBegan
            touchesMoved
            touchesEnded
        
    • 響應者鏈, 找到最合適處理事件的view

      • 判斷當前view是否能夠接收觸摸事件
      • 判斷當前的點玫锋,是否在當前view中
      • 如果點在當前的view中 遍歷當前view的所有子view蛾茉,遍歷的過程是從后往前遍歷的 如果找到子控件,返回子控件 如果沒有子控件滿足條件撩鹿,返回當前view
      //    hitTest:withEvent:方法的處理流程如下:
      //    ? hitTest:withEvent:會忽略隱藏谦炬、不和用戶交互的、透明度小于0.01的視圖
      //    ? 首先調(diào)用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內(nèi);
      //    ? 若返回NO,則hitTest:withEvent:返回nil;
      //    ? 若返回YES,則向當前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息节沦,所有
      //    子視圖的遍歷順序是從top到bottom吧寺,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖
      //      返回非空對象或者全部子視圖遍歷完畢;
      //    ? 若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象窜管,處理結(jié)束;
      //    ? 如所有子視圖都返回非,則hitTest:withEvent:方法返回自身(self)稚机。
      
      
      - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
      //1 判斷當前view,是否可以接收事件
         if (!self.userInteractionEnabled || self.alpha <= 0.01 || self.hidden) {
                 return nil; 
        }
      //2 判斷點擊的點获搏,是否在當前view中
         if (![self pointInside:point withEvent:event]) {
                 return nil; 
        }
      //3 從后往前遍歷當前view的子view赖条,是否是最合適的view,如果不是返回self 
        NSInteger count = self.subviews.count;
         for (NSInteger i =count-1;i>=0 ; i--) {
                 UIView *childView = self.subviews[i]; //把當前坐標系的點常熙,轉(zhuǎn)換成子view坐標系的點
                 CGPoint subPoint = [self convertPoint:point toView:childView]; //尋找最合適的view
             //        //判斷點是否在子控件中
                 if ([self pointInside:subPoint withEvent:event]) {
                         UIView *fitView = [childView hitTest:subPoint withEvent:event]; 
                         if (fitView) {
                             return fitView; 
                }
                 }
         }
         return self;
      }
      
  1. 圖像顯示原理

    • CPU工作

      • Layout: 布局
      • Display: 繪制
      • Prepare: 圖片解碼
      • Commit: 提交位圖
    • GPU工作

      頂點著色纬乍,圖元裝配,cell光柵化裸卫,片段著色仿贬,片段處理

  2. 滾動視圖優(yōu)化:

    • CPU角度

      • 對象創(chuàng)建銷毀,調(diào)整, 對于開銷大的對象進行優(yōu)化處理
      • 預排版: 布局計算, 文本計算, 采用緩存高度等
      • 預渲染: 文本圖片異步繪制等
    • GPU角度

      • 考慮是有有不必要的CPU渲染

      • 是否有太多的離屏渲染

      • 是否有圖層的混合操作(透明度盡量都不要使用)

      • view的層次結(jié)構(gòu)是否合理

      • cell光柵化處理

        // 光柵化
        layer.shouldRasterize = true
        layer.rasterizationScale = UIScreen.mainScreen().scale
        
        // 異步繪制
        layer.drawsAsynchronously = true
        
  1. 什么是離屏渲染(Off-Screen Rendering)

    • 與之對應的就是On-Screen Rendering(在屏渲染), GPU的渲染操作主要用于顯示當前屏幕緩沖去進行的操作, 而離屏渲染指的是GPU在當前屏幕緩沖區(qū)外新開辟緩沖區(qū)進行渲染操作

    • 離屏渲染什么時候會觸發(fā)?

      答案: 圓角, 蒙版, 陰影, 等

4. OC語言特性

  1. 分類category

    • 分類作用: 聲明私有方法, 給已存在類擴展方法(實例方法, 類方法, 協(xié)議, 屬性), 屬性需要借助運行時的關(guān)聯(lián)對象, 不能直接添加屬性

    • 分類特點: 運行時決議

  1. 擴展

    聲明私有屬性,聲明方法(沒什么意義)墓贿,聲明私有成員變量

  2. 代理

    代理是一種設計模式茧泪,以@protocol形式體現(xiàn),一般是一對一傳遞聋袋。
    一般以weak關(guān)鍵詞以規(guī)避循環(huán)引用队伟。

  3. 通知

    使用觀察者模式來實現(xiàn)的用于跨層傳遞信息的機制。傳遞方式是一對多的幽勒。

  4. KVO

    • 實現(xiàn)原理 :

      KVO的實現(xiàn)依賴于Objective-C強大的runtime嗜侮,KVO的底層實現(xiàn)是監(jiān)聽setter方法。當觀察某對象A時啥容,KVO動態(tài)機制會動態(tài)創(chuàng)建一個A類的子類NSKVONotifying_A锈颗,并為這個新的子類重寫父類的屬性的setter方法, 方法內(nèi)容如下

      [super setAge:age]; `
      [self willChangeValueForKey:@"age"];
      [self didChangeValueForKey:@"age"];
      ///其中后面兩個方法會調(diào)用
      -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
        
      
    • 作用:

      能夠監(jiān)聽某個對象屬性值的改變

  5. KVC

    通過Key值直接訪問對象屬性,或者給屬性賦值的操作, 常用操作valueforkey: / setValue: forKey:

    底層執(zhí)行機制: (屬性name舉例子)

    • 程序優(yōu)先調(diào)用屬性setName: 方法,代碼通過setter方法完成設置咪惠。
    • 如果沒有屬性name, 會按照_name击吱,_iskey,key硝逢,iskey的順序搜索成員變量
    • 如果以上都沒有會調(diào)用setValue:forUndefinedKey: 方法拋出錯誤

5 RunTime

RunTime是OC語言底層運行原理, OC代碼最終都要通過runtime去調(diào)用,

  1. 常見的runtime應用如下

    • 交換方法

      通過交換方法可以實現(xiàn)攔截系統(tǒng)方法, 添加自己的需求

      //常用方法
      class_getClassMethod 獲取類方法
      class_getInstanceMethod 獲取實例方法
      method_exchangeImplementations 方法交換
      
      在load方法中執(zhí)行狡猾
      
    • 關(guān)聯(lián)對象(給分類添加屬性)

      const char* name = "rylsj";
      - (void) setNick:(NSString *)nick {
          objc_setAssociatedObject(self, &name, nick, OBJC_ASSOCIATION_COPY_NONATOMIC);
      }
      - (NSString*) nick {
          return objc_getAssociatedObject(self, &name);
      }
      
    • 字典轉(zhuǎn)模型

      • 獲取屬性列表
    • 動態(tài)添加方法

      - (void) rylsj_AddMethod {
        //"v@:@": v表示void, @表示id姨拥, :表示 SEL
          class_addMethod([self.persion class], @selector(run:), (IMP)runMethod, "v@:@");
      }
      
      void runMethod(id self, SEL _cmd, NSString* rylsj) {
          NSLog(@"%@", rylsj);
      }
      
      //調(diào)用
       if ([self.persion respondsToSelector:@selector(run:)]) {
              [self.persion performSelector:@selector(run:) withObject:@"66 rylsj"];
       } else {
              NSLog(@"方法沒有實現(xiàn)!渠鸽!");
       }
      
  2. OC 消息轉(zhuǎn)發(fā)機制 (文中代碼有錯誤, 可以c)

    1. 首先根據(jù)receiver對象的isa指針獲取它對應的class

    2. 優(yōu)先在class的cache查找message方法叫乌,如果找不到,再到
      methodLists查找

    3. 如果沒有在class找到徽缚,再到super_class查找

    4. 一旦找到message這個方法憨奸,再依據(jù)receiver 中的self 指針找到當前的對象,調(diào)用當前對象的具體實現(xiàn)的方法(IMP),然后傳遞參數(shù),調(diào)用實現(xiàn)方法。

    5. 當對象收到無法解讀的消息后, 就會啟動"消息轉(zhuǎn)發(fā)(Message forwarding)"機制, 程序員可以通過消息轉(zhuǎn)發(fā)告訴對象應該如何處理位置消息, 過程如下:

      1. 系統(tǒng)會調(diào)用resolveInstanceMethod方法, 如果是類方法則會調(diào)用resolveClassMethod, 在這個方法中我們可以增加調(diào)用方法的實現(xiàn), 我們可以通過一個Person類來實踐一下

        @interface Person : NSObject
        - (void)run;
        @end
        @implementation Person
        ///要添加的方法
        void run (id self, SEL _cmd) {
               NSLog(@"人跑");
        }
        //在這個方法中添加自己的方法實現(xiàn)
        + (BOOL)resolveInstanceMethod:(SEL)sel {
            if(sel == @selector(run)){
              //關(guān)于生成簽名的類型"v@:"解釋一下凿试。每一個方法會默認隱藏兩個參數(shù)排宰,self似芝、_cmd,self代表方                法調(diào)用者板甘,_cmd代表這個方法的SEL党瓮,簽名類型就是用來描述這個方法的返回值、參數(shù)的盐类,v代表                返回值為void寞奸,@表示self,:表示_cmd在跳。 
                class_addMethod(self, sel, (IMP)run, "v@:");
                return YES;
            }
            return [super resolveInstanceMethod:sel];
        }
         
        @end
          
          
        @implementation ViewController
        - (void)viewDidLoad {
            [super viewDidLoad];
           Person *person = [Person new];
            [person run];
          
        }
        @end
          
        //調(diào)用結(jié)果打印: "人跑"
          
        

        如果未實現(xiàn)以上方法, 則會進入步驟二

      2. forwardingTargetForSelector: 調(diào)用

        在這個方法中可以返回你需要轉(zhuǎn)發(fā)消息的對象, 這里我們可以新建一個對象Car來演示, 在Person中不是現(xiàn)方案一中的resolveInstanceMethod方法 改成實現(xiàn)forwardingTargetForSelector方法 如下:

        @interface Car : NSObject
        - (void)run;
        @end
          
        @implementation Car
        - (void)run {
            NSLog(@"車跑");
        }
        @end
          
        @implementation Person
          
        - (id)forwardingTargetForSelector:(SEL)aSelector {
           return [Car new];
        }
          
        //執(zhí)行結(jié)果: 車跑
        
        
        

        通過forwardingTargetForSelector把消息轉(zhuǎn)發(fā)給我們認為合適的對象去執(zhí)行, 如果此步驟未實現(xiàn), 則會進入下一步

      3. 通過forwardInvocation函數(shù) 設置我們自己生成的函數(shù)簽名和對象

        //生成簽名
        - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
            NSString *sel = NSStringFromSelector(selector);
            if([sel isEqualToString:@"run"]) {
                return [NSMethodSignature signatureWithObjCTypes:"v@:"];
            }
            return [super methodSignatureForSelector:selector];
        }
        
        //設置
        - (void)forwardInvocation:(NSInvocation *)invocation {
            SEL selector = [invocation selector];
            //新建需要轉(zhuǎn)發(fā)消息的對象
            Car *car = [[Car alloc] init];
            if([car respondsToSelector:selector]){
                [invocation invokeWithTarget:car];
            }
        }
        
        //執(zhí)行結(jié)果: 車跑
        
        

        以上是當前類未實現(xiàn)方法的三種不就措施, 調(diào)用順序從前往后, 如果實現(xiàn)了第一種, 后面就不會執(zhí)行以此類推, 如果都沒實現(xiàn)程序就會crash

6. 內(nèi)存

  1. 內(nèi)存布局, 從代碼區(qū)到棧區(qū)地址一次從低到高

    • 棧區(qū)(stack): 方法調(diào)用枪萄,局部變量等,是連續(xù)的猫妙,高地址往低地址擴展, 系統(tǒng)自動管理
    • 堆區(qū)(heap): 通過alloc等分配的對象瓷翻,是離散的,低地址往高地址擴展割坠,需要我們手動控制
    • 未初始化數(shù)據(jù)(bss): 未初始化的全局變量等
    • 已初始化數(shù)據(jù)(data): 已初始化的全局變量等
    • 代碼段(text): 程序代碼 (常量區(qū))
  2. static關(guān)鍵字的作用

    • 用于修飾存儲類型使之成為靜態(tài)存儲類型

      在函數(shù)內(nèi)定義的靜態(tài)局部變量齐帚,該變量存在內(nèi)存的靜態(tài)區(qū),所以即使該函數(shù)運行結(jié)束韭脊,靜態(tài)變量的值不會被銷毀童谒,函數(shù)下次運行時能仍用到這個值。
      在函數(shù)外定義的靜態(tài)變量——靜態(tài)全局變量沪羔,該變量的作用域只能在定義該變量的文件中饥伊,不能被其他文件通過extern引用。
      
    • 用于修飾鏈接屬性使之成為內(nèi)部鏈接屬性

      靜態(tài)函數(shù)只能在聲明它的源文件中使用蔫饰。
      
  1. const關(guān)鍵字

    • 聲明常變量琅豆,使得指定的變量不能被修改。

      const int a = 5;/*a的值一直為5篓吁,不能被改變*/
      const int b; b = 10;/*b的值被賦值為10后茫因,不能被改變*/
      const int *ptr; /*ptr為指向整型常量的指針,ptr的值可以修改杖剪,但不能修改其所指向的值*/
      int *const ptr;/*ptr為指向整型的常量指針冻押,ptr的值不能修改,但可以修改其所指向的值*/
      const int *const ptr;/*ptr為指向整型常量的常量指針盛嘿,ptr及其指向的值都不能修改*/
      
  • 修飾函數(shù)形參洛巢,使得形參在函數(shù)內(nèi)不能被修改,表示輸入?yún)?shù)

    int fun(const int a);或int fun(const char *str);
    
  • 修飾函數(shù)返回值次兆,使得函數(shù)的返回值不能被修改稿茉。

    const char *getstr(void);使用:const *str= getstr();
    
    const int getint(void);  使用:const int a =getint();
    
  1. iOS 得內(nèi)存管理機制?

    采用的是引用計數(shù)的方式進行內(nèi)存管理, MRC下需要用戶手動管理引用計數(shù), ARC下, 系統(tǒng)禁用retain,release,retainCount,autorelease等關(guān)鍵字

  2. 自動釋放池

    在當次runloop將要結(jié)束的時候調(diào)用objc_autoreleasePoolPop漓库,并push進來一個新的AutoreleasePool, AutoreleasePoolPage是以棧為結(jié)點通過雙向鏈表的形式組合而成恃慧,是和線程一一對應的。內(nèi)部屬性有parent渺蒿,child對應前后兩個結(jié)點痢士,thread對應線程 ,next指針指向棧中下一個可填充的位置蘸嘶。

    實現(xiàn)原理:

    編譯器會將 @autoreleasepool {} 改寫為:

    void * ctx = objc_autoreleasePoolPush;
        {}
    objc_autoreleasePoolPop(ctx);
    
    • objc_autoreleasePoolPush:

      把當前next位置置為nil良瞧,即哨兵對象,然后next指針指向下一個可入棧位置,
      AutoreleasePool的多層嵌套训唱,即每次objc_autoreleasePoolPush,實際上是不斷地向棧中插入哨兵對象挚冤。

    • objc_autoreleasePoolPop:

      根據(jù)傳入的哨兵對象找到對應位置况增。給上次push操作之后添加的對象依次發(fā)送release消息⊙档玻回退next指針到正確的位置澳骤。

  3. 循環(huán)引用場景

    • 代理(delegate)引起的相互循環(huán)引用

      解決方案: 聲明地阿里是,使用weak關(guān)鍵字修飾

    • NSTimer引起的循環(huán)引用

      解決方案: 在合適的時候進行invalidate 并將指針指向nil

    • Block引起的循環(huán)引用

      對block中使用的self進行弱化 __weak typeof(self) weakSelf = self;

      注意: 并不是所有block都會造成循環(huán)引用, 只有被強引用了的block才會產(chǎn)生循環(huán)引用

  1. Block的內(nèi)存問題

    • Block的三種形式

      • 全局Block(_NSConcreteGlobalBlock), 存儲在已初始化的數(shù)據(jù)區(qū)(.data)

        不使用外部變量的是全局Block, 如下:

        NSLog(@"%@",[^{
             NSLog(@"globalBlock");
        } class]);
        
        //輸出: __NSGlobalBlock__
        
      • 棧Block(_NSConcreteStackBlock), 存儲在棧區(qū)(Stack)

        使用外部變量但是未進行copy操作的Block是棧Block, 如下:

        NSInteger num = 10;
        NSLog(@"%@",[^{
             NSLog(@"stackBlock:%zd",num);
        } class]);
        
        //輸出: __NSStackBlock__
        
      • 堆Block(_NSConcreteMallocBlock), 存儲在堆區(qū)(Heap)

        對棧Block進行copy操作,就是堆block澜薄,而對全局Block進行copy为肮,仍是全局Block, 如下:

        //對堆Block copy操作, 依然是全局Block
        void (^globalBlock)(void) = ^{
             NSLog(@"globalBlock");
        };
        NSLog(@"%@",[globalBlock class]);
        //輸出: __NSGlobalBlock__
        
        
        
        //對棧Block進行copy,是堆Block
        NSInteger num = 10;
        void (^mallocBlock)(void) = ^{
             NSLog(@"stackBlock:%zd",num);
        };
        NSLog(@"%@",[mallocBlock class]);
        //輸出: __NSMallocBlock__
        
        

        注意: 對棧Block copy之后,并不代表著棧Block就消失了肤京,左邊的mallock是堆Block颊艳,右邊被copy的仍是棧Block

        - (void)testWithBlock:(dispatch_block_t)block {
            block();
            
            dispatch_block_t tempBlock = block;
            
            NSLog(@"%@,%@",[block class],[tempBlock class]);
        }
        //輸出: __NSStackBlock__,__NSMallocBlock__
        

7. 數(shù)據(jù)存儲

  1. iOS中常用數(shù)據(jù)持久化方式

    • NSUserDefaults, 適用于輕量數(shù)據(jù)存儲

      偏好設置會將所有數(shù)據(jù)保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件忘分。

      NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
      [defaults setObject:@"jack" forKey:@"firstName"];
      [defaults setInteger:10 forKey:@"Age"];
      ///現(xiàn)在可不手動調(diào)用同步方法
      [defaults synchronize];
      
    • 文件寫入本地(數(shù)組, 字典, 字符串等) 如下:

      NSString *filePath = [[self getDocumentPath] stringByAppendingString:@"fileTest.txt"];
      NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"798293@qq.com", @"email", @"787907@qq.com", @"emailDisplay", nil];
      [dictionary writeToFile:filePath atomically:YES];
      
      
    • 對象歸檔

      需要對象實現(xiàn)NSCoding協(xié)議

      NSString *filePath = [[self getDocumentPath] 
      - (void)encodeWithCoder:(NSCoder *)aCoder {
        [aCoder encodeObject:_name forKey:kAddressCardName];
      }
      - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
        _name = [aDecoder decodeObjectForKey:kAddressCardName];
      }
      //存儲
      [NSKeyedArchiver archiveRootObject:obj toFile:filePath];
      
    • 數(shù)據(jù)庫(SQLite, CoreData)

      • SQLite 常用函數(shù)

        sqlite3_open() //創(chuàng)建并打開數(shù)據(jù)庫連接
        sqlite3_exec() //執(zhí)行數(shù)據(jù)庫操作(crud)
        
 * SQLite常用語句

   * DDL(Data Definition Language) 數(shù)據(jù)庫定義語句

     ```sql
     --create創(chuàng)表,
     create table t_student (id integer, name text, age inetger, score real);
     
     --drop刪除表
     drop table if exists t_student; 
     
     ```

   * DML(Data Manipulation Language) 數(shù)據(jù)庫操作語句

     ```sql
     --insert棋枕、
     insert into t_student (name, age) values (‘wg’, 10);
     --update、
     update t_student set name = ‘jack’, age = 20;
     --delete
     delete from t_student;
     
     ```

   * DQL(Data Query Language) 數(shù)據(jù)庫查詢語句

     ```sql
     -- select
     -- 查指定字段
     select name, age from t_student;
     -- 查全部
     select * from t_student ;
     -- 查數(shù)量
     select count (age) from t_student ;
     
     
     ```

   * 常用關(guān)鍵字: where妒峦,order by重斑,group by和having等

     ```sql
     --where關(guān)鍵字示例
     delete from t_student where age <= 10 or age > 30 ;
     select * from t_student where age > 10 ;  
     -- 排序
     select * from t_student order by age desc 
     -- limit條件限制
     select * from t_student limit 4, 8 ;
     
     ```

   * 別名

     ```sql
     -- select 字段1 別名 , 字段2 別名 , … from 表名 別名 ; 
     select name myname, age myage from t_student;
     ```

8. 音視頻開發(fā)

  1. 音頻

    • 音效播放 (AVFoundation), 支持acc , wav 等格式, 特征: 比較短 30秒以內(nèi)

      //創(chuàng)建音效
       NSString *path = [[NSBundle mainBundle]pathForResource:"music.acc" ofType:nil];
      NSURL *url  = [NSURL fileURLWithPath:path];
      AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
      //播放音效, 靜音無振動
      AudioServicesPlaySystemSound(soundID);
      //靜音有震動
      //AudioServicesPlayAlertSound(soundID);
      
      
      
    • 音樂播放 (AVAudioPlayer: 只能播放本地音樂, 不支持網(wǎng)絡媒體)

      //1.創(chuàng)建一個音樂對象
      NSString *path = [[NSBundle mainBundle]pathForResource:@"music.mp3" ofType:nil];
      NSURL *url = [NSURL fileURLWithPath:path];
      AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
      
      //2. 準備, 播放
      [audioPlayer prepareToPlay];
          //播放
      [audioPlayer play];
      
    • 音頻錄制(AVAudioRecorder)

      //保存路徑
       NSURL *url = [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"recoder001.wav"]];
      //錄音參數(shù)設置: AVSampleRateKey采樣率 AVNumberOfChannelsKey音頻通道數(shù), AVLinearPCMBitDepthKey線性音頻深度, 
      NSMutableDictionary *settings = [NSMutableDictionary dictionary];
      // 音頻采樣率
      settings[AVSampleRateKey] = @(8000.0);
      
      //創(chuàng)建音頻錄音機
      AVAudioRecorder *recorder = [[AVAudioRecorder alloc]initWithURL:url settings:settings error:&error];
      
      //錄音
      [recorder record];
      //停止錄音
      [self.recorder stop];
      
      
      
    • 網(wǎng)絡媒體流播放(AVPlayer, 見下文視頻播放描述)

  2. 視頻

    • 幾種視頻播放

      首先在iOS平臺使用播放視頻,可用的選項一般有這四個肯骇,他們各自的作用和功能如下:

      使用環(huán)境 優(yōu)點 缺點
      MPMoviePlayerController MediaPlayer 簡單易用, 自帶頁面 不可定制
      AVPlayerViewController AVKit 簡單易用, 自帶頁面 不可定制
      AVPlayer AVFoundation 可定制度高窥浪,功能強大 不支持流媒體
      IJKPlayer IJKMediaFramework 定制度高,支持流媒體播放 使用稍復雜
    • AVPlayer

      • 播放音頻

        NSURL *playUrl = [NSURL URLWithString:@"http://www.xxx.com/music.mp3"];
        self.player = [[AVPlayer alloc] initWithURL:playUrl];
        [self.player play];//播放
        [self.player pause];//暫停
        self.player.rate = 1.5//倍速
        
        
      • 播放視頻 需要創(chuàng)建顯示層AVPlayerLayer

  • 直播

    • 主播端:

      1. 獲取音視頻授權(quán)

      2. 配置采樣參數(shù)

        • 音頻: 碼率和采樣率
        • 視頻: 分辨率, 幀率, 碼率
      3. 采集數(shù)據(jù)

        • 音頻采集: AVAudioSession
        • 視頻采集: GPUImageVideoCamera
      4. 編碼

        • 音頻編碼: 軟編碼(h.264, mpeg等,第三方SDK)/硬編碼(AudioToolbox, 原生)
        • 視頻編碼: 軟編碼(mp3, acc等, 第三方sdk)/硬編碼(VideoToolbox, 原生)
      5. 推流

        • 音視頻封裝(flv或者TS格式)
        • 協(xié)議(RTMP, HLS, FLV)
        • 數(shù)據(jù)推送
    • 服務器端

      • 數(shù)據(jù)分發(fā)(CDN)
      • 截屏
      • 錄制
      • 轉(zhuǎn)碼
    • 用戶端

      1. 拉流: RTMP, HLS, FLV
      2. 解碼: 軟解碼和硬解碼
      3. 播放: IJKPlayer 開源播放器播放
    • 直播常見問題 請參考文章 iOS開發(fā)之移動直播技術(shù)秒開笛丙、直播優(yōu)化經(jīng)驗漾脂、直播問題解析、直播知識解惑

9. iOS開發(fā)中的動畫

  1. 核心動畫(CoreAnimation)

    • 核心動畫的分類(CAAnimation類的繼承結(jié)構(gòu))

      CAAnimation有三個子類: CAAnimationGroup(組動畫), CAPropertyAnimation(屬性動畫), CATranstion(轉(zhuǎn)場動畫), 其中屬性動畫又分為: CABasicAnimation(基礎(chǔ)動畫)和CAKeyframeAnimation(關(guān)鍵幀動畫)

  1. 基礎(chǔ)動畫

    
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末若债,一起剝皮案震驚了整個濱河市符相,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖啊终,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜豹,死亡現(xiàn)場離奇詭異,居然都是意外死亡蓝牲,警方通過查閱死者的電腦和手機趟脂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來例衍,“玉大人昔期,你說我怎么就攤上這事》鹦” “怎么了硼一?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梦抢。 經(jīng)常有香客問我般贼,道長,這世上最難降的妖魔是什么奥吩? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任哼蛆,我火速辦了婚禮,結(jié)果婚禮上霞赫,老公的妹妹穿的比我還像新娘腮介。我一直安慰自己,他們只是感情好端衰,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布叠洗。 她就那樣靜靜地躺著,像睡著了一般靴迫。 火紅的嫁衣襯著肌膚如雪惕味。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天玉锌,我揣著相機與錄音名挥,去河邊找鬼。 笑死主守,一個胖子當著我的面吹牛禀倔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播参淫,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼救湖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涎才?” 一聲冷哼從身側(cè)響起鞋既,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤力九,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邑闺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跌前,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年陡舅,在試婚紗的時候發(fā)現(xiàn)自己被綠了抵乓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡靶衍,死狀恐怖灾炭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颅眶,我是刑警寧澤蜈出,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站涛酗,受9級特大地震影響掏缎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜煤杀,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沪哺。 院中可真熱鬧沈自,春花似錦、人聲如沸辜妓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽籍滴。三九已至酪夷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孽惰,已是汗流浹背晚岭。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勋功,地道東北人坦报。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像狂鞋,于是被迫代替她去往敵國和親片择。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359