主要內容:
面向對象設計原則 iOS應用導航模式有哪些 iOS持久化方式有哪些NSClassFromString加載靜態庫中的類什麼狀況是nilid和NSObject*的區別 簡單描述一下Runtime Runtime給類添加屬性、成員變量 KVO原理 Property修飾符 程序內存分區extern的做用 指針函數/函數指針/Block __weak、__strong、__block理解 事件傳遞鏈/事件響應鏈 簡述RunLoopNSTimer原理 簡述GCD 自動釋放池 iOS中的定時器UIView/UILayer關係 簡述你瞭解的鎖 ISO七層、TCP/IP四層協議 什麼是ARC iOS類和結構體有什麼區別 iOS通知和協議的區別 iOS內存使用注意事項和優化 ViewController完整生命週期 frame和bounds區別@synthesize和@dynamic的做用 SDWebImage做用 XML解析方式 AFNetWorking做用 Http協議特色,GET/POST請求區別 Socket鏈接和Http鏈接區別 Tcp三次握手、四次揮手 performSelector傳三個參數(未解答) main方法前過程 線程安全方法NSOperationQueue和GCD區別聯繫 iOS經常使用設計模式 簡述Block 消息動態處理/轉發流程weak變量怎麼置爲nil對nil發消息會發生什麼 安全區域的理解UITableView優化方法 ssl/tls證書做用 MVC、MVP、MVVM
相關分析:
單一職責原則,開閉原則,依賴倒置原則(面向接口編程),迪米特原則,里氏替換原則,接口隔離原則。html
談談面向對象設計(OOD)原則java
這個問題更可能是設計人員考慮的,不過咱們也須要了解,否則咱們都不知道UITabBarController和UINavigationController等存在的意義是啥。
iOS應用屬於客戶端應用,問題實際上是問下面兩個部分:ios
1:什麼是導航模式?客戶端導航模式有哪些常見的? 2:iOS中存在哪些導航模式?
導航模式:將信息以最優的方式組織起來展示給用戶。
客戶端常見模式:tab、抽屜、列表、平鋪/輪播、宮格和懸浮icon等。
注:不要太在乎名稱,你會在網上搜到一種模式有多種名稱。git
移動端導航的七種設計模式
8種移動APP導航設計模式大對比程序員
這裏並非問你哪些控件/控制器對應這些導航模式,因此iOS具備上面提到的全部導航模式。github
首先這裏的持久化指的是數據持久化,目前客戶端的持久化也只有這一個含義。
爲什麼要持久化:iOS開發能夠沒有持久化,持久化更多的是業務需求;好比記錄用戶是否登錄,下次進應用不須要再登錄。
由於iOS的沙盒機制,因此持久化分爲兩類:沙盒內和沙盒外。web
只要遵循了NSCoding協議並正確實現了initWithCoder和encodeWithCoder方法的類均可以經過NSKeyedArchiver來序列化。
歸檔使用archiveRootObject,解歸檔使用unarchiveObjectWithFile;須要指定文件路徑。objective-c
[NSUserDefaults standardUserDefaults]獲取NSUserDefaults對象,以key-value方式進行持久化操做。sql
寫入使用writeToFile,讀取使用xxxWithContentsOfFile;須要指定文件路徑。shell
數據庫無疑是大量數據最好的持久化方案,數據庫目前有:sqlite、CoreData和Realm等。這裏就不用回答FMDB它只是封裝了sqlite而已。
這裏要和plist區分一下,plist方式是字典/數組數據格式寫入文件;而這裏的文件方式不限數據格式。
沙盒內的方式在應用被刪除後數據都會丟失,若是想要不丟失則須要使用KeyChain。
KeyChain本質是一個sqlite數據庫,其保存的全部數據都是加密過的。
KeyChain分爲私有和公有,公有則須要指定group,一個group中的應用能夠共享此KeyChain。
使用KeyChain過程當中要理解下面幾個問題:
1:本身使用的KeyChain和系統自帶的KeyChain數據是隔離的,內部應該是不一樣數據庫文件; 2:KeyChain數據可備份到iCloud中; 3:不須要聯網,也不用登錄iCloud帳號;一個設備一個sqlite數據庫,可是不一樣應用組不共享數據; 4:要在另外一臺設備上使用當前設備存儲的KeyChain信息,須要當前設備進行數據備份, 再在另外一設備上覆原數據;比較經常使用的是iCloud備份方式; 5:系統自帶的KeyChain中帳號密碼分類數據可在系統設置->帳號與密碼裏面看到, 你退出iCloud帳號仍是存在,只是iCloud會幫你備份若是你設置了的話;這個和照片是同樣的道理。
持久化
iOS 數據持久化的幾種方法
聊聊iOS KeyChain
NSClassFromString動態加載是OC中runtime的一個方法,用來從字符串獲得一個class對象,當系統給應用分配的運行內存中沒有這個類時會返回nil;靜態庫在連接階段會被寫入到執行文件,這裏要注意了,若是工程中沒有用到靜態庫中的某些類,那麼這些類是不會寫入到執行文件的,天然系統給應用分配的運行內存中沒有這個類。因此NSClassFromString返回nil只在工程中沒有使用到該類的狀況下。
有人可能會問了,那我能夠在運行的時候手動加載庫到運行內存嗎?動態庫是能夠的,這樣就是插件化了;靜態庫由於最後打包的包裏沒有這個文件了,因此沒辦法獲取到該靜態庫。而動態庫在工程General的Embedded Binaries中加入該動態庫,則打包後包內有一個framework文件夾專門放動態庫,則能夠實現手動加載。
又有人問了,General的Linked Frameworks and Libraries又是什麼做用呢?好吧通常咱們都忽略這個了,由於拖入庫到工程默認就會把該庫加入到此處,若是不加且你工程直接使用了該類則build通不過;使用workspace設置工程依賴實現組件化等爲了解決相應問題而使用此實現思路狀況下,要在適當的project的此處手動添加被依賴庫,若是不加且你工程直接使用了該類則build通不過。
這個問題沒有固定的答案,只須要答到比較重要的點就能夠了。
你能夠從中看到以下的內容。
id的定義:
typedef struct objc_object { struct objc_class *isa; } *id;
NSObject的定義:
@interface NSObject { struct objc_class *isa; }
開始分析得先知道這樣一個事實,iOS中不是全部的類都繼承自NSObject:
@interface NSProxy { struct objc_class *isa; }
因此也就得出了答案:id能夠指向oc中的任何對象,而NSObject*只能指向NSObject及子類對象。
Runtime是一個運行時系統,用來執行編譯連接後的可執行文件;它將不少靜態語言在編譯和連接時期作的事放到了運行時來處理。這種咱們寫代碼更具靈活性,如咱們能夠把消息轉發給咱們想要的對象,或者隨意交換一個方法的實現等。
objc_class的定義:
struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;};
從中能夠看出,咱們能夠修改一些值達到運行時改變原有行爲的目的;好比給對象調用方法是從methodLists查找方法實現等。
延伸內容舉個例子:Runtime是怎麼對對象發送消息的呢?
先須要瞭解object_class中isa和super_class指的是什麼。
其中圖最左邊列表示類的實例對象,中間列表示類,右邊列表示元類。
最左邊實例對象只存在一個objc_object結構體; 類則有objc_object和objc_class兩個結構體,由於類是元類的實例對象; 元類則只有一個objc_class結構體; 對類、實例對象調用方法都是從objc_object中isa查找。
接下來咱們來分析MyClass *myClass = [[MyClass alloc] init];[myClass test]。
首先咱們要拆分紅MyClass *myClass = [MyClass alloc];myClass = [myClass init];[myClass test]。
1:先會執行[MyClass alloc]語句,這是對MyClass類調用類方法,MyClass也是MyClass元類的實例對象, 一樣也有objc_object結構體,objc_object結構體中isa指向MyClass元類;MyClass元類也是一個類, 在其objc_class中的methodLists中並無發現alloc方法; 2:則從MyClass元類的objc_class中super_class進行遞歸查找,最終在NSObject元類中找到alloc方法; 3:[MyClass alloc]返回MyClass的實例對象myClass,這樣myClass就有了一個objc_object結構體, objc_object是經過alloc中class_createInstance建立的,isa指針指向MyClass類; 4:對myClass調用init,由於myClass的objc_object中isa指向MyClass類,因此會在MyClass類的 objc_class中methodLists進行查找,發現沒有,則去MyClass類的objc_class中 super_class進行遞歸查找,最終在NSObject類中找到init方法,init只是作一個初始化的操做, 返回自身;這裏要注意是從類中不是元類中去查找,由於是對實例對象調用方法; 5:這樣實例對象分配空間和初始化就完成了,接下來是對myClass調用test方法, 由於test就是MyClass類中定義的,前面說了myClass的objc_object中isa指向MyClass類,因此直接 就在MyClass的objc_class中methodLists找到了test方法,直接執行test; 6:整個過程執行完畢。
那麼我怎麼獲取objc_class中isa和super_class指向誰呢?
能夠用objc_getClass獲取isa,用class_getSuperclass獲取super_class,class方法則只會返回類自己。
//ClassTwo : ClassOne,ClassOne : NSObject //獲取ClassTwo對應的元類的super_class Class currentClass = objc_getMetaClass("ClassTwo"); //打印這些元類的super_class ClassTwo ClassOne NSObject NSObject nil //基元類的super_calss指向基類 基類的super_class爲nil 造成閉環 for (int i = 0; i < 5; i++) { NSLog(@"Following the super_class pointer %d times gives %p", i, currentClass); currentClass = class_getSuperclass(currentClass); } //打印ClassTwo類的super_class指向 ClassTwo ClassOne NSObject nil currentClass = [ClassTwo class]; for (int i = 0; i < 4; i++) { NSLog(@"Following the super_class pointer %d times gives %p", i, currentClass); currentClass = class_getSuperclass(currentClass); } //打印ClassTwo的isa指向 ClassTwo nil nil nil 任何元類的isa指向基類的元類,就是nil currentClass = [ClassTwo class]; for (int i = 0; i < 4; i++) { NSLog(@"Following the isa pointer %d times gives %p", i, currentClass); currentClass = objc_getClass((__bridge void *)currentClass); }
Objective-C Runtime 運行時之一:類與對象
Objective-C對象模型及應用
iOS中isa的深層理解
Objective-C 中的元類(meta class)是什麼?
iOS底層原理總結 - 探尋Class的本質
能夠給任意類添加屬性,用class_addProperty。
這裏須要注意了,若是咱們在類定義中加的屬性,那麼編譯器會默認生成一個成員變量和getter/setter方法,若是咱們動態加屬性,則只是表示這是一個屬性罷了,咱們須要再添加對應的成員變量和getter/setter方法才能正常使用。
只能給動態添加類添加成員變量,用class_addIvar;該類不能是元類。
能夠給任意類添加關聯對象達到添加"屬性"做用,用objc_setAssociatedObject。
//設置objc_setAssociatedObject(self, (const void *)"key", @(1), OBJC_ASSOCIATION_ASSIGN);//獲取id object =objc_getAssociatedObject(self, (const void *)"key");
對象關聯的使用objc_setAssociatedObject
Ivar 詳解
要了解KVO,咱們得知道KVC和KeyPath。
KeyPath:鍵路徑;運行時系統根據鍵路徑找到最後的屬性/成員變量/關聯對象進行相應的操做。
KVC:容許開發者經過Key名直接訪問對象的屬性/成員變量/關聯對象;並有一組api供開發者使用,像操做字典同樣操做對象屬性/成員變量/關聯對象。
... - (void)setValue:(nullable id)value forKey:(NSString *)key - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath ...
爲何說是屬性/成員變量/關聯對象呢?
聲明屬性編譯器默認會給咱們生成對應的私有成員變量,其實屬性就是私有成員變量+getter+setter罷了; 這裏咱們不考慮幺蛾子狀況,好比聲明瞭兩個屬性year和month,你又寫了這樣的代碼@synthesize year = month;那麼很差意思,這樣的話編譯器不會生成_month和_year成員變量了,也不會生成month的getter和setter方法,只會生成一個month成員變量和year的getter和setter方法,操做self.year 就至關於操做了month成員變量; 成員變量是本身寫在類擴展、類定義或類實現後成員變量聲明區中的變量;類定義中聲明的默認是受保護 類型,類擴展和類實現聲明的默認是私有類型;任何類型的變量均可以被子類繼承; 也可用Runtime修改/獲取值。 關聯對象是若是在分類中聲明瞭"屬性"且本類中沒聲明,若是本類中聲明瞭那麼操做的將會是本類中的屬性, 編譯器不會生成對應的成員變量,只是生成了getter和setter罷了;KeyPath了分類中的"屬性"實際上是調用了分類中對應"屬性"重寫的getter和setter罷了, 內部實現通常是設置/返回關聯對象的值。
這裏須要注意了。若是咱們聲明一個屬性爲year,那麼你下面兩種方式均可以修改到year的值,理由上面已經說過了。
[xxx setValue:@(10) forKey:@"year"]; [xxx setValue:@(20) forKey:@"_year"];
若是你聲明瞭屬性year,你又添加了一個成員變量year,那麼將會有year和_year兩個成員變量,self.year操做的_year成員變量。你能夠加@synthesize year = year告訴編譯器year屬性使用year成員變量而不用再生成_year;那麼下面的這句話就會崩潰。
[xxx setValue:@(20) forKey:@"_year"];
KVO:鍵值對觀察者,在監聽屬性值變化時發出一個通知給監聽者。
... - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context ...
系統提供的接口,在新/舊值同樣時也會發出通知。
實現KVO方式不少種,咱們看看系統KVO的內部大概實現思路。
假設有一個Person類,該類有一個name屬性。執行下面語句的時候。 [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; 其實系統動態建立了一個子類NSKVONotifying_Person,並把person的isa指針指向了NSKVONotifying_Person;若是咱們打印出NSKVONotifying_Person因此方 法,能夠獲得這麼幾個:setName:、class、dealloc和_isKVOA。重點是setName:方法,咱們打印 setName:的內容爲:(Foundation`_NSSetObjectValueAndNotify);這個是一個私有方法,不過咱們 不難猜到通知是從這個方法發出來的。那麼當咱們改變person的name屬性時,實際上是走了NSKVONotifying_Person的setName:方法,該方法先調用Person的setName:方法給name賦值後 發出一個通知給監聽者。
我看了一些網上本身實現KVO的作法,網上的大部分實現只能給實例添加一個監聽對象、不支持實例變量添加KVO、不支持keyPath;要本身仿寫KVO實際上是很是困難。
iOS開發技巧系列---詳解KVC(我告訴你KVC的一切)
iOS KVO的底層實現原理
KVO 的內部實現
這裏咱們講ARC環境下;修飾符主要分爲下面的幾類。
nonatomic:原子性訪問,對屬性賦值的時候不加鎖,多線程併發訪問會提升性能。
atomic:屬性默認爲atomic,提供多線程安全,在多線程環境下,原子操做是必要的,不然有可能引發錯誤的結果。
在iOS開發中,幾乎全部屬性都聲明爲 nonatomic。 atomic的做用只是給getter和setter加了個鎖,atomic只能保證代碼進入getter或者setter函數內部時是安全的,一旦出了getter和setter,多線程安全只能靠程序員本身保障了。
readwrite:同時產生setter/getter方法。
readonly:只產生簡單的getter,沒有setter。
有的朋友會問怎麼沒有writeonly?由於寫權限包含了讀權限。
copy:目標對象引用計數不變,拷貝一份引用計數爲1的對象,該屬性指向拷貝對象。
weak:目標對象引用計數不變,該屬性指向目標對象地址,當目標對象銷燬時,該屬性置爲nil。
strong:目標對象引用計數+1,該屬性指向目標對象地址。
對於NSString對象須要單獨考慮。
NSString *a = @"abc";//@"abc"被放到常量區,對a對象copy和strong引用計數不會變化,a是NSCFConstantString類型。NSString *a = [NSString stringWithFormat:@"abc"];//在堆上分配內存獲得的a是NSCFString對象,對a對象copy和strong都只是引用計數+1。
對於可變對象賦值給copy屬性時會變成不可變對象。
nonnull:對象不該該爲nil;當賦值爲nil時編譯器會給出警告。
NS_ASSUME_NONNULL_BEGIN//之間的屬性都被認爲是nonnull的NS_ASSUME_NONNULL_END
nullable:對象能夠爲nil;屬性默認是nullable的。
iOS中property的關鍵字(史上最詳解)
iOS屬性經常使用關鍵字解析
NSString特性分析學習
iOS多線程到底不安全在哪裏?
如下是比較經常使用的五分區方式,固然也不排除網上有其餘的分區方式。
棧的大小在編譯時就已經肯定了,通常是2M;棧是一塊從高到低地址的連續區域,存放臨時變量和執行函數時的內存等。棧內存分配分爲動態和靜態,靜態如自動變量(局部變量)等,動態如alloc等。
堆是從低到高地址的不連續區域,相似鏈表;用來存放malloc或new申請的內存。
存放靜態/全局變量;全局區細分爲未初始化/初始化區。
存放常量;程序中使用的常量會到常量區獲取。
能夠看看這個例子來理解一下。
...int a;//a在全局未初始化區int a = 10;//a在全局初始化區 10在常量區static int a = 10;//a在靜態區 10在常量區//程序入口int main(...) { int a = 10;//a在棧區 10在常量區 static int a = 10;//a在靜態區 10在常量區 char *a = (char *)malloc(10); //a在棧區 malloc後的內存在堆區 ... }
存放二進制代碼,運行程序就是執行代碼,代碼要執行就要加載進內存(RAM運行內存)。
iOS程序中的內存分配分區
iOS基礎全局變量·靜態變量·局部變量·自動變量
告訴編譯器,這個全局變量在本文件找不到就去其餘文件去找。若有必要須要使用#import "x.h"這樣編譯器才知道到哪裏去找。
//.hint age = 10;//error 不能.h此處聲明全局非靜態變量,.m中能夠extern int age = 10;//error 和int age = 10;等價extern static int age = 10;//全局靜態變量聲明不和extern一塊兒用@interface Class : NSObject...@end//.mextern static int age = 10;//全局靜態變量聲明不和extern一塊兒用@implementation Class { int age;//成員變量不能用做extern;} - (void)test { extern int age = 10;//error 由於這並非全局變量 static int age = 10;//error 由於這並非全局變量 extern int age;//error 由於這並非全局變量} ...@end
使用extern前要保證對應變量被編譯過
//.hextern int age;//error extern在聲明前extern static int age;//error extern沒有static@interface Class : NSObject...@end//.mstatic int age = 10;@implementation Class ...@end
//.hstatic int age = 10;extern int age;//正確 @interface Class : NSObject...@end//.m@implementation Class ... - (void)test { extern int age;//正確 聲明和extern能夠在不一樣文件中}@end
全局非靜態變量
//.hextern int age;//正確@interface Class : NSObject...@end//.mint age = 10;@implementation Class ...@end
C語言的概念;本質是函數,返回指針。
char *fun() { char *p = ""; return p; }
C語言的概念;本質是指針,指向函數。
int fun(int a,int b) { return a + b; }int (*func)(int,int); func = fun; func(1,2);//3
OC語言的概念;表示一個代碼塊,OC中視爲對象;挺像C函數指針的。
//typedeftypedef int (^SumBlock)(int a,int b); SumBlock sumBlock = ^(int a,int b) { return a + b; }; sumBlock(1,2);//3//普通 int (^sumBlock)(int a,int b) = ^(int a,int b) { return a + b; }; sumBlock(1,2);//3
指針函數與函數指針(C語言)
iOS開發-由淺至深學習block
咱們基本都是ARC環境,因此回答以ARC角度。
講這個以前,咱們須要搞清楚一個概念,這個block存在內存什麼區域的?
若是這個block內部沒有訪問棧、堆的變量,那麼這個block存在代碼區;反之存在堆區。block內部修改棧區的變量,該變量須要加__block修飾,這樣會將變量從棧上覆制到堆上。棧上那個變量會指向複製到堆上的變量。block內部修改堆區的變量不用加__block。
由於堆區不斷有變量建立和銷燬,block做爲屬性時咱們須要加copy或者strong修飾。
__weak咱們就在block和聲明屬性中看到過。
如block是被self強引用的。
@property (nonatomic, copy) void (^Block)(void);
那麼在Block內部使用self時,Block內部又會對self進行一次強引用;這就造成了循環引用,因此須要對self進行__weak。
__weak typeof(self) weakSelf = self;
弱引用不會影響對象的釋放,當對象被釋放時,全部指向它的弱引用都會自定被置爲nil。
固然了,self沒有強引用block時是不須要__weak的。
- (void)func() { void (^Block)(void) = ^(void) { [self test]; }; }
對self進行了__weak,那麼在block執行時weakSelf隨時可能被釋放,因此內部須要對weakSelf進行__strong讓self不被釋放。
__strong typeof(self) strongSelf = weakSelf;
在block執行完成後,strongSelf會被釋放,不會形成循環引用。
iOS中block塊的存儲位置&內存管理
__block & __weak & __strong
當點擊一個按鈕的時候,事件若是傳遞到按鈕這個第一響應者上,這就是事件傳遞鏈要作的事情。系統根據下面兩個方法來傳遞事件。
//該點是否在本視圖點擊範圍內 point已經被轉換了成本視圖對應frame- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { //內部實現大概是這樣 return CGRectContainsPoint(self.bounds, point); }//本視圖/子視圖是否可以傳遞本事件 point已經被轉換了成本視圖對應frame- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //內部實現大概是這樣 //用戶交互爲NO,不處理 if(self.userInteractionEnabled == NO) { return nil; } if([self pointInside:point withEvent:event]) { NSArray * superViews = self.subviews; //倒序從最上面的一個視圖開始查找 for (NSUInteger i = superViews.count; i > 0; i--) { UIView * subview = superViews[i - 1]; //轉換座標系 使座標基於子視圖 CGPoint newPoint = [self convertPoint:point toView:subview]; //獲得子視圖 hitTest 方法返回的值 UIView * view = [subview hitTest:newPoint withEvent:event]; //若是子視圖返回一個view 就直接返回 不在繼續遍歷 if (view) { return view; } } //全部子視圖都沒有返回 則返回自身 return self; } return nil; }
當點擊按鈕的時候,其實事件是這樣傳遞的:AppDelegate->UIApplication->UIWindow->xxx->UIViewController->UIView->UIButton。
當找到事件第一響應者以後,該事件如何響應,就是事件響應鏈要作的事情。
接着上面的例子,UIButton就是系統找出來的第一響應者,那麼會執行以下方法:
//觸摸事件開始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {}//觸摸事件移動- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {}//觸摸事件結束- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {}//觸摸事件取消- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {}
若是你本身不處理,你能夠self.nextResponder讓下一個響應者處理。
//觸摸事件開始- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //讓下一個響應者處理 [self.nextResponder touchesBegan:touches withEvent:event]; } ...
響應者鏈也就是傳遞鏈的倒序。
這裏須要注意的就是,若是給UIButton添加了target和UITapGestureRecognizer那麼點擊按鈕只會執行UITapGestureRecognizer,也就是說若是手勢和target同時知足條件則只會執行手勢。target也是touchesxxx中斷定的,你能夠重寫touchesxxx內部實現爲空,你會發現並不影響手勢但會影響target。
iOS事件攔截和事件轉發
UIView之userInteractionEnabled屬性介紹
iOS觸摸事件那點兒事
RunLoop是iOS中的Event Loop實現,簡單來講是一個do while循環,須要GCD等協做執行;循環體內沒事件須要處理就休眠,被mach_port喚醒以後處理相應事件後判斷條件繼續進入循環。一個線程只能有一個根RunLoop,RunLoop保存在TSD中;一次RunLoop執行只能指定一個RunLoopMode,mode有timer、source、common和observer等;幾乎全部的操做都是經過Call out方法進行回調的,好比點擊是經過source1到source0再到action回調;要切換mode必須退出當前RunLoop並指定新mode從新執行。
iOS刨根問底-深刻理解RunLoop
Run Loop 記錄與源碼註釋
深刻理解RunLoop
RunLoop 源碼閱讀
有容錯值用gcd timer實現,反之用mk_timer實現,mk_timer更準確;觸發點並非一開始就計算出的,而是每次觸發後動態計算;當RunLoop執行一個阻塞操做時,觸發點可能延遲,可能會跳過中間的觸發點。
iOS的一個多核調度器,用於優化應用程序以支持多核處理器;內部默認建立一個串行主隊列和12種不一樣優先級的併發隊列,能夠本身建立默認優先級爲Default的串行/併發隊列;獲得隊列後能夠向隊列同步/異步方式添加任務,異步GCD會按需建立線程;向主隊列添加的任務將由主線程RunLoop處理;GCD還能夠實現定時器、延遲、柵欄、信號量和組等。
iOS多線程:『GCD』詳盡總結
iOS GCD之dispatch_semaphore(信號量 )
GCD源碼分析
釋放池是由n個page組成的雙向鏈表,線程和釋放池一一對應;釋放池push時會放入哨兵對象,根據next指針放置添加進來的自動釋放對象;釋放池pop時會將hotpage中next指針依次向前移動,對所指對象調用release直到遇到結束標誌,清理過程可跨越page;從main方法中知道iOS項目默認是包裹在大的釋放池中;RunLoop開始循環、休眠和退出時會對釋放池進行poolpush/poolpop操做。
理解 iOS 的內存管理
iOS 自動釋放池原理探究
黑幕背後的Autorelease
iOS中autorelease的那些事兒
NSTimer、GCD定時器和CADisplayLink。
NSTimer根據容錯值使用GCD定時器或mk_timer。
NSTimer和CADisplayLink依賴於RunLoop,GCD定時器不依賴。
iOS定時器 NSTimer、CADisplayLink、GCD
view是layer的代理對象;view負責管理layer,layer負責渲染;view初始化的時候默認會建立一個layer;設置view的frame和bounds等內部實際上是修改layer對應屬性。
互斥鎖:NSLock、pthread_mutex、@synchronized。
加鎖後,其餘加鎖操做阻塞直到解鎖。
遞歸鎖:NSRecursiveLock。
一個線程能夠屢次加鎖,相應的要對應屢次解鎖其餘線程才能夠加鎖。
條件鎖:NSCondition、NSConditionLock。
鎖知足指定條件時才繼續執行,不然阻塞。
信號量:dispatch_semaphore。
wait操做阻塞直到signal被調用。
讀寫鎖:pthread_rwlock。
讀模式佔有鎖時其餘線程只能讀;寫模式佔有鎖時其餘線程不能進行任何操做。
應用、表示、會話、傳輸、網絡、數據鏈路、物理。
應用、傳輸、網絡、數據鏈路。
傳輸層單位:段; 網絡層單位:報; 鏈路層單位:幀。
ARC是引用計數,是一個簡單而有效的管理對象生命週期的方式;編譯器在代碼合適的地方自動給咱們加了一些關鍵字,好比:retain、release和autorelease等;這樣咱們就不用手動管理對象生命週期。
區別仍是有不少的,答到核心的就能夠了。
1:類指針賦值時只是複製了地址,結構體是複製內容;
2:類不能有同名同參數個數的方法,結構體能夠;
3:結構體方法實現編譯時就肯定了,類方法實現可動態改變;
4:內存分配不同,結構體在棧,類在堆;
5:結構體能夠多重繼承,類只能單繼承。
網上不少文章說結構體不能有方法,結構體不能繼承; 請看清楚題,說的是iOS中的結構體,不是C中的結構體。
一個協議一時間只能有一個代理對象,而一個通知一時間能夠有多個監聽者。
通知的發送和監聽依靠通知中心,協議則能夠本身建立,經過setDelegate指定代理對象。
IOS中的協議Protocol與代理Delegate以及通知
訪問野指針:數據越界、對象已經釋放但對其發送消息等;
內存泄漏:循環引用、imageNamed讀取圖片等;
觸碰內存峯值:for循環聲明變量等;
申請了不使用的內存:聲明變量但未使用、只在某個邏輯分支用到某些變量但一開始就初始化等。
訪問野指針:訪問前加判斷;
這部分問題大可能是多線程形成的,好比兩個線程同時執行一個方法,方法內部對數組有更新操做。
內存泄漏:Instrument Leaks/Allocations檢測、使用imageWithContentsOfFile讀取圖片等;
imageNamed會緩存加載的圖片;imageWithContentsOfFile只是簡單的加載圖片。
觸碰內存峯值:手動添加釋放池;
for循環內大量建立局部變量,這些局部變量會等到RunLoop的下一個循環才釋放, 而手動加入釋放池則會提早釋放。
申請了不使用的內存:懶加載。
init loadView viewDidLoad viewWillAppear viewDidAppear viewWillDisappear viewDisDisappear viewWillUnload(Deprecated) viewDidUnload(Deprecated) delloc
這裏要注意loadView方法,平時咱們都沒有去管這個事情;
這個方法執行後self.view纔有值,也就是說這個方法完成了view的加載,內部會根據控制器名字、是否本身實現、xib等條件來加載view。
若是本身實現loadView方法,而且方法體爲空,則self.view爲nil, 而且viewDidLoad方法會調用兩次; 這說明viewDidLoad是被loadView調起的,在viewDidLoad中若是self.view爲nil會再調用一次loadView。
延伸瞭解。
viewWillUnload和viewDidUnload被棄用了,內部是在清理self.view。 storyboard加載的是控制器+View,而xib加載的是View,也就是說storyboard加載時會調用 ViewController的awakeFromNib方法。 awakeFromNib:表示ViewController/View從xib加載。
iOS開發筆記(九):UIViewController的生命週期
frame表示在superview座標系中的位置和大小,bounds表示自身座標系,默認左上角爲0,0;
bounds給subview參考,其結合自身frame肯定顯示位置;
改變bounds不會改變本身在superview中的位置,但會改變subview的位置。
改變bounds時若是寬高和frame寬高不一致則會以center爲中心縮放,此時將改變frame並從新顯示。
若是屬性沒有自動生成getter/setter方法,則告訴編譯器去生成。
告訴編譯器不要生成此屬性的getter/setter方法,開發者本身去實現。
iOS @property、@synthesize和@dynamic
先了解一下NSURLCache。
NSURLCache默認會對部分GET請求進行緩存; 請求一張圖片,第一次請求成功後NSURLCache會緩存圖片內容,第二次請求的時候直接從緩存中取就能夠了, 並無真正發起請求;NSURLCache系統也是默認啓動的。
看了上面的介紹你發現這不就是你最終要實現的功能嗎?那SDWebImage到底作了什麼呢?
1:正在下載該url的圖片,直接返回;2:處理多線程問題,好比cell複用形成的顯示錯亂問題,也就是下載以前先取消;3:增長一層內存緩存,直接從url獲得image,NSURLCache存的是data;4:相同url請求時只加了一個finish回調;5:請求作了排隊處理,控制資源。
因此能夠把SDWebImage當作是NSURLCache的封裝,等同於NSOperationQueue和GCD的關係。
SAX:基於事件驅動,逐行解析,採用協議回調;文件比較大時建議用此方式。
DOM:文檔對象模型,解析時將整個文檔讀入並結構化成樹,經過樹狀結構讀取相關數據;文件比較小時建議用此方式。
我看的是AFNetWorking 3.0版本,是基於NSURLSession封裝的,NSURLSession使用起來已經足夠方便了,因此AFNetWorking作的內容相對來講少了不少。
1:設置請求/響應序列化對象,這樣能夠幫你檢查請求/響應參數是否正確; 2:https驗證功能,能夠用於自簽名證書等特殊狀況,若是不啓用則讓系統幫你驗證, 那麼就只能是蘋果官方承認的CA證書才能經過; 3:對於不一樣的請求方式和參數,幫你設置請求,好比頭部的Content-type。
快速:協議簡單,通信速度快;
靈活:經過Content-Type能夠傳遞任何類型的數據;
長鏈接:一次鏈接處理多個請求,並支持管線(同時發出多個請求,不用等到前面請求響應)、多路複用(一個請求屢次響應);
無狀態:協議對於事務處理沒有記憶能力,若是本次請求須要上次的數據則須要重傳。
GET在URL後以?形式拼接參數,POST將參數放在body中;
GET也能夠在body裏面放數據,POST也能夠在URL放數據,Http只是規定,你也能夠不遵照啊。 至於服務器獲不獲取就看具體實現了。
POST比GET安全,由於GET參數在URL中,用戶一眼就能看到;
POST固然也能夠用工具看到參數。
語意理解,GET用來獲取數據,POST用來上傳數據;
客戶端和服務器對URL數據通常有1kb的限制。
Http協議對URL並無限制。
iOS-網絡編程(一)HTTP協議
http2.0的時代真的來了...
Http是基於Tcp的,而Socket是一套編程接口讓咱們更方便的使用Tcp/Ip協議;Http是應用層協議,在Tcp/Udp上一層。
1:Http是基於"請求-響應"的,服務器不能主動向客戶端推送數據,只能藉助客戶端請求到後向客戶端推送數據,而Sokcet雙方隨時能夠互發數據;
2:Http不是持久鏈接的,Socket用Tcp是持久鏈接;
3:Http基於Tcp,Socket能夠基於Tcp/Udp;
4:Http鏈接是經過Socket實現的;
5:Http鏈接後發送的數據必須知足Http協議規定的格式:請求頭、請求頭和請求體,而Socket鏈接後發送的數據沒有格式要求。
Tcp是傳輸層可靠傳輸協議,發送數據前要進行握手,數據發送完時能夠揮手斷開鏈接釋放資源;揮手比握手多一次是由於斷開須要雙方單專斷開,而握手被鏈接端是被動打開的。
首先,咱們必需要明白一些重要的Tcp頭部標誌。
序號:每個包都有一個序號,此序號mod2^32; 確認號:用來確認收到對方的包,此序號mod2^32; SYN:爲1時表示但願與對方創建鏈接; FIN:爲1時表示我已經沒有數據發送了,但願斷開鏈接; ACK:爲1時確認號有效,Tcp規定除了第一次創建鏈接的包ACK都要置爲1。
這是我抓的訪問百度握手/揮手過程,Http協議也是基於Tcp的。
10.10.9.141:客戶端ip;180.97.33.107:百度ip。 第一次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 64) 10.10.9.141.50806 > 180.97.33.107.http: Flags [S], cksum 0x95b1 (correct), seq 589117916, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 815096723 ecr 0,sackOK,eol], length 0 第一次握手 SYN=1 seq=589117916 第二次握手 IP (tos 0x0, ttl 53, id 0, offset 0, flags [DF], proto TCP (6), length 64) 180.97.33.107.http > 10.10.9.141.50806: Flags [S.], cksum 0x2cb0 (correct), seq 1775662715, ack 589117917, win 8192, options [mss 1408,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0 第二次握手 SYN=1 ACK=1 seq=1775662715 ack=589117917第三次握手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0xa25d (correct), seq 589117917, ack 1775662716, win 8192, length 0 第三次握手 ACK=1 seq=589117917 ack=1775662716數據傳輸... 數據傳輸... 第一次揮手 IP (tos 0x0, ttl 50, id 43779, offset 0, flags [DF], proto TCP (6), length 40) 180.97.33.107.http > 10.10.9.141.50806: Flags [F.], cksum 0xb3c8 (correct), seq 1775665497, ack 589118060, win 808, length 0 第一次揮手 FIN=1 ACK=1 seq=1775665497 ack=589118060第二次揮手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [.], cksum 0x973b (correct), seq 589118060, ack 1775665498, win 8117, length 0 第二次揮手 ACK=1 seq=589118060 ack=1775665498第三次揮手 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40) 10.10.9.141.50806 > 180.97.33.107.http: Flags [F.], cksum 0x96ef (correct), seq 589118060, ack 1775665498, win 8192, length 0 第三次揮手 ACK=1 FIN=1 seq=589118060 ack=1775665498第四次揮手 IP (tos 0x0, ttl 50, id 0, offset 0, flags [DF], proto TCP (6), length 40) 180.97.33.107.http > 10.10.9.141.50806: Flags [.], cksum 0xb3c7 (correct), seq 1775665498, ack 589118061, win 808, length 0 第四次揮手 ACK=1 seq=1775665498 ack=589118061
通俗大白話來理解TCP協議的三次握手和四次分手
tcpdump查看三次握手
1:系統內核作好準備工做,好比把應用數據從ROM移到RAM;
2:libdyld接管後續工做,加載動態庫到內存等;
3:ImageLoader把動態庫和可執行文件加載到內存;
4.1:Runtime解析文件符號表,註冊類、調用load方法;
4.2:libdispath等其餘庫初始化;
5:libdyld調用main方法。
線程安全:多線程環境下保證數據的完整性。
把操做放入隊列線性執行,可用GCD和NSOperationQueue。
用鎖/信號量造成操做互斥。
讓操做原子執行,系統提供了一些原子執行的方法。
NSOperationQueue沒有串行/併發隊列,但能夠設置最大併發數;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue能夠暫停/取消操做;
NSOperationQueue支持更多的功能,好比KVO和自定義操做;
NSOperationQueue能夠設置操做的優先級,GCD只能設置隊列的優先級。
提供的功能是類似的;
NSOperationQueue是GCD的封裝。
其實設計模式有不少,你只要答上6個就能夠了。
裝飾模式:分類; 代理模式:協議; 工廠模式:UIButton建立; 原型模式:[object copy]; 觀察者模式:KVO; 迭代器模式:數組的遍歷; 單例模式:Appdelegate; 命令模式:給對象發消息; 職責鏈模式:事件傳遞鏈; 中介者模式:模塊解耦; 解釋器模式:Siri語意識別;
Block本質就是函數,根據有無返回值/參數有4種Block;
在ARC下根據是否訪問棧/堆變量可分爲全局/堆Block;
Block內修改棧變量時須要__block修飾,__block的做用其實就是在堆上建立一個指向棧變量的指針達到修改棧變量值的目的。
當向對象發送了一個不存在的消息時,會先走resolvexxxMethod動態處理流程,你能夠在這之中用Runtime動態添加該方法;你也能夠不處理,則進入轉發流程。
先走forwardingTargetForSelector,你能夠在這裏返回一個能夠處理aSelector的對象,不然走forwardInvocation操做anInvocation;forwardInvocation須要實現methodSignatureForSelector獲得一個方法簽名。
系統維護了一個weak變量組成的hash表,key爲weak指向的變量地址,value爲weak變量的地址;當對象引用計數爲0時,遍歷hash表,設置對應的weak變量爲nil。
咱們能夠從Runtime的源碼中看到,發消息最終會走objc_msgSend()並把nil最爲第一個參數,其內部用匯編實現,裏面會判斷第一個參數是否爲nil,若是爲nil則返回0,因此iOS容許對nil發送消息;這個0針對不一樣的selector返回值有不一樣的表示,好比:0、nil和結構體等。
SafeArea是View的屬性,是iOS11出來用來代替bottomLayoutGuide/topLayoutGuide的,bottomLayoutGuide/topLayoutGuide是ViewController的屬性;從這裏能夠看出,SafeArea更靈活,能夠對每個View進行配置;他們都是讓控件不被父View遮擋系統自動計算的距離,你固然也能夠關閉這個功能;iOS11前用automaticallyAdjustsScrollViewInsets,iOS11後用contentInsetAdjustmentBehavior。
cell重用,異步執行耗時操做這種就不用提了,這都是你們能想到的,咱們能夠說一點其餘的。
1:減小視圖層次;
2:正確使用api,好比設置rowHeight而不去取dataSource;
3:減小離屏渲染;
圓角圖片 陰影 ...
4:設置視圖不透明減小渲染代價,若是有透明度則會根據多個視圖才知道一個像素點顯示什麼顏色;
5:RunLoop空閒計算cell高度並緩存;
6:利用RunLoop的mode在滾動時不執行耗時操做。
若是沒有ssl/tls證書,每一個不一樣客戶端訪問服務器都須要生成一對密鑰,這會形成服務器端存儲的密鑰太多等問題;讓所用客戶端使用統一的服務器ssl/tls證書則能夠解決這些問題。
https=http+ssl/tls,拿咱們客戶端請求api來講,咱們適配https時要後臺給咱們一個cer證書,這個證書裏面包括了:頒發機構、有效期、RSA公鑰和Hash指紋等信息;客戶端會把這個證書交給請求庫,請求庫負責完成加/解密;爲何須要從頒發機構申請證書呢?由於咱們的api和web可能在一個域名下,一個域名只能一個證書,也就是說若是隻有api訪問這個域名咱們徹底能夠本身建立一個證書設置受信用;然而若是web也要訪問該域名則瀏覽器不會認爲這個證書受信用會拒絕訪問。
HTTPS 原理詳解
圖解 HTTPS:Charles 捕獲 HTTPS 的原理