# 運行時
```
1、程序中任何代碼都會被轉化成runtime的C代碼執(zhí)行,如[target doSomeThing]會轉化成objc_msgSend(tagert,@selector(doSomeThing))
2、OC中一切都是對象,實例,類本質也是對象,在runtime中結構體表示。
3、相關定義
* typedef struct objc_method *Method; 方法
* typedef struct objc_ivar *Ivar;實例變量
* typedef struct objc_category *Category;實例Category
* typedef struct objc_property *objc_property_t;類中聲明的屬性
4、基于以上,可以實時獲取當前類中所有屬性,方法,變量
* 獲取屬性:objc_property_t *propertyList = class_copyPropertyList([self class], &count);
* 獲取方法:Method *methodList = class_copyMethodList([self class], &count);
* 獲取變量:Ivar *ivarList = class_copyIvarList([self class], &count);
* 獲取協(xié)議:__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
5、方法調用
* 如果用實例對象調用實例方法,會到實例的isa指針指向的對象(類對象)中操作;
* 如果調用的是類方法,會到類對象的isa指針指向的對象(元類對象)中操作。
* 尋找過程:
* 首先在操作對象中的緩存方法列表中尋找
* 若無,在操作對象中的方法列表中尋找
* 若無,去父類指針指向對象中執(zhí)行1,2
* 依次遍歷,若遍歷到根類還沒有,轉向攔截調用(若沒有實現(xiàn),程序報錯)
* 攔截調用:
* + (BOOL)resolveClassMethod:(SEL)sel;當調用一個不存在的類方法的時候,會調用此方法,默認返回NO
* + (BOOL)resolveInstanceMethod:(SEL)sel;當調用不存在的實例方法時,會調用次方法。
* - (id)forwardingTargetForSelector:(SEL)aSelector;將你調用的不存在的方法重定向到一個其他聲明了這個方法的類,只需要你返回一個有這個方法的target。
* - (void)forwardInvocation:(NSInvocation *)anInvocation; 將你調用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理后,調用invokeWithTarget:方法讓某個target觸發(fā)這個方法。
6、關聯(lián)對象
//首先定義一個全局變量,用它的地址作為關聯(lián)對象的key
static char associatedObjectKey;
//設置關聯(lián)對象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//獲取關聯(lián)對象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey); NSLog(@"AssociatedObject = %@", string);
7、方法交換
* 通常寫在load方法中,保證執(zhí)行時間靠前,且只執(zhí)行一次
* // swizzing method for instance
#define swizzing_sel_instance(_cls_, _sel_1, _sel_2) {\
Method method1 = class_getInstanceMethod(_cls_, _sel_1);\
Method method2 = class_getInstanceMethod(_cls_, _sel_2);\
ASSERT(method1 != NULL);\
ASSERT(method2 != NULL);\
LOG(@"swizzing (%@), -%@(0x%08lx) ==> -%@(0x%08lx), ", NSStringFromClass(_cls_), NSStringFromSelector(_sel_1), (long)method1, NSStringFromSelector(_sel_2), (long)method2);\
if (method1 && method2) {\
method_exchangeImplementations(method1, method2);\
}\
}
```
# 內存管理
```
1、只有OC對象才需要管理,非OC對象不需要內存管理(OC對象放在堆內存里,非OC對象放在棧內存里,棧內存里的東西系統(tǒng)會自動管理)
2、自動釋放池底層實現(xiàn):
3、string的內存管理
* NSString *str1 = @"abcde";字符串“abcde”在常量區(qū)(有且只有一份),str1指針在棧區(qū)。
* NSString *str2 = [NSString stringWithFormat:@"hijkl"];
* 通過stringWithFormat創(chuàng)建的字符串放在堆中(即使字符串內容相同,還是會再次開辟新空間),str2在棧中。
* 通過stringWithFormat創(chuàng)建的字符串,會返回一個autorelease的實例,會在自動釋放池自動釋放。
* 通過initWithFormt創(chuàng)建的字符串,則需要自己進行手動管理內存
4、copy和Mutablecopy
* copy返回immutable對象,mutableCopy返回mutable對象(所以不可以對mutabe對象使用copy操作)
* NSmutableArray *mArray = [array mutableCopy];(此處的內容拷貝,僅僅是拷貝array這個對象,array集合內部的元素仍然是指針拷貝)
* [immutableObject copy] // 淺復制
* [immutableObject mutableCopy] //單層深復制
* [mutableObject copy] //單層深復制
* [mutableObject mutableCopy] //單層深復制
* 使用總結:
* 修飾可變類型的屬性時,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong
* 修飾不可變類型的屬性時,如NSArray、NSDictionary、NSString,用copy。
5、Block內存管理
* 默認情況下,Block的內存是在棧中(程序自動管理)
* 如果對block做了copy操作,block的內存會搬到堆中,對引用對象做一次+1操作
* block內部可以一直引用__block修飾的變量,static修飾的變量,全局變量(Block不可以修改外部變量的值,指的是棧中指針的內存地址,__block修飾的變量之所以可以被修改,是因為__block會將棧中的內存地址放到堆中,所以可以修改【static和全局變量在全局初始化區(qū)】)
* unsafe_unretained和weak的區(qū)別:兩者都不持有對象,當引用計數(shù)為0時,weak會被置為nil,unsafe_unretained不置為nil(有野指針風險)
```
# 多線程# 動畫
```
1、GCD
* 同步:阻塞當前線程
* 異步:不會阻塞當前線程
* 串行:FIFO 依次取出,一個一個執(zhí)行(主隊列)DISPATCH_QUEUE_SERIAL
* 并行:也是FIFO,但是放到不同線程去執(zhí)行(全局隊列)DISPATCH_QUEUE_CONCURRENT
* dispatch_barrier_async:當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執(zhí)行完成后才會開始執(zhí)行自己
* dispatch_barrier_sync:自定義的并發(fā)隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程
2、NSOperation
* NSOperation 只是一個抽象類,所以不能封裝任務。但它有 2 個子類用于封裝任務。分別是:NSInvocationOperation 和 NSBlockOperation ( 默認在當前隊列同步執(zhí)行)
* NSOperationQueue可以設置maxConcurrentOperationCount,可以添加依賴
3、線程同步:防止多個線程搶奪同一塊資源
* 互斥鎖:@synchronized(self)
* NSLock
取消GCD任務 ?
```
# 常見設計模式
# Runloop
```
1、Runloop的寄生于線程:一個線程只能有唯一對應的runloop;但這個根runloop里可以嵌套子runloops;
2、同一時間一個runloop只能在一個mode,切換mode只能退出runloop,再重進指定mode(隔離modeItems使之互不干擾);
3、timerWithTimeInterval:需要手動加到runloop的mode中
scheduledTimerWithTimeInterval:默認已經添加到主線程的runLoop的DefaultMode中
4、mode類型
* kCFRunLoopDefaultMode: 默認 mode,通常主線程在這個 Mode 下運行。
* UITrackingRunLoopMode: 追蹤mode,保證Scrollview滑動順暢不受其他 mode 影響。
* UIInitializationRunLoopMode: 啟動程序后的過渡mode,啟動完成后就不再使用。
* GSEventReceiveRunLoopMode: Graphic相關事件的mode,通常用不到。
* kCFRunLoopCommonModes: 占位mode,作為標記DefaultMode和CommonMode用。
5、應用
* 當tableview的cell上有需要從網絡獲取的圖片的時候,滾動tableView,異步線程會去加載圖片,加載完成后主線程就會設置cell的圖片,但是會造成卡頓??梢宰屧O置圖片的任務在CFRunLoopDefaultMode下進行,當滾動tableView的時候,RunLoop是在 UITrackingRunLoopMode 下進行,不去設置圖片,而是當停止的時候,再去設置圖片。
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
* 常駐子線程,保持子線程一直處理事件
為了保證線程長期運轉,可以在子線程中加入RunLoop,并且給Runloop設置item,防止Runloop自動退出。
self.downloadRunloop = CFRunLoopGetCurrent();
*? 當調用 NSObject 的 performSelecter:afterDelay:后,實際上其內部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。
```
# 事件響應鏈條
```
UIApplication-->UIWindow-->遞歸找到最合適處理的控件-->控件調用touches方法-->判斷是否實現(xiàn)touches方法-->沒有實現(xiàn)默認會將事件傳遞給上一個響應者-->找到上一個響應者-->找不到方法作廢
1、根據main函數(shù)的參數(shù)加載UIApplication->AppDelegate->UIWindow->UIViewController->superView->subViews
關系為:UIApplication.keyWindow.rootViewController.view.subView
事件傳遞機制:
1.當iOS程序中發(fā)生觸摸事件后,系統(tǒng)會將事件加入到UIApplication管理的一個任務隊列中
2.UIApplication將處于任務隊列最前端的事件向下分發(fā)。即UIWindow。
3.UIWindow將事件向下分發(fā),即UIView。
4.UIView首先看自己是否能處理事件,觸摸點是否在自己身上。如果能,那么繼續(xù)尋找子視圖。
5.遍歷子控件,重復以上兩步。
6.如果沒有找到,那么自己就是事件處理者。如果
7.如果自己不能處理,那么不做任何處理。
其中 UIView不接受事件處理的情況主要有以下三種
1)alpha <0.01
2)userInteractionEnabled = NO
3.hidden = YES
2、hittest
UIWindow實例對象會首先在它的內容視圖上調用hitTest:withEvent:,此方法會在其視圖層級結構中的每個視圖上調用pointInside:withEvent:(該方法用來判斷點擊事件發(fā)生的位置是否處于當前視圖范圍內,以確定用戶是不是點擊了當前視圖),如果pointInside:withEvent:返回YES,則繼續(xù)逐級調用,直到找到touch操作發(fā)生的位置,這個視圖也就是要找的hit-test view。
```
# 性能優(yōu)化 (內存占用,耗電,UI渲染)
1、例如一個黑色半透明的可以設置為一個灰色不透明的View替代.原因是這會使系統(tǒng)用一個最優(yōu)的方式渲染這些views:如果一個圖層是完全不透明的,則系統(tǒng)直接顯示該圖層的顏色即可。而如果圖層是帶透明效果的,則會引入更多的計算,因為需要把下面的圖層也包括進來,進行混合后顏色的計算
2、永遠不要使主線程承擔過多。因為UIKit在主線程上做所有工作,渲染,管理觸摸反應,回應輸入等都需要在它上面完成
3、如果要在UIImageView中顯示一個來自bundle的圖片,你應保證圖片的大小和UIImageView的大小相同,盡可能的先在子線程把圖片縮放后再進行賦值。
4、盡量使用懶加載,不要一次性創(chuàng)建所有subview,等用到的時候再進行加載。(NSDateFormatter和NSCalendar初始化就比較慢)
5、處理內存警告
在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
注冊并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
6、如果你用小圖平鋪來創(chuàng)建背景,你就需要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費很多內存:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
7、如果你要加載一個大圖片而且是一次性使用,那么就沒必要緩存這個圖片,用imageWithContentsOfFile足矣,這樣不會浪費內存來緩存它。
然而,在圖片反復重用的情況下imageNamed是一個好得多的選擇。
# tableView CollectionView 性能調優(yōu)
提前計算并緩存好高度(布局),因為heightForRowAtIndexPath:是調用最頻繁的方法;
異步繪制,遇到復雜界面,遇到性能瓶頸時,可能就是突破口;
滑動時按需加載[快速滑動只展示cell,不加載圖片],這個在大量圖片展示,網絡加載的時候很管用?。⊿DWebImage已經實現(xiàn)異步加載,配合這條性能杠杠的)。
除了上面最主要的三個方面外,還有很多幾乎大伙都很熟知的優(yōu)化點:
正確使用reuseIdentifier來重用Cells
盡量使所有的view opaque,包括Cell自身
盡量少用或不用透明圖層
如果Cell內現(xiàn)實的內容來自web,使用異步加載,緩存請求結果
減少subviews的數(shù)量
在heightForRowAtIndexPath:中盡量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結果
盡量少用addView給Cell動態(tài)添加View,可以初始化時就添加,然后通過hide來控制是否顯示
NSLog向??輸出添加時間戳和標識符,而println則不會;
NSLog同步日志語句,以便如果您同時發(fā)出來自不同線程的日志,則它們將不會彼此重疊; println可能會導致混亂的輸出,如果同時從單獨的線程執(zhí)行,而不做一些同步
iOS啟動做了哪些事?
1.main 函數(shù)
2.UIApplicationMain
創(chuàng)建UIApplication對象
創(chuàng)建UIApplication的delegate對象
delegate對象開始處理(監(jiān)聽)系統(tǒng)事件(沒有storyboard)
程序啟動完畢的時候, 就會調用代理的application:didFinishLaunchingWithOptions:方法
在application:didFinishLaunchingWithOptions:中創(chuàng)建UIWindow
創(chuàng)建和設置UIWindow的rootViewController
顯示窗口
3.根據Info.plist獲得最主要storyboard的文件名,加載最主要的storyboard(有storyboard)
創(chuàng)建UIWindow
創(chuàng)建和設置UIWindow的rootViewController
顯示窗口
mas_equalTo有自動包裝的功能,equalTo沒有自動包裝功能。
用mas_equalTo可以把基本數(shù)據類型轉換為對象類型,這個過程叫裝箱,比如自動將1包裝成@1。
load和initialize
load和initialize方法都會在實例化對象之前調用,以main函數(shù)為分水嶺,前者在main函數(shù)之前調用,后者在之后調用。這兩個方法會被自動調用,不能手動調用它們。
load和initialize方法都不用顯示的調用父類的方法而是自動調用,即使子類沒有initialize方法也會調用父類的方法,而load方法則不會調用父類。
load方法通常用來進行Method Swizzle,initialize方法一般用于初始化全局變量或靜態(tài)變量。
load和initialize方法內部使用了鎖,因此它們是線程安全的。實現(xiàn)時要盡可能保持簡單,避免阻塞線程,不要再使用鎖。
autoreleasePool
自動釋放池可以延長對象的聲明周期,如果一個事件周期很長,比如有一個很長的循環(huán)邏輯,那么一個臨時變量可能很長時間都不會被釋放,一直在內存中保留,那么內存的峰值就會一直增加,但是其實這個臨時變量是我們不再需要的。這個時候就通過創(chuàng)建新的自動釋放池來縮短臨時變量的生命周期來降低內存的峰值。