【2019年最新】 iOS面試題及答案

  1. 設計模式是什麼? 你知道哪些設計模式,並簡要敘述?

    設計模式是一種編碼經驗,就是用比較成熟的邏輯去處理某一種類型的事情。
    1). MVC模式:Model View Control,把模型 視圖 控制器 層進行解耦合編寫。
    2). MVVM模式:Model View ViewModel 把模型 視圖 業務邏輯 層進行解耦和編寫。
    3). 單例模式:通過static關鍵詞,聲明全局變量。在整個進程運行期間只會被賦值一次。
    4). 觀察者模式:KVO是典型的通知模式,觀察某個屬性的狀態,狀態發生變化時通知觀察者。
    5). 委託模式:代理+協議的組合。實現1對1的反向傳值操作。
    6). 工廠模式:通過一個類方法,批量的根據已有模板生產對象。
  2. MVC 和 MVVM 的區別

    
     

    MVC是一種架構模式,M表示Model,V表示視圖View,C表示控制器Controller:

    • Model負責存儲、定義、操作數據;
    • View用來展示給用戶,並且和用戶進行交互;
    • Controller是Model和View的協調者,Controller把Model中的數據拿過來給View使用。Controller可以直接與Model和View進行通信,而View不能與Controller直接通信。,當有數據更新時,Model也要與Controller進行通信,這個時候就要用Notification和KVO,這個方式就像發廣播一樣,Model發信號,Controller設置接收監聽信號,當有數據更新是就發信號給Controller,Model和View不能直接通信,這樣違背MVC設計原則。View與Controller通信需要利用代理協議的方式,Controller可以直接根據Model決定View的展示。View如果接受響應事件則通過delegate,target-action,block等方式告訴Controller的狀態變化。Controller進行業務的處理,然後再控制View的展示。

    那這樣Model和View就是相互獨立的。View只負責頁面的展示,Model只是數據的存儲,那麼也就達到了解耦和重用的目的。 
    實例:假設蘋果根據買iPhone的人給予不同的優惠,學生優惠20%,it民工優惠50%,其他不優惠。

    
     
    • MVVM設計模式

    MVVM就是幫忙分擔一下controller裏面的部分業務邏輯。 
    這裏寫圖片描述 
    這個時候,controller將不再直接和真實的model進行綁定了,而通過ViewModel,viewModel進而持有真實的Model。 
    實例:

    看到修改完的代碼,你會發現VC裏面已經省去了不少的代碼。一切都和viewModel進行交流。這裏我只是展示一個最簡單的數據展示,如果有其他響應事件,是需要viewModel開放方法來進行處理的,並要通知VC處理結果的。

    關於MVVM的優點:

    • 方便測試

    在MVC下,Controller基本是無法測試的,裏面混雜了個各種邏輯,而且分散在不同的地方。有了MVVM我們就可以測試裏面的viewModel,來驗證我們的處理結果對不對(Xcode7的測試已經越來越完善了)。

    • 便於代碼的移植

    比如iOS裏面有iPhone版本和iPad版本,除了交互展示不一樣外,業務邏輯的model是一致的。這樣,我們就可以以很小的代價去開發另一個app。

    • 兼容MVC

    MVVM是MVC的一個升級版,目前的MVC也可以很快的轉換到MVVM這個模式。VC可以省去一大部分展示邏輯。

    缺點:

    • 類會增多

    每個VC都附帶一個viewModel,類的數量*2

    • viewModel會越來越龐大

    我們把邏輯給了viewModel,那勢必Model也會變得很複雜,裏面的屬性和方法越來越多。可能重寫的方法比較多,因爲涉及到一些數據的轉換以及和controller之間的通信。

    • 調用複雜度增加

    由於數據都是從viewModel來,想想突然來了一個新人,一看代碼,不知道真實的模型是誰。比如常用tableview的數據源,一般都是一個數組,如果不斷的通過viewModel去取,溝通上沒有那麼直接。況且每封一層,意味着要寫很多代碼去融合他們的轉換。

  3. #import跟 #include 有什麼區別,@class呢,#import<> 跟 #import””有什麼區別?

    
     

    (1)#import指令是Object-C針對@include的改進版本,能確保引用的文件只會被引用一次,不會陷入遞歸包含的問題中;

    (2)@import與@class的區別:

        #import會鏈入該頭文件的全部信息,包括實體變量和方法等;二@class只是告訴編譯器,其後面聲明的名稱是類的名稱,至於這些類如何定義的,暫時不用考慮。在頭文件中,一般只需要知道被引用的類的名稱就可以了,不需要知道其內部的實體變量和方法,所以在頭文件中一般使用@class來聲明這個名稱是類的名稱;而在實現類裏面,因爲會用到這個引用類的內部的實體變量和方法,所以需要使用#import類包含這個被引用類的頭文件。

    @class還可以解決循環包含的問題

    (3)#import<>跟#import""的區別:

    #import<>用來包含系統自帶的文件,#import""用來包含自定義的文件

    (4)屬性readwrite,readonly,assign,retain,copy,nonatomic 各是什麼作用,在那種情況下用?

    • readwrite:是可讀可寫特性,同時生成get方法和set方法的聲明和實現(補充:默認屬性,將生成不帶額外參數的getter和setter方法(setterff只有一個參數))

    • readonly:只讀特性,只會生成get方法的聲明和實現;不希望屬性在類外改變

    • assign:是賦值特性,set方法的實現是直接賦值,用於基本數據類型;僅設置變量時

    • retain:表示持有特性,set方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1;

    • copy:表示拷貝特性,set方法的實現是release舊值,copy新值,用於NSString、block等類型(set方法將傳入的對象複製一份;需要完全一份新的變量時使用);

    • nonatomic:非原子操作,決定編譯器生成的setter getter是否是原子操作,atomic表示多線程安全,一般使用nonatomic

  4. frame 和 bounds 有什麼不同?

    frame指的是:該view在父view座標系統中的位置和大小。(參照點是父view的座標系統)
    bounds指的是:該view在本身座標系統中的位置和大小。(參照點是本身座標系統)
    
    
  5. Objective-C的類可以多重繼承麼?可以實現多個接口麼?Category是什麼?重寫一個類的方式用繼承好還是分類好?爲什麼?

    答:Objective-C的類不可以多重繼承;可以實現多個接口(協議);Category是類別;一般情況用分類好,用Category去重寫類的方法,僅對本Category有效,不會影響到其他類與原有類的關係。
    
    
  6. @property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的

    @property 的本質是什麼?
    	@property = ivar + getter + setter;
    “屬性” (property)有兩大概念:ivar(實例變量)、getter+setter(存取方法)
    
    “屬性” (property)作爲 Objective-C 的一項特性,主要的作用就在於封裝對象中的數據。 Objective-C 對象通常會把其所需要的數據保存爲各種實例變量。實例變量一般通過“存取方法”(access method)來訪問。其中,“獲取方法” (getter)用於讀取變量值,而“設置方法” (setter)用於寫入變量值。
    
    
  7. @property中有哪些屬性關鍵字?/ @property 後面可以有哪些修飾符?

    屬性可以擁有的特質分爲四類:
    1.原子性--- nonatomic 特質
    2.讀/寫權限---readwrite(讀寫)、readonly (只讀)
    3.內存管理語義---assign、strong、 weak、unsafe_unretained、copy
    4.方法名---getter=<name> 、setter=<name>
    5.不常用的:nonnull,null_resettable,nullable
    
    
  8. 屬性關鍵字 readwrite,readonly,assign,retain,copy,nonatomic 各是什麼作用,在那種情況下用?

    答:
    1). readwrite 是可讀可寫特性。需要生成getter方法和setter方法。
    2). readonly 是隻讀特性。只會生成getter方法,不會生成setter方法,不希望屬性在類外改變。
    3). assign 是賦值特性。setter方法將傳入參數賦值給實例變量;僅設置變量時,assign用於基本數據類型。
    4). retain(MRC)/strong(ARC) 表示持有特性。setter方法將傳入參數先保留,再賦值,傳入參數的retaincount會+1。
    5). copy 表示拷貝特性。setter方法將傳入對象複製一份,需要完全一份新的變量時。
    6). nonatomic 非原子操作。決定編譯器生成的setter和getter方法是否是原子操作,atomic表示多線程安全,一般使用nonatomic,效率高。
    
    
  9. 什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?

    1.在 ARC 中,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性。
    2.自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。
    
    IBOutlet連出來的視圖屬性爲什麼可以被設置成weak?
    	因爲父控件的subViews數組已經對它有一個強引用。
    
    不同點:
    assign 可以用非 OC 對象,而 weak 必須用於 OC 對象。
    weak 表明該屬性定義了一種“非擁有關係”。在屬性所指的對象銷燬時,屬性值會自動清空(nil)。
    
    
  10. 怎麼用 copy 關鍵字?

     用途:
     1. NSString、NSArray、NSDictionary 等等經常使用copy關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
     2. block 也經常使用 copy 關鍵字。
    
     說明:
     block 使用 copy 是從 MRC 遺留下來的“傳統”,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 可以把它放到堆區.在 ARC 中寫不寫都行:對於 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行了 copy 操作。如果不寫 copy ,該類的調用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行了 copy 操作”,他們有可能會在調用之前自行拷貝屬性值。這種操作多餘而低效。
    
    
  11. 用@property聲明的 NSString / NSArray / NSDictionary 經常使用 copy 關鍵字,爲什麼?如果改用strong關鍵字,可能造成什麼問題?

    答:用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因爲他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作(就是把可變的賦值給不可變的),爲確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
    
    1. 因爲父類指針可以指向子類對象,使用 copy 的目的是爲了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本。
    2. 如果我們使用是 strong ,那麼這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那麼會影響該屬性。
    
    //總結:使用copy的目的是,防止把可變類型的對象賦值給不可變類型的對象時,可變類型對象的值發送變化會無意間篡改不可變類型對象原來的值。
    
    
  12. 淺拷貝和深拷貝的區別?

    答:
    淺拷貝:只複製指向對象的指針,而不復制引用對象本身。
    深拷貝:複製引用對象本身。內存中存在了兩份獨立對象本身,當修改A時,A_copy不變。
    
    
  13. 系統對象的 copy 與 mutableCopy 方法

    不管是集合類對象(NSArray、NSDictionary、NSSet ... 之類的對象),還是非集合類對象(NSString, NSNumber ... 之類的對象),接收到copy和mutableCopy消息時,都遵循以下準則:
    1. copy 返回的是不可變對象(immutableObject);如果用copy返回值調用mutable對象的方法就會crash。
    2. mutableCopy 返回的是可變對象(mutableObject)。
    
    一、非集合類對象的copy與mutableCopy
      在非集合類對象中,對不可變對象進行copy操作,是指針複製,mutableCopy操作是內容複製;
      對可變對象進行copy和mutableCopy都是內容複製。用代碼簡單表示如下:
    	NSString *str = @"hello word!";
    	NSString *strCopy = [str copy] // 指針複製,strCopy與str的地址一樣
    	NSMutableString *strMCopy = [str mutableCopy] // 內容複製,strMCopy與str的地址不一樣
    
    	NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];
    	NSString *strCopy = [mutableStr copy] // 內容複製
    	NSMutableString *strMCopy = [mutableStr mutableCopy] // 內容複製
    
    二、集合類對象的copy與mutableCopy (同上)
      在集合類對象中,對不可變對象進行copy操作,是指針複製,mutableCopy操作是內容複製;
      對可變對象進行copy和mutableCopy都是內容複製。但是:集合對象的內容複製僅限於對象本身,對集合內的對象元素仍然是指針複製。(即單層內容複製)
        NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
        NSArray *copyArr = [arr copy]; // 指針複製
        NSMutableArray *mCopyArr = [arr mutableCopy]; //單層內容複製
       
        NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
        NSArray *copyArr = [mutableArr copy]; // 單層內容複製
        NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 單層內容複製
        
    【總結一句話】:
    	只有對不可變對象進行copy操作是指針複製(淺複製),其它情況都是內容複製(深複製)!
    
    
  14. 這個寫法會出什麼問題:@property (nonatomic, copy) NSMutableArray *arr;

    問題:添加,刪除,修改數組內的元素的時候,程序會因爲找不到對應的方法而崩潰。
    //如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
    // copy後返回的是不可變對象(即 arr 是 NSArray 類型,NSArray 類型對象不能調用 NSMutableArray 類型對象的方法)
    原因:是因爲 copy 就是複製一個不可變 NSArray 的對象,不能對 NSArray 對象進行添加/修改。
    
    
  15. 如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?

    若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對象分爲可變版本與不可變版本,那麼就要同時實現 NSCopying 與 NSMutableCopying 協議。
    具體步驟:
    	1. 需聲明該類遵從 NSCopying 協議
    	2. 實現 NSCopying 協議的方法。
    		// 該協議只有一個方法: 
    		- (id)copyWithZone:(NSZone *)zone;
    		// 注意:使用 copy 修飾符,調用的是copy方法,其實真正需要實現的是 “copyWithZone” 方法。
    
    
  16. 寫一個 setter 方法用於完成 @property (nonatomic, retain) NSString *name,寫一個 setter 方法用於完成 @property (nonatomic, copy) NSString *name

    答:
    // retain
    - (void)setName:(NSString *)str {
      [str retain];
      [_name release];
      _name = str;
    }
    // copy
    - (void)setName:(NSString *)str {
      id t = [str copy];
      [_name release];
      _name = t;
    }
    
    
  17. @synthesize 和 @dynamic 分別有什麼作用?

    @property有兩個對應的詞,一個是@synthesize(合成實例變量),一個是@dynamic。
    如果@synthesize和@dynamic都沒有寫,那麼默認的就是 @synthesize var = _var;
    // 在類的實現代碼裏通過 @synthesize 語法可以來指定實例變量的名字。(@synthesize var = _newVar;)
    1. @synthesize 的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動爲你加上這兩個方法。
    2. @dynamic 告訴編譯器,屬性的setter與getter方法由用戶自己實現,不自動生成(如,@dynamic var)。
    
    
  18. 常見的 Objective-C 的數據類型有那些,和C的基本數據類型有什麼區別?如:NSInteger和int

    答:
    Objective-C的數據類型有NSString,NSNumber,NSArray,NSMutableArray,NSData等等,這些都是class,創建後便是對象,而C語言的基本數據類型int,只是一定字節的內存空間,用於存放數值;NSInteger是基本數據類型,並不是NSNumber的子類,當然也不是NSObject的子類。NSInteger是基本數據類型Int或者Long的別名(NSInteger的定義typedef long NSInteger),它的區別在於,NSInteger會根據系統是32位還是64位來決定是本身是int還是long。
    
    
  19. id 聲明的對象有什麼特性?

    答:id 聲明的對象具有運行時的特性,即可以指向任意類型的Objcetive-C的對象。
    
    
  20. Objective-C 如何對內存管理的,說說你的看法和解決方法?

    答:Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。
    1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。
    2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。
    3). 內存釋放池Release Pool:把需要釋放的內存統一放在一個池子中,當池子被抽乾後(drain),池子中所有的內存空間也被自動釋放掉。內存池的釋放操作分爲自動和手動。自動釋放受runloop機制影響。
    
    
  21. Objective-C 中創建線程的方法是什麼?如果在主線程中執行代碼,方法是什麼?如果想延時執行代碼、方法又是什麼?

    答:線程創建有三種方法:使用NSThread創建、使用GCD的dispatch、使用子類化的NSOperation,然後將其加入NSOperationQueue;在主線程執行代碼,方法是performSelectorOnMainThread,如果想延時執行代碼可以用performSelector:onThread:withObject:waitUntilDone:
    
    
  22. Category(類別)、 Extension(擴展)和繼承的區別

    區別:
    1. 分類有名字,類擴展沒有分類名字,是一種特殊的分類。
    2. 分類只能擴展方法(屬性僅僅是聲明,並沒真正實現),類擴展可以擴展屬性、成員變量和方法。
    3. 繼承可以增加,修改或者刪除方法,並且可以增加屬性。
    
    
  23. 我們說的OC是動態運行時語言是什麼意思?

    答:主要是將數據類型的確定由編譯時,推遲到了運行時。簡單來說, 運行時機制使我們直到運行時纔去決定一個對象的類別,以及調用該類別對象指定方法。
    
    
  24. 爲什麼我們常見的delegate屬性都用是week而不是retain/strong?

    答:是爲了防止delegate兩端產生不必要的循環引用。
    @property (nonatomic, weak) id<UITableViewDelegate> delegate;
    
    
  25. 什麼時候用delete,什麼時候用Notification?

    Delegate(委託模式):1對1的反向消息通知功能。
    Notification(通知模式):只想要把消息發送出去,告知某些狀態的變化。但是並不關心誰想要知道這個。
    
    
  26. 什麼是 KVO 和 KVC?

    1). KVC(Key-Value-Coding):鍵值編碼 是一種通過字符串間接訪問對象的方式(即給屬性賦值)
       	舉例說明:
       	stu.name = @"張三" // 點語法給屬性賦值
       	[stu setValue:@"張三" forKey:@"name"]; // 通過字符串使用KVC方式給屬性賦值
       	stu1.nameLabel.text = @"張三";
       	[stu1 setValue:@"張三" forKey:@"nameLabel.text"]; // 跨層賦值
    2). KVO(key-Value-Observing):鍵值觀察機制 他提供了觀察某一屬性變化的方法,極大的簡化了代碼。
         KVO只能被KVC觸發,包括使用setValue:forKey:方法和點語法。
       // 通過下方方法爲屬性添加KVO觀察
       - (void)addObserver:(NSObject *)observer
                         forKeyPath:(NSString *)keyPath
                         options:(NSKeyValueObservingOptions)options
                         context:(nullable void *)context;
       // 當被觀察的屬性發送變化時,會自動觸發下方方法                   
       - (void)observeValueForKeyPath:(NSString *)keyPath
                                  ofObject:(id)object
                                      change:(NSDictionary *)change
                                     context:(void *)context{}
    	
    KVC 和 KVO 的 keyPath 可以是屬性、實例變量、成員變量。
    
    
  27. KVC的底層實現?

    當一個對象調用setValue方法時,方法內部會做以下操作:
    1). 檢查是否存在相應的key的set方法,如果存在,就調用set方法。
    2). 如果set方法不存在,就會查找與key相同名稱並且帶下劃線的成員變量,如果有,則直接給成員變量屬性賦值。
    3). 如果沒有找到_key,就會查找相同名稱的屬性key,如果有就直接賦值。
    4). 如果還沒有找到,則調用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
    這些方法的默認實現都是拋出異常,我們可以根據需要重寫它們。
    
    
  28. KVO的底層實現?

    KVO基於runtime機制實現。
    
    
  29. ViewController生命週期

    按照執行順序排列:
    1. initWithCoder:通過nib文件初始化時觸發。
    2. awakeFromNib:nib文件被加載的時候,會發生一個awakeFromNib的消息到nib文件中的每個對象。      
    3. loadView:開始加載視圖控制器自帶的view。
    4. viewDidLoad:視圖控制器的view被加載完成。  
    5. viewWillAppear:視圖控制器的view將要顯示在window上。
    6. updateViewConstraints:視圖控制器的view開始更新AutoLayout約束。
    7. viewWillLayoutSubviews:視圖控制器的view將要更新內容視圖的位置。
    8. viewDidLayoutSubviews:視圖控制器的view已經更新視圖的位置。
    9. viewDidAppear:視圖控制器的view已經展示到window上。 
    10. viewWillDisappear:視圖控制器的view將要從window上消失。
    11. viewDidDisappear:視圖控制器的view已經從window上消失。
    
    
  30. 方法和選擇器有何不同?

    selector是一個方法的名字,方法是一個組合體,包含了名字和實現。
    
    
  31. 你是否接觸過OC中的反射機制?簡單聊一下概念和使用

    1). class反射
    	通過類名的字符串形式實例化對象。
    		Class class = NSClassFromString(@"student"); 
    		Student *stu = [[class alloc] init];
    	將類名變爲字符串。
    		Class class =[Student class];
    		NSString *className = NSStringFromClass(class);
    2). SEL的反射
    	通過方法的字符串形式實例化方法。
    		SEL selector = NSSelectorFromString(@"setName");  
    		[stu performSelector:selector withObject:@"Mike"];
    	將方法變成字符串。
    		NSStringFromSelector(@selector*(setName:));
    
    
  32. 調用方法有兩種方式:

    
     

    利用performSelector 和NSInvocation來調用

    相同點:父類都是NSObject不同點:performSelector最多傳兩個參數,使用比較簡單

    performSelector的方法以及部分使用方法

    - (id)performSelector:(SEL)aSelector;
    - (id)performSelector:(SEL)aSelector withObject:(id)object;
    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
    
    if ([self canPerformAction:@selector(myLog:) withSender:nil]) {
        [self performSelector:@selector(p_Log:) withObject:@"abc" afterDelay:5];
    }
    
    - (void)p_Log:(NSString*)log{
         NSLog(@"MyLog = %@",log);
      }
    

    NSInvocation使用方法

        NSString *str1 = @"a";
        NSString *str2 = @"b";
        NSString *str3 = @"c";
        
        NSMethodSignature *sign = [self methodSignatureForSelector:@selector(personInfo:age:gender:)];
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sign];
        [invocation setTarget:self];
        [invocation setSelector:@selector(personInfo:age:gender:)];
        [invocation setArgument:&str1 atIndex:2];
        [invocation setArgument:&str2 atIndex:3];
        [invocation setArgument:&str3 atIndex:4];
        [invocation invoke];
    
    
    - (void)personInfo:(NSString *)strName age:(NSString *)strAge gender:(NSString *)strGender {
        NSLog(@"%@,%@,%@",strName,strAge,strGender);
    }
    
    

    一開始以爲setArgument 的index 從0開始代表第一個參數,結果崩潰了,po了一下發現

    
    Printing description of invocation:
    <NSInvocation: 0x17027a500>
    return value: {v} void
    target: {@} 0x0
    selector: {:} null
    argument 2: {@} 0x0
    argument 3: {@} 0x0
    argument 4: {@} 0x0
    
    + (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;
    

    從這個方法可以看的出來的types是char類型的 personInfo這個方法其實就是"v@:@@@"。

    這裏的Index要從2開始,以爲0跟1已經被佔據了,分別是self(target),selector(_cmd)

  33. 如何對iOS設備進行性能測試?

    
     

    1.app使用過程中,接聽電話。可以測試不同的通話時間的長短,對於通話結束後,原先打開的app的響應,比如是否停留在原先界面,繼續操作時的相應速度等。

    2.app使用過程中,有推送消息時,對app的使用影響

    3.設備在充電時,app的響應以及操作流暢度

    4.設備在不同電量時(低於10%,50%,95%),app的響應以及操作流暢度

    5.意外斷電時,app數據丟失情況

    6.網絡環境變化時,app的應對情況如何:是否有適當提示?從有網絡環境到無網絡環境時,app的反饋如何?從無網絡環境回到有網絡環境時,是否能自動加載數據,多久才能開始加載數據

    7.多點觸摸的情況

    8.跟其他app之間互相切換時的響應

    9.進程關閉再重新打開的反饋

    10.IOS系統語言環境變化時

  34. 開發項目時你是怎麼檢查內存泄露?

    
     

    一: 內存泄漏

            內存泄漏是編程中常常見到的一個問題,內存泄漏往往會一種奇怪的方式來表現出來,基本上每個程序都表現出不同的方式。 但是一般最後的結果只有兩個,一個是程序當掉,一個是系統內存不足。 還有一種就是比較介於中間的結果程序不會當,但是系統的反映時間明顯降低,需要定時的Reboot纔會正常。

            有一個很簡單的辦法來檢查一個程序是否有內存泄漏。就是是用Windows的任務管理器(Task Manager)。運行程序,然後在任務管理器裏面查看 “內存使用”和”虛擬內存大小”兩項,當程序請求了它所需要的內存之後,如果虛擬內存還是持續的增長的話,就說明了這個程序有內存泄漏問題。 當然如果內存泄漏的數目非常的小,用這種方法可能要過很長時間才能看的出來。

            當然最簡單的辦法大概就是用CompuWare的BoundChecker 之類的工具來檢測了,不過這些工具的價格對於個人來講稍微有點奢侈了。

            如果是已經發布的程序,檢查是否有內存泄漏是又費時又費力。所以內存泄漏應該在Code的生成過程就要時刻進行檢查。


    二: 原因 
            
    內存泄漏產生的原因一般是三種情況:

    1. 分配完內存之後忘了回收; 
    2. 程序Code有問題,造成沒有辦法回收; 
    3. 某些API函數操作不正確,造成內存泄漏。


        1. 內存忘記回收,這個是不應該的事情。但是也是在代碼種很常見的問題。分配內存之後,用完之後,就一定要回收。如果不回收,那就造成了內存的泄漏,造成內存泄漏的Code如果被經常調用的話,那內存泄漏的數目就會越來越多的。從而影響整個系統的運行。比如下面的代碼:

    for (int =0;I<100;I++)
    {
        Temp = new BYTE[100];
    }

    就會產生 100*100Byte的內存泄漏。

        2. 在某些時候,因爲代碼上寫的有問題,會導致某些內存想回收都收不回來,比如下面的代碼:

    Temp1 = new BYTE[100];
    Temp2 = new BYTE[100];
    Temp2 = Temp1;

    這樣,Temp2的內存地址就丟掉了,而且永遠都找不回了,這個時候Temp2的內存空間想回收都沒有辦法。

        3. API函數應用不當,在Windows提供API函數裏面有一些特殊的API,比如FormatMessage。 如果你給它參數中有FORMAT_MESSAGE_ALLOCATE_BUFFER,它會在函數內部New一塊內存Buffer出來。但是這個 buffer需要你調用LocalFree來釋放。 如果你忘了,那就會產生內存泄漏。 

    三: 檢查方法

            一般的內存泄漏檢查的確是很困難,但是也不是完全沒有辦法。如果你用VC的庫來寫東西的話,那麼很幸運的是,你已經有了很多檢查內存泄漏的工具,只是你想不想用的問題了。Visual C++的Debug版本的C運行庫(C Runtime Library)。它已經提供好些函數來幫助你診斷你的代碼和跟蹤內存泄漏。 而且最方便的地方是這些函數在Release版本中完全不起任何作用,這樣就不會影響你的Release版本程序的運行效率。

            比如下面的例子裏面,有一個明細的內存泄漏。當然如果只有這麼幾行代碼的話,是很容易看出有內存泄漏的。但是想在成千上萬行代碼裏面檢查內存泄漏問題就不是那麼容易了。

    char * pstr = new char[5];
    lstrcpy(pstr,"Memory leak");

            如果我們在Debug版本的Code裏面對堆(Heap)進行了操作,包括malloc, free, calloc, realloc, new 和 delete可以利用VC Debug運行時庫中堆Debug函數來做堆的完整性和安全性檢查。比如上面的代碼,lstrcpy的操作明顯破壞了pstr的堆結構。使其溢出,並破壞了臨近的數據。那我們可以在調用lstrcpy之後的代碼裏面加入 _CrtCheckMemory函數。_CrtCheckMemory函數發現前面的lstrcpy使得pstr的堆結構被破壞,會輸出這樣的報告: 

    emory check error at 0x00372FA5 = 0x79, should be 0xFD.
    memory check error at 0x00372FA6 = 0x20, should be 0xFD.
    memory check error at 0x00372FA7 = 0x6C, should be 0xFD.
    memory check error at 0x00372FA8 = 0x65, should be 0xFD.
    DAMAGE: after Normal block (#41) at 0x00372FA0.
    Normal located at 0x00372FA0 is 5 bytes long.

            它告訴說 pstr的長度應該時5個Bytes,但是在5Bytes後面的幾個Bytes也被非法改寫了。提醒你產生了越界操作。_CrtCheckMemory 的返回值只有TRUE和FALSE,那麼你可以用_ASSERTE()來報告出錯信息。 上面的語句可以換成 _ASSERTE(_CrtCheckMemory()); 這樣Debug版本的程序在運行的時候就會彈出一個警告對話框,這樣就不用在運行時候一直盯着Output窗口看了。這個時候按Retry,就可以進入源代碼調試了。看看問題到底出在哪裏。

            其他類似的函數還有 _CrtDbgReport, _CrtDoForAllClientObjects, _CrtDumpMemoryLeaks,_CrtIsValidHeapPointer, _CrtIsMemoryBlock, _CrtIsValidPointer,_CrtMemCheckpoint, _CrtMemDifference, _CrtMemDumpAllObjectsSince, _CrtMemDumpStatistics, _CrtSetAllocHook, _CrtSetBreakAlloc, _CrtSetDbgFlag,_CrtSetDumpClient, _CrtSetReportFile, _CrtSetReportHook, _CrtSetReportMode

            這些函數全部都可以用來在Debug版本中檢查內存的使用情況。具體怎麼使用這些函數就不在這裏說明了,各位可以去查查MSDN。在這些函數中用處比較大的,或者說使用率會比較高的函數是_CrtMemCheckpoint, 設置一個內存檢查點。這個函數會取得當前內存的運行狀態。 _CrtMemDifference 檢查兩種內存狀態的異同。 _CrtMemDumpAllObjectsSince 從程序運行開始,或者從某個內存檢查點開始Dump出堆中對象的信息。還有就是_CrtDumpMemoryLeaks當發生內存溢出的時候Dump出堆中的內存信息。 _CrtDumpMemoryLeaks一般都在有懷疑是內存泄漏的代碼後面調用。比如下面的例子:

    #include <windows.h>
    #include <crtdbg.h>
    void main()
    {
    char * pstr;
    pstr = new char[5];
    _CrtDumpMemoryLeaks();
    }

    輸出:
    Detected memory leaks! à提醒你,代碼有內存泄漏.
    Dumping objects ->
    {44} normal block at 0x00372DB8, 5 bytes long.
    Data: < > CD CD CD CD CD 
    Object dump complete.

            如果你雙擊包含行文件名的輸出行,指針將會跳到源文件中內存被分配地方的行。當無法確定那些代碼產生了內存泄漏的時候,我們就需要進行內存狀態比較。在可疑的代碼段的前後設置內存檢查點,比較內存使用是否有可疑的變化。以確定內存是否有泄漏。爲此要先定義三個_CrtMemState 對象來保存要比較的內存狀態。兩個是用來比較,一個用了保存前面兩個之間的區別。

    _CrtMemState Sh1,Sh2,Sh_Diff;
    char *pstr1 = new char[100];
    _CrtMemCheckPoint(&Sh1); ->
    設置第一個內存檢查點
    char *pstr2 = new char[100];
    _CrtMemCheckPoint(&Sh2); ->設置第二個內存檢查點
    _CrtMemDifference(&Sh_Diff, &Sh1, &Sh2); ->檢查變化
    _CrtMemDumpAllObjectsSince(&Sh_Diff); ->Dump變化

            如果你的程序中使用了MFC類庫,那麼內存泄漏的檢查方法就相當的簡單了。因爲Debug版本的MFC本身就提供一部分的內存泄漏檢查。 大部分的new 和delete沒有配對使用而產生的內存泄漏,MFC都會產生報告。這個主要是因爲MFC重載了Debug版本的new 和delete操作符, 並且對前面提到的API函數重新進行了包裝。在MFC類庫中檢查內存泄漏的Class就叫 CMemoryState,它重新包裝了了_CrtMemState,_CrtMemCheckPoint, _CrtMemDifference, _CrtMemDumpAllObjectsSince這些函數。並對於其他的函數提供了Afx開頭的函數,供MFC程序使用。比如 AfxCheckMemory, AfxDumpMemoryLeaks 這些函數的基本用法同上面提到的差不多。 CMemoryState和相關的函數的定義都在Afx.h這個頭文件中。 有個簡單的辦法可以跟蹤到這些函數的聲明。在VC中找到MFC程序代碼中下面的代碼, 一般都在X.cpp的開頭部分

    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif 

            把光標移到DEBUG_NEW上面 按F12,就可以進入Afx.h中定義這些Class和函數的代碼部分。 VC中內存泄漏的常規檢查辦法主要是上面的兩種。當然這兩種方法只是針對於Debug版本的Heap的檢查。如果Release版本中還有內存泄漏,那麼檢查起來就麻煩很多了。

    4 .總結:

            實際上Heap的內存泄漏問題是相當的好查的。VC的提供的檢查工具也不太少,但是如果是棧出了什麼問題,恐怕就麻煩很多了。棧出問題,一般不會產生內存泄漏,但是你的代碼的邏輯上很有可能會有影響。這個是最最痛苦的事情。 編程,就是小心,小心再小心而已。

  35. 什麼是懶加載?

    答:懶加載就是隻在用到的時候纔去初始化。也可以理解成延時加載。
    我覺得最好也最簡單的一個例子就是tableView中圖片的加載顯示了, 一個延時加載, 避免內存過高,一個異步加載,避免線程堵塞提高用戶體驗。
    
    
  36. 類變量的 @public,@protected,@private,@package 聲明各有什麼含義?

    @public 任何地方都能訪問;
    @protected 該類和子類中訪問,是默認的;
    @private 只能在本類中訪問;
    @package 本包內使用,跨包不可以。
    
    
  37. 什麼是謂詞?

    謂詞就是通過NSPredicate給定的邏輯條件作爲約束條件,完成對數據的篩選。
    //定義謂詞對象,謂詞對象中包含了過濾條件(過濾條件比較多)
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
    //使用謂詞條件過濾數組中的元素,過濾之後返回查詢的結果
    NSArray *array = [persons filteredArrayUsingPredicate:predicate];
    
    
  38. isa指針問題

    isa:是一個Class 類型的指針. 每個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調 用時,先會從本身查找類方法的實現,如果沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass)。根元類的isa指針指向本身,這樣形成了一個封閉的內循環。
    
    
  39. 如何訪問並修改一個類的私有屬性?

    
     

    1.KVC

    我們可以用setValue:的方法設置私有屬性,並利用valueForKey:的方法訪問私有屬性。假設我們有一個類Person,並且這個類有一個私有屬性name。看代碼:

     

    // 利用KVC訪問私有屬性

        Person * ls = [[Person alloc] init];

        [ls setValue:@"wo" forKey:@"name"];

        NSLog(@"=======%@", [ls valueForKey:@"name"])

     

    2.runtime

    我們可以利用runtime獲取某個類的所有屬性(私有屬性、非私有屬性),在獲取到某個類的屬性後就可以對該屬性進行訪問以及修改了。之前有篇博客就是通過runtime獲取某個類的所有成員變量名稱然後對其進行歸檔,博客地址:http://blog.csdn.net/u010105969/article/details/62233752。看代碼:

     

        // 利用run time訪問並修改私有屬性

        Person *p = [Person new];

        // IVar是runtime聲明的一個宏

        unsigned int count = 0; //count記錄變量的數量

        // 獲取類的所有屬性變量

        Ivar *members = class_copyIvarList([Person class], &count);

        for (int i = 0; i < count; i++) {

            Ivar ivar = members[i];

            // 取得屬性名並轉成字符串類型

            const char *memberName = ivar_getName(ivar);

            NSLog(@"%s",memberName);

            Ivar name = members[0];

            // 修改屬性值

            object_setIvar(p, name, @"bushiwo");

        }

        NSLog(@"%@", [p valueForKey:@"name"]);

  40. 一個objc對象的isa的指針指向什麼?有什麼作用?

    
     

    isa 指的就是 是個什麼,對象的isa指向類,類的isa指向元類(meta class),元類isa指向元類的根類。isa幫助一個對象找到它的方法。isa:是一個Class 類型的指針. 每個實例對象有個isa的指針,他指向對象的類,而Class裏也有個isa的指針, 指向meteClass(元類)。元類保存了類方法的列表。當類方法被調用時,先會從本身查找類方法的實現,如果沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指針,它的isa指針最終指向的是一個根元類(root meteClass).根元類的isa指針指向本身,這樣形成了一個封閉的內循環。可以看看這位大神寫的文章http://blog.csdn.net/kesalin/article/details/7211228

  41. 下面的代碼輸出什麼?

    @implementation Son : Father
    - (id)init {
       if (self = [super init]) {
           NSLog(@"%@", NSStringFromClass([self class])); // Son
           NSLog(@"%@", NSStringFromClass([super class])); // Son
       }
       return self;
    }
    @end
    // 解析:
    self 是類的隱藏參數,指向當前調用方法的這個類的實例。
    super是一個Magic Keyword,它本質是一個編譯器標示符,和self是指向的同一個消息接收者。
    不同的是:super會告訴編譯器,調用class這個方法時,要去父類的方法,而不是本類裏的。
    上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前 Son *obj 這個對象。
    
    
  42. 寫一個完整的代理,包括聲明、實現

    // 創建
    @protocol MyDelagate
    @required
    -(void)eat:(NSString *)foodName; 
    @optional
    -(void)run;
    @end
    
    //  聲明 .h
    @interface person: NSObject<MyDelagate>
    
    @end
    
    //  實現 .m
    @implementation person
    - (void)eat:(NSString *)foodName { 
       NSLog(@"吃:%@!", foodName);
    } 
    - (void)run {
       NSLog(@"run!");
    }
    
    @end
    
    
  43. isKindOfClass、isMemberOfClass、selector作用分別是什麼

    isKindOfClass:作用是某個對象屬於某個類型或者繼承自某類型。
    isMemberOfClass:某個對象確切屬於某個類型。
    selector:通過方法名,獲取在內存中的函數的入口地址。
    
    
  44. delegate 和 notification 的區別

    1). 二者都用於傳遞消息,不同之處主要在於一個是一對一的,另一個是一對多的。
    2). notification通過維護一個array,實現一對多消息的轉發。
    3). delegate需要兩者之間必須建立聯繫,不然沒法調用代理的方法;notification不需要兩者之間有聯繫。
    
    
  45. 什麼是block?

    閉包(block):閉包就是獲取其它函數局部變量的匿名函數。
    
    
  46. block反向傳值

  • 在控制器間傳值可以使用代理或者block,使用block相對來說簡潔。

  • 在前一個控制器的touchesBegan:方法內實現如下代碼。

      // OneViewController.m
      TwoViewController *twoVC = [[TwoViewController alloc] init];
      twoVC.valueBlcok = ^(NSString *str) {
      	NSLog(@"OneViewController拿到值:%@", str); 
      };
      [self presentViewController:twoVC animated:YES completion:nil];
    
      // TwoViewController.h   (在.h文件中聲明一個block屬性)
      @property (nonatomic ,strong) void(^valueBlcok)(NSString *str);
    
      // TwoViewController.m   (在.m文件中實現方法)
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    	// 傳值:調用block
    	if (_valueBlcok) {
    		_valueBlcok(@"123456");
    	}
    }
    
    
  1. block的注意點

    1). 在block內部使用外部指針且會造成循環引用情況下,需要用__week修飾外部指針:
    	__weak typeof(self) weakSelf = self; 
    2). 在block內部如果調用了延時函數還使用弱指針會取不到該指針,因爲已經被銷燬了,需要在block內部再將弱指針重新強引用一下。
    	__strong typeof(self) strongSelf = weakSelf;
    3). 如果需要在block內部改變外部棧區變量的話,需要在用__block修飾外部變量。
    
    
  2. BAD_ACCESS在什麼情況下出現?

    答:這種問題在開發時經常遇到。原因是訪問了野指針,比如訪問已經釋放對象的成員變量或者發消息、死循環等。
    
    
  3. lldb(gdb)常用的控制檯調試命令?

    1). p 輸出基本類型。是打印命令,需要指定類型。是print的簡寫
    	p (int)[[[self view] subviews] count]
    2). po 打印對象,會調用對象description方法。是print-object的簡寫
    	po [self view]
    3). expr 可以在調試時動態執行指定表達式,並將結果打印出來。常用於在調試過程中修改變量的值。
    4). bt:打印調用堆棧,是thread backtrace的簡寫,加all可打印所有thread的堆棧
    5). br l:是breakpoint list的簡寫
    
    
  4. 你一般是怎麼用Instruments的?

    Instruments裏面工具很多,常用:
    1). Time Profiler: 性能分析
    2). Zombies:檢查是否訪問了殭屍對象,但是這個工具只能從上往下檢查,不智能。
    3). Allocations:用來檢查內存,寫算法的那批人也用這個來檢查。
    4). Leaks:檢查內存,看是否有內存泄露。
    
    
  5. iOS中常用的數據存儲方式有哪些?

    數據存儲有四種方案:NSUserDefault、KeyChain、file、DB。
    	其中File有三種方式:plist、Archive(歸檔)
    	DB包括:SQLite、FMDB、CoreData
    
    
  6. iOS的沙盒目錄結構是怎樣的?

    沙盒結構:
    1). Application:存放程序源文件,上架前經過數字簽名,上架後不可修改。
    2). Documents:常用目錄,iCloud備份目錄,存放數據。(這裏不能存緩存文件,否則上架不被通過)
    3). Library:
    		Caches:存放體積大又不需要備份的數據。(常用的緩存路徑)
    		Preference:設置目錄,iCloud會備份設置信息。
    4). tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能。
    
    
  7. iOS多線程技術有哪幾種方式?

    答:pthread、NSThread、GCD、NSOperation
    
    
  8. GCD 與 NSOperation 的區別:

    GCD 和 NSOperation 都是用於實現多線程:
    	GCD 基於C語言的底層API,GCD主要與block結合使用,代碼簡潔高效。
    	NSOperation 屬於Objective-C類,是基於GCD更高一層的封裝。複雜任務一般用NSOperation實現。
    
    
  9. 寫出使用GCD方式從子線程回到主線程的方法代碼

    答:dispatch_sync(dispatch_get_main_queue(), ^{ });
    
    
  10. 如何用GCD同步若干個異步調用?(如根據若干個url異步加載多張圖片,然後在都下載完成後合成一張整圖)

    // 使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。
    // 創建隊列組
    dispatch_group_t group = dispatch_group_create();
    // 獲取全局併發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{ /*加載圖片1 */ });
    dispatch_group_async(group, queue, ^{ /*加載圖片2 */ });
    dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); 
    // 當併發隊列組中的任務執行完畢後纔會執行這裏的代碼
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 合併圖片
    });
    
    
  11. dispatch_barrier_async(柵欄函數)的作用是什麼?

    函數定義:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
    作用:
    	1.在它前面的任務執行結束後它才執行,它後面的任務要等它執行完成後纔會開始執行。
    	2.避免數據競爭
    
    // 1.創建併發隊列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    // 2.向隊列中添加任務
    dispatch_async(queue, ^{  // 1.2是並行的
        NSLog(@"任務1, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2, %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"任務 barrier, %@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{   // 這兩個是同時執行的
        NSLog(@"任務3, %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"任務4, %@",[NSThread currentThread]);
    });
    
    // 輸出結果: 任務1 任務2 ——》 任務 barrier ——》任務3 任務4 
    // 其中的任務1與任務2,任務3與任務4 由於是並行處理先後順序不定。
    
    
  12. 以下代碼運行結果如何?

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"1");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    }
    // 只輸出:1。(主線程死鎖)
    
    
  13. 什麼是 RunLoop

    從字面上講就是運行循環,它內部就是do-while循環,在這個循環內部不斷地處理各種任務。
    一個線程對應一個RunLoop,基本作用就是保持程序的持續運行,處理app中的各種事件。通過runloop,有事運行,沒事就休息,可以節省cpu資源,提高程序性能。
    
    主線程的run loop默認是啓動的。iOS的應用程序裏面,程序啓動後會有一個如下的main()函數
    int main(int argc, char * argv[]) {
    	@autoreleasepool {
        	return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    	}
    }
    
    
  14. 什麼是 Runtime

    Runtime又叫運行時,是一套底層的C語言API,其爲iOS內部的核心之一,我們平時編寫的OC代碼,底層都是基於它來實現的。
    
    
  15. Runtime實現的機制是什麼,怎麼用,一般用於幹嘛?

    1). 使用時需要導入的頭文件 <objc/message.h> <objc/runtime.h>
    2). Runtime 運行時機制,它是一套C語言庫。
    3). 實際上我們編寫的所有OC代碼,最終都是轉成了runtime庫的東西。
    	比如:
    		類轉成了 Runtime 庫裏面的結構體等數據類型,
    		方法轉成了 Runtime 庫裏面的C語言函數,
    		平時調方法都是轉成了 objc_msgSend 函數(所以說OC有個消息發送機制)
    	// OC是動態語言,每個方法在運行時會被動態轉爲消息發送,即:objc_msgSend(receiver, selector)。
    	// [stu show];  在objc動態編譯時,會被轉意爲:objc_msgSend(stu, @selector(show));	
    4). 因此,可以說 Runtime 是OC的底層實現,是OC的幕後執行者。
    
    有了Runtime庫,能做什麼事情呢?
     Runtime庫裏面包含了跟類、成員變量、方法相關的API。
     比如:
        (1)獲取類裏面的所有成員變量。
        (2)爲類動態添加成員變量。
        (3)動態改變類的方法實現。
        (4)爲類動態添加新的方法等。
     因此,有了Runtime,想怎麼改就怎麼改。
    
    
  16. 什麼是 Method Swizzle(黑魔法),什麼情況下會使用?

    1). 在沒有一個類的實現源碼的情況下,想改變其中一個方法的實現,除了繼承它重寫、和藉助類別重名方法暴力搶先之外,還有更加靈活的方法 Method Swizzle。
    2). Method Swizzle 指的是改變一個已存在的選擇器對應的實現的過程。OC中方法的調用能夠在運行時通過改變,通過改變類的調度表中選擇器到最終函數間的映射關係。
    3). 在OC中調用一個方法,其實是向一個對象發送消息,查找消息的唯一依據是selector的名字。利用OC的動態特性,可以實現在運行時偷換selector對應的方法實現。
    4). 每個類都有一個方法列表,存放着selector的名字和方法實現的映射關係。IMP有點類似函數指針,指向具體的方法實現。
    5). 我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP。
    6). 我們可以利用 class_replaceMethod 來修改類。
    7). 我們可以利用 method_setImplementation 來直接設置某個方法的IMP。
    8). 歸根結底,都是偷換了selector的IMP。
    
    
  17. _objc_msgForward 函數是做什麼的,直接調用它將會發生什麼?

    答:_objc_msgForward是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並沒有實現的時候,_objc_msgForward會嘗試做消息轉發。
    
    
  18. 什麼是 TCP / UDP ?

    TCP:傳輸控制協議。
    UDP:用戶數據協議。
    
    TCP 是面向連接的,建立連接需要經歷三次握手,是可靠的傳輸層協議。
    UDP 是面向無連接的,數據傳輸是不可靠的,它只管發,不管收不收得到。
    簡單的說,TCP注重數據安全,而UDP數據傳輸快點,但安全性一般。
    
    
  19. 通信底層原理(OSI七層模型)

    OSI採用了分層的結構化技術,共分七層:
    	物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層。
    
    
  20. 介紹一下XMPP?

    XMPP是一種以XML爲基礎的開放式實時通信協議。
    簡單的說,XMPP就是一種協議,一種規定。就是說,在網絡上傳東西,XMM就是規定你上傳大小的格式。
    
    
  21. OC中創建線程的方法是什麼?如果在主線程中執行代碼,方法是什麼?

    // 創建線程的方法
    - [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
    - [self performSelectorInBackground:nil withObject:nil];
    - [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
    - dispatch_async(dispatch_get_global_queue(0, 0), ^{});
    - [[NSOperationQueue new] addOperation:nil];
    
    // 主線程中執行代碼的方法
    - [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
    - dispatch_async(dispatch_get_main_queue(), ^{});
    - [[NSOperationQueue mainQueue] addOperation:nil];
    
    
  22. tableView的重用機制?

    答:UITableView 通過重用單元格來達到節省內存的目的: 通過爲每個單元格指定一個重用標識符,即指定了單元格的種類,當屏幕上的單元格滑出屏幕時,系統會把這個單元格添加到重用隊列中,等待被重用,當有新單元格從屏幕外滑入屏幕內時,從重用隊列中找看有沒有可以重用的單元格,如果有,就拿過來用,如果沒有就創建一個來使用。
    
    
  23. 用僞代碼寫一個線程安全的單例模式

    static id _instance;
    + (id)allocWithZone:(struct _NSZone *)zone {
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           _instance = [super allocWithZone:zone];
       });
       return _instance;
    }
    
    + (instancetype)sharedData {
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           _instance = [[self alloc] init];
       });
       return _instance;
    }
    
    - (id)copyWithZone:(NSZone *)zone {
       return _instance;
    }
    
    
  24. 如何實現視圖的變形?

    答:通過修改view的 transform 屬性即可。
    
    
  25. 在手勢對象基礎類UIGestureRecognizer的常用子類手勢類型中哪兩個手勢發生後,響應只會執行一次?

    答:UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手勢,手勢發生後,響應只會執行一次。
    
    
  26. 字符串常用方法:

    NSString *str = @"abc*123";
    NSArray *arr = [str componentsSeparatedByString:@"*"]; //以目標字符串把原字符串分割成兩部分,存到數組中。@[@"abc", @"123"];
    
    
  27. 如何高性能的給 UIImageView 加個圓角?

    • 不好的解決方案:使用下面的方式會強制Core Animation提前渲染屏幕的離屏繪製, 而離屏繪製就會給性能帶來負面影響,會有卡頓的現象出現。

      self.view.layer.cornerRadius = 5.0f;
      self.view.layer.masksToBounds = YES;
      
      
    • 正確的解決方案:使用繪圖技術

      - (UIImage *)circleImage {
          // NO代表透明
          UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
          // 獲得上下文
          CGContextRef ctx = UIGraphicsGetCurrentContext();
          // 添加一個圓
          CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
          CGContextAddEllipseInRect(ctx, rect);
          // 裁剪
          CGContextClip(ctx);
          // 將圖片畫上去
          [self drawInRect:rect];
          UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
          // 關閉上下文
          UIGraphicsEndImageContext();
          return image;
      }
      
      
    • 還有一種方案:使用了貝塞爾曲線"切割"個這個圖片, 給UIImageView 添加了的圓角,其實也是通過繪圖技術來實現的。

      UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
      imageView.center = CGPointMake(200, 300);
      UIImage *anotherImage = [UIImage imageNamed:@"image"];
      UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
      [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                             cornerRadius:50] addClip];
      [anotherImage drawInRect:imageView.bounds];
      imageView.image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      [self.view addSubview:imageView];
      
      
  28. 你是怎麼封裝一個view的

    1). 可以通過純代碼或者xib的方式來封裝子控件
    2). 建立一個跟view相關的模型,然後將模型數據傳給view,通過模型上的數據給view的子控件賦值
    
    /**
     *  純代碼初始化控件時一定會走這個方法
     */
    - (instancetype)initWithFrame:(CGRect)frame {
        if(self = [super initWithFrame:frame]) {
            [self setupUI];
        }
        return self;
    }
    
    /**
     *  通過xib初始化控件時一定會走這個方法
     */
    - (id)initWithCoder:(NSCoder *)aDecoder {
        if(self = [super initWithCoder:aDecoder]) {
            [self setupUI];
        }
        return self;
    }
    
    - (void)setupUI {
        // 初始化代碼
    }
    
    
  29. HTTP協議中 POST 方法和 GET 方法有那些區別?

    1. GET用於向服務器請求數據,POST用於提交數據
    2. GET請求,請求參數拼接形式暴露在地址欄,而POST請求參數則放在請求體裏面,因此GET請求不適合用於驗證密碼等操作
    3. GET請求的URL有長度限制,POST請求不會有長度限制
    
    
  30. 請簡單的介紹下APNS發送系統消息的機制

    APNS優勢:杜絕了類似安卓那種爲了接受通知不停在後臺喚醒程序保持長連接的行爲,由iOS系統和APNS進行長連接替代。
    APNS的原理:
    	1). 應用在通知中心註冊,由iOS系統向APNS請求返回設備令牌(device Token)
    	2). 應用程序接收到設備令牌併發送給自己的後臺服務器
    	3). 服務器把要推送的內容和設備發送給APNS
    	4). APNS根據設備令牌找到設備,再由iOS根據APPID把推送內容展示
    
    

第三方框架

  1. AFNetworking 底層原理分析

    AFNetworking主要是對NSURLSession和NSURLConnection(iOS9.0廢棄)的封裝,其中主要有以下類:
    1). AFHTTPRequestOperationManager:內部封裝的是 NSURLConnection, 負責發送網絡請求, 使用最多的一個類。(3.0廢棄)
    2). AFHTTPSessionManager:內部封裝是 NSURLSession, 負責發送網絡請求,使用最多的一個類。
    3). AFNetworkReachabilityManager:實時監測網絡狀態的工具類。當前的網絡環境發生改變之後,這個工具類就可以檢測到。
    4). AFSecurityPolicy:網絡安全的工具類, 主要是針對 HTTPS 服務。
    
    5). AFURLRequestSerialization:序列化工具類,基類。上傳的數據轉換成JSON格式
    	(AFJSONRequestSerializer).使用不多。
    6). AFURLResponseSerialization:反序列化工具類;基類.使用比較多:
    7). AFJSONResponseSerializer; JSON解析器,默認的解析器.
    8). AFHTTPResponseSerializer; 萬能解析器; JSON和XML之外的數據類型,直接返回二進
    制數據.對服務器返回的數據不做任何處理.
    9). AFXMLParserResponseSerializer; XML解析器;
    
    
  2. 描述下SDWebImage裏面給UIImageView加載圖片的邏輯

    SDWebImage 中爲 UIImageView 提供了一個分類UIImageView+WebCache.h, 這個分類中有一個最常用的接口sd_setImageWithURL:placeholderImage:,會在真實圖片出現前會先顯示佔位圖片,當真實圖片被加載出來後再替換佔位圖片。
        
    加載圖片的過程大致如下:
    	1.首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以url 作爲數據的索引先在內存中尋找是否有對應的緩存
    	2.如果緩存未找到就會利用通過MD5處理過的key來繼續在磁盤中查詢對應的數據, 如果找到了, 就會把磁盤中的數據加載到內存中,並將圖片顯示出來
    	3.如果在內存和磁盤緩存中都沒有找到,就會向遠程服務器發送請求,開始下載圖片
    	4.下載後的圖片會加入緩存中,並寫入磁盤中
    	5.整個獲取圖片的過程都是在子線程中執行,獲取到圖片後回到主線程將圖片顯示出來
    	
    SDWebImage原理:
    調用類別的方法:
    	1. 從內存(字典)中找圖片(當這個圖片在本次使用程序的過程中已經被加載過),找到直接使用。
    	2. 從沙盒中找(當這個圖片在之前使用程序的過程中被加載過),找到使用,緩存到內存中。
    	3. 從網絡上獲取,使用,緩存到內存,緩存到沙盒。
    
    
  3. 友盟統計接口統計的所有功能

    APP啓動速度,APP停留頁面時間等
    
    

算法

  1. 不用中間變量,用兩種方法交換A和B的值

    // 1.中間變量
    void swap(int a, int b) {
       int temp = a;
       a = b;
       b = temp;
    }
    
    // 2.加法
    void swap(int a, int b) {
       a = a + b;
       b = a - b;
       a = a - b;
    }
    
    // 3.異或(相同爲0,不同爲1. 可以理解爲不進位加法)
    void swap(int a, int b) {
       a = a ^ b;
       b = a ^ b;
       a = a ^ b;
    }
    
    

  2. 求最大公約數

    /** 1.直接遍歷法 */
    int maxCommonDivisor(int a, int b) {
        int max = 0;
        for (int i = 1; i <=b; i++) {
            if (a % i == 0 && b % i == 0) {
                max = i;
            }
        }
        return max;
    }
    /** 2.輾轉相除法 */
    int maxCommonDivisor(int a, int b) {
        int r;
        while(a % b > 0) {
            r = a % b;
            a = b;
            b = r;
        }
        return b;
    }
    
    // 擴展:最小公倍數 = (a * b)/最大公約數
    
    
  3. 模擬棧操作

     /**
     *  棧是一種數據結構,特點:先進後出
     *  練習:使用全局變量模擬棧的操作
     */
    #include <stdio.h>
    #include <stdbool.h>
    #include <assert.h>
    //保護全局變量:在全局變量前加static後,這個全局變量就只能在本文件中使用
    static int data[1024];//棧最多能保存1024個數據
    static int count = 0;//目前已經放了多少個數(相當於棧頂位置)
    
    //數據入棧 push
    void push(int x){
      	assert(!full());//防止數組越界
    	data[count++] = x;
    }
    //數據出棧 pop
    int pop(){
    	assert(!empty());
    	return data[--count];
    }
    //查看棧頂元素 top
    int top(){
    	assert(!empty());
    	return data[count-1];
    }
    
    //查詢棧滿 full
    bool full() {
    	if(count >= 1024) {
         	return 1;
    	}
         return 0; 
    }
    
    //查詢棧空 empty
    bool empty() {
    	if(count <= 0) {
    		return 1;
    	}
        return 0;
    }
    
    int main(){
        //入棧
        for (int i = 1; i <= 10; i++) {
            push(i);
        }
      
        //出棧
        while(!empty()){
            printf("%d ", top()); //棧頂元素
            pop(); //出棧
        }
        printf("\n");
        
        return 0;
    }
    
    
  4. 排序算法

    選擇排序、冒泡排序、插入排序三種排序算法可以總結爲如下:

    • 都將數組分爲已排序部分和未排序部分。

      1. 選擇排序將已排序部分定義在左端,然後選擇未排序部分的最小元素和未排序部分的第一個元素交換。
      2. 冒泡排序將已排序部分定義在右端,在遍歷未排序部分的過程執行交換,將最大元素交換到最右端。
      3. 插入排序將已排序部分定義在左端,將未排序部分元的第一個元素插入到已排序部分合適的位置。
      
      
    • 選擇排序

    /** 
     *	【選擇排序】:最值出現在起始端
     *	
     *	第1趟:在n個數中找到最小(大)數與第一個數交換位置
     *	第2趟:在剩下n-1個數中找到最小(大)數與第二個數交換位置
     *	重複這樣的操作...依次與第三個、第四個...數交換位置
     *	第n-1趟,最終可實現數據的升序(降序)排列。
     *
     */
    void selectSort(int *arr, int length) {
        for (int i = 0; i < length - 1; i++) { //趟數
            for (int j = i + 1; j < length; j++) { //比較次數
                if (arr[i] > arr[j]) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
    
    
    • 冒泡排序

    /** 
     *	【冒泡排序】:相鄰元素兩兩比較,比較完一趟,最值出現在末尾
     *	第1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推進,最值最後出現在第n個元素位置
     *	第2趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推進,最值最後出現在第n-1個元素位置
     *	 ……   ……
     *	第n-1趟:依次比較相鄰的兩個數,不斷交換(小數放前,大數放後)逐個推進,最值最後出現在第2個元素位置	
     */
    void bublleSort(int *arr, int length) {
        for(int i = 0; i < length - 1; i++) { //趟數
            for(int j = 0; j < length - i - 1; j++) { //比較次數
                if(arr[j] > arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            } 
        }
    }
    
    
  5. 折半查找(二分查找)

    /**
     *	折半查找:優化查找時間(不用遍歷全部數據)
     *
     *	折半查找的原理:
     *   1> 數組必須是有序的
     *   2> 必須已知min和max(知道範圍)
     *   3> 動態計算mid的值,取出mid對應的值進行比較
     *   4> 如果mid對應的值大於要查找的值,那麼max要變小爲mid-1
     *   5> 如果mid對應的值小於要查找的值,那麼min要變大爲mid+1
     *
     */ 
    
    // 已知一個有序數組, 和一個key, 要求從數組中找到key對應的索引位置 
    int findKey(int *arr, int length, int key) {
        int min = 0, max = length - 1, mid;
        while (min <= max) {
            mid = (min + max) / 2; //計算中間值
            if (key > arr[mid]) {
                min = mid + 1;
            } else if (key < arr[mid]) {
                max = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
    
    

編碼格式(優化細節)

  1. 在 Objective-C 中,enum 建議使用 NS_ENUM 和 NS_OPTIONS 宏來定義枚舉類型。

    //定義一個枚舉(比較嚴密)
    typedef NS_ENUM(NSInteger, BRUserGender) {
        BRUserGenderUnknown,	// 未知
        BRUserGenderMale,		// 男性
        BRUserGenderFemale,		// 女性
        BRUserGenderNeuter		// 無性
    };
    
    
    @interface BRUser : NSObject<NSCopying>
    
    @property (nonatomic, readonly, copy) NSString *name;
    @property (nonatomic, readonly, assign) NSUInteger age;
    @property (nonatomic, readonly, assign) BRUserGender gender;
    
    - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age gender:(BRUserGender)gender;
    
    @end
    
    //說明:
    //既然該類中已經有一個“初始化方法” ,用於設置 name、age 和 gender 的初始值: 那麼在設計對應 @property 時就應該儘量使用不可變的對象:其三個屬性都應該設爲“只讀”。用初始化方法設置好屬性值之後,就不能再改變了。
    //屬性的參數應該按照下面的順序排列: (原子性,讀寫,內存管理)
    
    

  2. 避免使用C語言中的基本數據類型,建議使用 Foundation 數據類型,對應關係如下:

    int -> NSInteger
    unsigned -> NSUInteger
    float -> CGFloat
    動畫時間 -> NSTimeInterval
    
    

其它知識點

  1. HomeKit,是蘋果2014年發佈的智能家居平臺。

  2. 什麼是 OpenGL、Quartz 2D?

    Quatarz 2d 是Apple提供的基本圖形工具庫。只是適用於2D圖形的繪製。
    OpenGL,是一個跨平臺的圖形開發庫。適用於2D和3D圖形的繪製。
    
    
  3. ffmpeg框架:​ffmpeg 是音視頻處理工具,既有音視頻編碼解碼功能,又可以作爲播放器使用。

  1. 談談 UITableView 的優化

    1). 正確的複用cell。
    2). 設計統一規格的Cell
    3). 提前計算並緩存好高度(佈局),因爲heightForRowAtIndexPath:是調用最頻繁的方法;
    4). 異步繪製,遇到複雜界面,遇到性能瓶頸時,可能就是突破口;
    4). 滑動時按需加載,這個在大量圖片展示,網絡加載的時候很管用!
    5). 減少子視圖的層級關係
    6). 儘量使所有的視圖不透明化以及做切圓操作。
    7). 不要動態的add 或者 remove 子控件。最好在初始化時就添加完,然後通過hidden來控制是否顯示。
    8). 使用調試工具分析問題。
    
    
  2. 如何實行cell的動態的行高

    如果希望每條數據顯示自身的行高,必須設置兩個屬性,1.預估行高,2.自定義行高。
    設置預估行高 tableView.estimatedRowHeight = 200。
    設置定義行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。 
    如果要讓自定義行高有效,必須讓容器視圖有一個自下而上的約束。
    
    
  3. 說說你對 block 的理解

    棧上的自動複製到堆上,block 的屬性修飾符是 copy,循環引用的原理和解決方案。
    
    
  4. 說說你對 runtime 的理解

    主要是方法調用時如何查找緩存,如何找到方法,找不到方法時怎麼轉發,對象的內存佈局。
    
    
  5. 什麼是野指針、空指針?

    野指針:不知道指向了哪裏的指針叫野指針。即指針指向不確定,指針存的地址是一個垃圾值,未初始化。
    空指針:不指向任何位置的指針叫空指針。即指針沒有指向,指針存的地址是一個空地址,NULL。
    
    
  6. 什麼是 OOA / OOD / OOP ?

    OOA(Object Oriented Analysis)   --面向對象分析
    OOD(Object Oriented Design)     --面向對象設計
    OOP(Object Oriented Programming)--面向對象編程

     

     

    10. 多線程是什麼

    多線程是個複雜的概念,按字面意思是同步完成多項任務,提高了資源的使用效率,從硬件、操作系統、應用軟件不同的角度去看,多線程被賦予不同的內涵,對於硬件,現在市面上多數的CPU都是多核的,多核的CPU運算多線程更爲出色;從操作系統角度,是多任務,現在用的主流操作系統都是多任務的,可以一邊聽歌、一邊寫博客;對於應用來說,多線程可以讓應用有更快的迴應,可以在網絡下載時,同時響應用戶的觸摸操作。在iOS應用中,對多線程最初的理解,就是併發,它的含義是原來先做燒水,再摘菜,再炒菜的工作,會變成燒水的同時去摘菜,最後去炒菜。

     

    11. iOS 中的多線程

    iOS中的多線程,是Cocoa框架下的多線程,通過Cocoa的封裝,可以讓我們更爲方便的使用線程,做過C++的同學可能會對線程有更多的理解,比如線程的創立,信號量、共享變量有認識,Cocoa框架下會方便很多,它對線程做了封裝,有些封裝,可以讓我們創建的對象,本身便擁有線程,也就是線程的對象化抽象,從而減少我們的工程,提供程序的健壯性。

    GCD是(Grand Central Dispatch)的縮寫 ,從系統級別提供的一個易用地多線程類庫,具有運行時的特點,能充分利用多核心硬件。GCD的API接口爲C語言的函數,函數參數中多數有Block,關於Block的使用參看這裏,爲我們提供強大的“接口”,對於GCD的使用參見本文

    NSOperation與Queue

    NSOperation是一個抽象類,它封裝了線程的細節實現,我們可以通過子類化該對象,加上NSQueue來同面向對象的思維,管理多線程程序。具體可參看這裏:一個基於NSOperation的多線程網絡訪問的項目。

    NSThread

    NSThread是一個控制線程執行的對象,它不如NSOperation抽象,通過它我們可以方便的得到一個線程,並控制它。但NSThread的線程之間的併發控制,是需要我們自己來控制的,可以通過NSCondition實現。

    參看 iOS多線程編程之NSThread的使用

    其他多線程

    在Cocoa的框架下,通知、Timer和異步函數等都有使用多線程,(待補充).

     

    12. 在項目什麼時候選擇使用GCD,什麼時候選擇NSOperation?

    項目中使用NSOperation的優點是NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化NSOperation的設計思路,是具有面向對象的優點(複用、封裝),使得實現是多線程支持,而接口簡單,建議在複雜項目中使用。

    項目中使用GCD的優點是GCD本身非常簡單、易用,對於不復雜的多線程操作,會節省代碼量,而Block參數的使用,會是代碼更爲易讀,建議在簡單項目中使用。

     

    13 KVO,NSNotification,delegate及block區別

    • KVO就是cocoa框架實現的觀察者模式,一般同KVC搭配使用,通過KVO可以監測一個值的變化,比如View的高度變化。是一對多的關係,一個值的變化會通知所有的觀察者。
    • NSNotification是通知,也是一對多的使用場景。在某些情況下,KVO和NSNotification是一樣的,都是狀態變化之後告知對方。NSNotification的特點,就是需要被觀察者先主動發出通知,然後觀察者註冊監聽後再來進行響應,比KVO多了發送通知的一步,但是其優點是監聽不侷限於屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽範圍廣,使用也更靈活。

    • delegate 是代理,就是我不想做的事情交給別人做。比如狗需要吃飯,就通過delegate通知主人,主人就會給他做飯、盛飯、倒水,這些操作,這些狗都不需要關心,只需要調用delegate(代理人)就可以了,由其他類完成所需要的操作。所以delegate是一對一關係。

    • block是delegate的另一種形式,是函數式編程的一種形式。使用場景跟delegate一樣,相比delegate更靈活,而且代理的實現更直觀。

    • KVO一般的使用場景是數據,需求是數據變化,比如股票價格變化,我們一般使用KVO(觀察者模式)。delegate一般的使用場景是行爲,需求是需要別人幫我做一件事情,比如買賣股票,我們一般使用delegate。
      Notification一般是進行全局通知,比如利好消息一出,通知大家去買入。delegate是強關聯,就是委託和代理雙方互相知道,你委託別人買股票你就需要知道經紀人,經紀人也不要知道自己的顧客。Notification是弱關聯,利好消息發出,你不需要知道是誰發的也可以做出相應的反應,同理發消息的人也不需要知道接收的人也可以正常發出消息。

     

    14 將一個函數在主線程執行的4種方法

    • GCD方法,通過向主線程隊列發送一個block塊,使block裏的方法可以在主線程中執行。
    dispatch_async(dispatch_get_main_queue(), ^{      
        //需要執行的方法
    });
    • NSOperation 方法
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主隊列
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    	//需要執行的方法
    }];
    [mainQueue addOperation:operation];
    • NSThread 方法
    [self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];
    
    [self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];
    
    [[NSThread mainThread] performSelector:@selector(method) withObject:nil];
    • RunLoop方法
    [[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];
    

     

    15 如何讓計時器調用一個類方法

    • 計時器只能調用實例方法,但是可以在這個實例方法裏面調用靜態方法。
    • 使用計時器需要注意,計時器一定要加入RunLoop中,並且選好model才能運行。scheduledTimerWithTimeInterval方法創建一個計時器並加入到RunLoop中所以可以直接使用。
    • 如果計時器的repeats選擇YES說明這個計時器會重複執行,一定要在合適的時機調用計時器的invalid。不能在dealloc中調用,因爲一旦設置爲repeats 爲yes,計時器會強持有self,導致dealloc永遠不會被調用,這個類就永遠無法被釋放。比如可以在viewDidDisappear中調用,這樣當類需要被回收的時候就可以正常進入dealloc中了。
     [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    
    -(void)timerMethod
    {
    //調用類方法
    	[[self class] staticMethod];
    }
    
    -(void)invalid
    {
    	[timer invalid];
    	timer = nil;
    }

     

    16 如何重寫類方法

    • 1、在子類中實現一個同基類名字一樣的靜態方法
    • 2、在調用的時候不要使用類名調用,而是使用[self class]的方式調用。原理,用類名調用是早綁定,在編譯期綁定,用[self class]是晚綁定,在運行時決定調用哪個方法。

     

    17 NSTimer創建後,會在哪個線程運行。

    • 用scheduledTimerWithTimeInterval創建的,在哪個線程創建就會被加入哪個線程的RunLoop中就運行在哪個線程
    • 自己創建的Timer,加入到哪個線程的RunLoop中就運行在哪個線程。

     

    18 id和NSObject*的區別

    • id是一個 objc_object 結構體指針,定義是
    typedef struct objc_object *id
    • id可以理解爲指向對象的指針。所有oc的對象 id都可以指向,編譯器不會做類型檢查,id調用任何存在的方法都不會在編譯階段報錯,當然如果這個id指向的對象沒有這個方法,該崩潰還是會崩潰的。

    • NSObject *指向的必須是NSObject的子類,調用的也只能是NSObjec裏面的方法否則就要做強制類型轉換。

    • 不是所有的OC對象都是NSObject的子類,還有一些繼承自NSProxy。NSObject *可指向的類型是id的子集。

       

    77.ios開發逆向傳值的幾種方法整理

    第一種:代理傳值

    第二個控制器:

    @protocol WJSecondViewControllerDelegate <NSObject>
    - (void)changeText:(NSString*)text;
    @end
     @property(nonatomic,assign)id<WJSecondViewControllerDelegate>delegate;
    
    - (IBAction)buttonClick:(UIButton*)sender {
    _str = sender.titleLabel.text;
    [self.delegate changeText:sender.titleLabel.text];
    [self.navigationController popViewControllerAnimated:YES];
    }

    第一個控制器:

    - (IBAction)pushToSecond:(id)sender {
    WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil];
    svc.delegate = self;
    svc.str = self.navigationItem.title;
    [self.navigationController pushViewController:svc animated:YES];
    [svc release];
    }
    - (void)changeText:(NSString *)text{
    self.navigationItem.title = text;
    }

    第二種:通知傳值

    第一個控制器:

     //註冊監聽通知
     [[NSNotificationCenter defaultCenter] addObserver:self         selector:@selector(limitDataForModel:) name:@"NOV" object:nil];
    - (void)limitDataForModel:(NSNotification *)noti{
    self.gamesInfoArray = noti.object;
    }

    第二個控制器:

    //發送通知
      [[NSNotificationCenter defaultCenter]     postNotificationName:@"NOV" object:gameArray];

    第三種:單例傳值

    Single是一個單例類,並且有一個字符串類型的屬性titleName
    在第二個控制器:

    - (IBAction)buttonClick:(UIButton*)sender {
    Single *single = [Single sharedSingle];
    single.titleName = sender.titleLabel.text;
    [self.navigationController popViewControllerAnimated:YES];
    }

    第一個控制器:

    - (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    Single *single = [Single sharedSingle];
    self.navigationItem.title = single.titleName;
    }

    第四種:block傳值

    第二個控制器:

    @property (nonatomic,copy) void (^changeText_block)(NSString*);
    - (IBAction)buttonClick:(UIButton*)sender {
    _str = sender.titleLabel.text;
    self.changeText_block(sender.titleLabel.text);
    [self.navigationController popViewControllerAnimated:YES];
    }

    第一個控制器:

    - (IBAction)pushToSecond:(id)sender {
    WJSecondViewController *svc = [[WJSecondViewController alloc]initWithNibName:@"WJSecondViewController" bundle:nil];
    svc.str = self.navigationItem.title;
    [svc setChangeText_block:^(NSString *str) {
        >self.navigationItem.title = str;
    }];
    [self.navigationController pushViewController:svc animated:YES];
    }

    第五種:extern傳值

    第二個控制器:

     extern NSString *btn;
    - (IBAction)buttonClick:(UIButton*)sender {
    btn = sender.titleLabel.text;
    [self.navigationController popViewControllerAnimated:YES];
    }

    第一個控制器:

    NSString *btn = nil;
    - (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    self.navigationItem.title = btn;
    }

    第六種:KVO傳值

    第一個控制器:

    - (void)viewDidLoad {
    [super viewDidLoad];
     _vc =[[SecondViewController alloc]init];
    //self監聽vc裏的textValue屬性
    [_vc addObserver:self forKeyPath:@"textValue" options:0 context:nil];   
    }

    第二個控制器:

    - (IBAction)buttonClicked:(id)sender {
    self.textValue = self.textField.text;
    [self.navigationController popViewControllerAnimated:YES];
    }

     

     

    78.淺談iOS開發中方法延遲執行的幾種方式

     

    Method1. performSelector方法

    Method2. NSTimer定時器

    Method3. NSThread線程的sleep

    Method4. GCD


    公用延遲執行方法

    - (void)delayMethod{ NSLog(@"delayMethodEnd");}


    Method1:performSelector

    [self performSelector:@selector(delayMethod) withObject:nil/*可傳任意類型參數*/ afterDelay:2.0];
    注:此方法是一種非阻塞的執行方式,未找到取消執行的方法。

    程序運行結束
    2015-08-31 10:56:59.361 CJDelayMethod[1080:39604] delayMethodStart2015-08-31 10:56:59.363 CJDelayMethod[1080:39604] nextMethod2015-08-31 10:57:01.364 CJDelayMethod[1080:39604] delayMethodEnd

    Method2:NSTimer定時器

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
    注:此方法是一種非阻塞的執行方式,
    取消執行方法:- (void)invalidate;即可

    程序運行結束
    2015-08-31 10:58:10.182 CJDelayMethod[1129:41106] delayMethodStart2015-08-31 10:58:10.183 CJDelayMethod[1129:41106] nextMethod2015-08-31 10:58:12.185 CJDelayMethod[1129:41106] delayMethodEnd

    Method3:NSThread線程的sleep

    [NSThread sleepForTimeInterval:2.0];
    注:此方法是一種阻塞執行方式,建議放在子線程中執行,否則會卡住界面。但有時還是需要阻塞執行,如進入歡迎界面需要沉睡3秒才進入主界面時。
    沒有找到取消執行方式。

    程序運行結束
    2015-08-31 10:58:41.501 CJDelayMethod[1153:41698] delayMethodStart2015-08-31 10:58:43.507 CJDelayMethod[1153:41698] nextMethod

    Method4:GCD

    __block ViewController/*主控制器*/ *weakSelf = self;

    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0/*延遲執行時間*/ * NSEC_PER_SEC));
    
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        [weakSelf delayMethod];
    });`

    注:此方法可以在參數中選擇執行的線程,是一種非阻塞執行方式。沒有找到取消執行方式。

    程序運行結束
    2015-08-31 10:59:21.652 CJDelayMethod[1181:42438] delayMethodStart2015-08-31 10:59:21.653 CJDelayMethod[1181:42438] nextMethod2015-08-31 10:59:23.653 CJDelayMethod[1181:42438] delayMethodEnd

    完整代碼參見:

    //
    // ViewController.m
    // CJDelayMethod
    //
    // Created by 陳杰 on 8/31/15.
    // Copyright (c) 2015 chenjie. All rights reserved.
    //

    import "ViewController.h"

    @interface ViewController ()
    @property (nonatomic, strong) NSTimer timer;
    @end
    @implementation ViewController


    - (void)viewDidLoad { 

     [super viewDidLoad]; 

     

     NSLog(@"delayMethodStart"); 

     

     [self methodOnePerformSelector];// 

     

     [self methodTwoNSTimer];// 

     

     [self methodThreeSleep];//

     

     [self methodFourGCD]; 

     

     NSLog(@"nextMethod");

    }


    - (void)methodFiveAnimation{ 

     [UIView animateWithDuration:0 delay:2.0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ } completion:^(BOOL finished) { 

     [self delayMethod]; 

     }];

    }
    - (void)methodFourGCD{ 

     __block ViewControllerweakSelf = self; dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)); dispatch_after(delayTime, dispatch_get_main_queue(), ^{ 

     [weakSelf delayMethod]; 

     });

    }


    - (void)methodThreeSleep{ 

     [NSThread sleepForTimeInterval:2.0];

    }


    - (void)methodTwoNSTimer{

     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];

    }


    - (void)methodOnePerformSelector{

     [self performSelector:@selector(delayMethod) withObject:nil/*可傳任意類型參數*/ afterDelay:2.0];

    }


    - (void)delayMethod{

     NSLog(@"delayMethodEnd");

    }
    - (void)didReceiveMemoryWarning { 

     [super didReceiveMemoryWarning]; 

     // Dispose of any resources that can be recreated.

    }

    @end


     

    79.NSPersistentStoreCoordinator  ,   NSManaged0bjectContext 和NSManaged0bject中的那些需要在線程中創建或者傳遞

     

     答:NSPersistentStoreCoordinator是持久化存儲協調者,主要用於協調託管對象上下文和持久化存儲區之間的關係。NSManagedObjectContext使用協調者的託管對象模型將數據保存到數據庫,或查詢數據。

     

    80.您是否做過一部的網絡處理和通訊方面的工作?如果有,能具體介紹一下實現策略麼 

     

    答:使用NSOperation發送異步網絡請求,使用NSOperationQueue管理線程數目及優先級,底層是用NSURLConnetion,

     

    81.你使用過Objective-C的運行時編程(Runtime Programming)麼?如果使用過,你用它做了什麼?你還能記得你所使用的相關的頭文件或者某些方法的名稱嗎?

     

     答:Objecitve-C的重要特性是Runtime(運行時),在#import <objc/runtime.h> 下能看到相關的方法,用過objc_getClass()和class_copyMethodList()獲取過私有API;使用  
    ```objective-c
    Method method1 = class_getInstanceMethod(cls, sel1);
    Method method2 = class_getInstanceMethod(cls, sel2);
    method_exchangeImplementations(method1, method2);  
    ```   
    代碼交換兩個方法,在寫unit test時使用到。

     

    82.Core開頭的系列的內容。是否使用過CoreAnimation和CoreGraphics。UI框架和CA,CG框架的聯繫是什麼?分別用CA和CG做過些什麼動畫或者圖像上的內容。(有需要的話還可以涉及Quartz的一些內容)

     

    答:UI框架的底層有CoreAnimation,CoreAnimation的底層有CoreGraphics。    
    UIKit | 
    ------------ | 
    Core Animation | 
    Core Graphics |
    Graphics Hardware|  
    使用CA做過menu菜單的展開收起(太遜了)  


    83.是否使用過CoreText或者CoreImage等?如果使用過,請談談你使用CoreText或者CoreImage的體驗。

     

    答:CoreText可以解決複雜文字內容排版問題。CoreImage可以處理圖片,爲其添加各種效果。體驗是很強大,挺複雜的。


    85.NSNotification和KVO的區別和用法是什麼?什麼時候應該使用通知,什麼時候應該使用KVO,它們的實現上有什麼區別嗎?如果用protocol和delegate(或者delegate的Array)來實現類似的功能可能嗎?如果可能,會有什麼潛在的問題?如果不能,爲什麼?(雖然protocol和delegate這種東西面試已經面爛了…)

     

    答:NSNotification是通知模式在iOS的實現,KVO的全稱是鍵值觀察(Key-value observing),其是基於KVC(key-value coding)的,KVC是一個通過屬性名訪問屬性變量的機制。例如將Module層的變化,通知到多個Controller對象時,可以使用NSNotification;如果是只需要觀察某個對象的某個屬性,可以使用KVO。
    對於委託模式,在設計模式中是對象適配器模式,其是delegate是指向某個對象的,這是一對一的關係,而在通知模式中,往往是一對多的關係。委託模式,從技術上可以現在改變delegate指向的對象,但不建議這樣做,會讓人迷惑,如果一個delegate對象不斷改變,指向不同的對象。


    86.你用過NSOperationQueue麼?如果用過或者瞭解的話,你爲什麼要使用NSOperationQueue,實現了什麼?請描述它和G.C.D的區別和類似的地方(提示:可以從兩者的實現機制和適用範圍來描述)。

     

    答:使用NSOperationQueue用來管理子類化的NSOperation對象,控制其線程併發數目。GCD和NSOperation都可以實現對線程的管理,區別是 NSOperation和NSOperationQueue是多線程的面向對象抽象。項目中使用NSOperation的優點是NSOperation是對線程的高度抽象,在項目中使用它,會使項目的程序結構更好,子類化NSOperation的設計思路,是具有面向對象的優點(複用、封裝),使得實現是多線程支持,而接口簡單,建議在複雜項目中使用。
    項目中使用GCD的優點是GCD本身非常簡單、易用,對於不復雜的多線程操作,會節省代碼量,而Block參數的使用,會是代碼更爲易讀,建議在簡單項目中使用。


    87.既然提到G.C.D,那麼問一下在使用G.C.D以及block時要注意些什麼?它們兩是一回事兒麼?block在ARC中和傳統的MRC中的行爲和用法有沒有什麼區別,需要注意些什麼?


    答:使用block是要注意,若將block做函數參數時,需要把它放到最後,GCD是Grand Central Dispatch,是一個對線程開源類庫,而Block是閉包,是能夠讀取其他函數內部變量的函數。

     

     

    88. 對於Objective-C,你認爲它最大的優點和最大的不足是什麼?對於不足之處,現在有沒有可用的方法繞過這些不足來實現需求。如果可以的話,你有沒有考慮或者實踐過重新實現OC的一些功能,如果有,具體會如何做?


    答:最大的優點是它的運行時特性,不足是沒有命名空間,對於命名衝突,可以使用長命名法或特殊前綴解決,如果是引入的第三方庫之間的命名衝突,可以使用link命令及flag解決衝突。

      
    89. 你實現過一個框架或者庫以供別人使用麼?如果有,請談一談構建框架或者庫時候的經驗;如果沒有,請設想和設計框架的public的API,並指出大概需要如何做、需要注意一些什麼方面,來使別人容易地使用你的框架。

     

    答:抽象和封裝,方便使用。首先是對問題有充分的瞭解,比如構建一個文件解壓壓縮框架,從使用者的角度出發,只需關注發送給框架一個解壓請求,框架完成複雜文件的解壓操作,並且在適當的時候通知給是哦難過者,如解壓完成、解壓出錯等。在框架內部去構建對象的關係,通過抽象讓其更爲健壯、便於更改。其次是API的說明文檔。

 

 

90.說說你理解的埋點?

以下幾篇文章寫的相當不錯,可以適當借鑑下!

iOS無埋點數據SDK實踐之路

iOS無埋點數據SDK的整體設計與技術實現

iOS無埋點SDK 之 RN頁面的數據收集

91.消息轉發機制原理?

消息轉發機制基本分爲三個步驟:

1、動態方法解析

2、備用接受者

3、完整轉發

轉發機制原理

新建一個HelloClass的類,定義兩個方法:

1

2

3

@interfaceHelloClass:NSObject

- (void)hello;

+ (HelloClass *)hi;@end

動態方法解析

對象在接收到未知的消息時,首先會調用所屬類的類方法+resolveInstanceMethod:(實例方法)或者+resolveClassMethod:(類方法)。在這個方法中,我們有機會爲該未知消息新增一個”處理方法”“。不過使用該方法的前提是我們已經實現了該”處理方法”,只需要在運行時通過class_addMethod函數動態添加到類裏面就可以了。

1

2

3

4

5

6

7

8

9

void functionForMethod(id self, SEL _cmd)

{

 NSLog(@"Hello!");

}

Class functionForClassMethod(id self, SEL _cmd)

{

 NSLog(@"Hi!");

 return [HelloClass class];

}

#pragma mark - 1、動態方法解析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

+ (BOOL)resolveClassMethod:(SEL)sel

{

NSLog(@"resolveClassMethod");

NSString *selString = NSStringFromSelector(sel);

if ([selString isEqualToString:@"hi"])

{

Class metaClass = objc_getMetaClass("HelloClass");

class_addMethod(metaClass, @selector(hi), (IMP)functionForClassMethod, "v@:");

return YES;

}

return [super resolveClassMethod:sel];

}

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

NSLog(@"resolveInstanceMethod");

NSString *selString = NSStringFromSelector(sel);

if ([selString isEqualToString:@"hello"])

{

class_addMethod(self, @selector(hello), (IMP)functionForMethod, "v@:");

return YES;

}

return [super resolveInstanceMethod:sel];

}

備用接受者

動態方法解析無法處理消息,則會走備用接受者。這個備用接受者只能是一個新的對象,不能是self本身,否則就會出現無限循環。如果我們沒有指定相應的對象來處理aSelector,則應該調用父類的實現來返回結果。

#pragma mark - 2、備用接收者

1

2

3

4

5

6

7

8

9

- (id)forwardingTargetForSelector:(SEL)aSelector

{

NSLog(@"forwardingTargetForSelector");

NSString *selectorString = NSStringFromSelector(aSelector);

// 將消息交給_helper來處理? ? if ([selectorString isEqualToString:@"hello"]) {

 return _helper;

}

return [super forwardingTargetForSelector:aSelector];

}

在本類中需要實現這個新的接受對象

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@interfaceHelloClass()

{

RuntimeMethodHelper *_helper;

}

@end

@implementationHelloClass- (instancetype)init

{

self = [super init];

if (self)

{

_helper = [RuntimeMethodHelper new];

}

 return self;

}

RuntimeMethodHelper 類需要實現這個需要轉發的方法:

1

2

3

4

5

#import"RuntimeMethodHelper.h"

@implementationRuntimeMethodHelper- (void)hello

{

 NSLog(@"%@, %p", self, _cmd);

}@end

完整消息轉發

如果動態方法解析和備用接受者都沒有處理這個消息,那麼就會走完整消息轉發:

#pragma mark - 3、完整消息轉發

1

2

3

4

5

6

7

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

NSLog(@"forwardInvocation");

if ([RuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {

[anInvocation invokeWithTarget:_helper];

}

}

/*必須重新這個方法,消息轉發機制使用從這個方法中獲取的信息來創建NSInvocation對象*/

1

2

3

4

5

6

7

8

9

10

11

12

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

if (!signature)

{

if ([RuntimeMethodHelper instancesRespondToSelector:aSelector])

{

 signature = [RuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];

}

}

 return signature;

}

92.說說你理解weak屬性?

weak實現原理:

Runtime維護了一個weak表,用於存儲指向某個對象的所有weak指針。weak表其實是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數組。

1、初始化時:runtime會調用objc_initWeak函數,初始化一個新的weak指針指向對象的地址。

2、添加引用時:objc_initWeak函數會調用 objc_storeWeak() 函數, objc_storeWeak() 的作用是更新指針指向,創建對應的弱引用表。

3、釋放時,調用clearDeallocating函數。clearDeallocating函數首先根據對象地址獲取所有weak指針地址的數組,然後遍歷這個數組把其中的數據設爲nil,最後把這個entry從weak表中刪除,最後清理對象的記錄。

追問的問題一:

1.實現weak後,爲什麼對象釋放後會自動爲nil?

runtime?對註冊的類, 會進行佈局,對於?weak?對象會放入一個?hash?表中。 用?weak?指向的對象內存地址作爲?key,當此對象的引用計數爲?0?的時候會?dealloc,假如?weak?指向的對象內存地址是?a?,那麼就會以?a?爲鍵, 在這個?weak?表中搜索,找到所有以?a?爲鍵的?weak?對象,從而設置爲?nil?。

追問的問題二:

2.當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?

1、調用objc_release

2、因爲對象的引用計數爲0,所以執行dealloc

3、在dealloc中,調用了_objc_rootDealloc函數

4、在_objc_rootDealloc中,調用了object_dispose函數

5、調用objc_destructInstance

6、最後調用objc_clear_deallocating,詳細過程如下:

a. 從weak表中獲取廢棄對象的地址爲鍵值的記錄

b. 將包含在記錄中的所有附有 weak修飾符變量的地址,賦值爲 nil

c. 將weak表中該記錄刪除

d. 從引用計數表中刪除廢棄對象的地址爲鍵值的記錄

90.假如Controller太臃腫,如何優化?

1.將網絡請求抽象到單獨的類中

方便在基類中處理公共邏輯;

方便在基類中處理緩存邏輯,以及其它一些公共邏輯;

方便做對象的持久化。

2.將界面的封裝抽象到專門的類中

構造專門的 UIView 的子類,來負責這些控件的拼裝。這是最徹底和優雅的方式,不過稍微麻煩一些的是,你需要把這些控件的事件回調先接管,再都一一暴露回 Controller。

3.構造 ViewModel

借鑑MVVM。具體做法就是將 ViewController 給 View 傳遞數據這個過程,抽象成構造 ViewModel 的過程。

4.專門構造存儲類

專門來處理本地數據的存取。

5.整合常量

90.項目中網絡層如何做安全處理?

1、儘量使用https

https可以過濾掉大部分的安全問題。https在證書申請,服務器配置,性能優化,客戶端配置上都需要投入精力,所以缺乏安全意識的開發人員容易跳過https,或者拖到以後遇到問題再優化。https除了性能優化麻煩一些以外其他都比想象中的簡單,如果沒精力優化性能,至少在註冊登錄模塊需要啓用https,這部分業務對性能要求比較低。

2、不要傳輸明文密碼

不知道現在還有多少app後臺是明文存儲密碼的。無論客戶端,server還是網絡傳輸都要避免明文密碼,要使用hash值。客戶端不要做任何密碼相關的存儲,hash值也不行。存儲token進行下一次的認證,而且token需要設置有效期,使用refresh

token去申請新的token。

3、Post並不比Get安全

事實上,Post和Get一樣不安全,都是明文。參數放在QueryString或者Body沒任何安全上的差別。在Http的環境下,使用Post或者Get都需要做加密和簽名處理。

4、不要使用301跳轉

301跳轉很容易被Http劫持攻擊。移動端http使用301比桌面端更危險,用戶看不到瀏覽器地址,無法察覺到被重定向到了其他地址。如果一定要使用,確保跳轉發生在https的環境下,而且https做了證書綁定校驗。

5、http請求都帶上MAC

所有客戶端發出的請求,無論是查詢還是寫操作,都帶上MAC(Message Authentication

Code)。MAC不但能保證請求沒有被篡改(Integrity),還能保證請求確實來自你的合法客戶端(Signing)。當然前提是你客戶端的key沒有被泄漏,如何保證客戶端key的安全是另一個話題。MAC值的計算可以簡單的處理爲hash(request

params+key)。帶上MAC之後,服務器就可以過濾掉絕大部分的非法請求。MAC雖然帶有簽名的功能,和RSA證書的電子簽名方式卻不一樣,原因是MAC簽名和簽名驗證使用的是同一個key,而RSA是使用私鑰簽名,公鑰驗證,MAC的簽名並不具備法律效應。

6、http請求使用臨時密鑰

高延遲的網絡環境下,不經優化https的體驗確實會明顯不如http。在不具備https條件或對網絡性能要求較高且缺乏https優化經驗的場景下,http的流量也應該使用AES進行加密。AES的密鑰可以由客戶端來臨時生成,不過這個臨時的AES

key需要使用服務器的公鑰進行加密,確保只有自己的服務器才能解開這個請求的信息,當然服務器的response也需要使用同樣的AES

key進行加密。由於http的應用場景都是由客戶端發起,服務器響應,所以這種由客戶端單方生成密鑰的方式可以一定程度上便捷的保證通信安全。

7、AES使用CBC模式

不要使用ECB模式,記得設置初始化向量,每個block加密之前要和上個block的祕文進行運算。

95.main()之前的過程有哪些?

1、main之前的加載過程

1)dyld 開始將程序二進制文件初始化

2)交由ImageLoader 讀取 image,其中包含了我們的類,方法等各種符號(Class、Protocol 、Selector、 IMP)

3)由於runtime 向dyld 綁定了回調,當image加載到內存後,dyld會通知runtime進行處理

4)runtime 接手後調用map_images做解析和處理

5)接下來load_images 中調用call_load_methods方法,遍歷所有加載進來的Class,按繼承層次依次調用Class的+load和其他Category的+load方法

6)至此 所有的信息都被加載到內存中

7)最後dyld調用真正的main函數

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章