UIView
和CALayer
是什麼關係
UIView
繼承自UIResponder
類,能夠響應事件CALayer
直接繼承自NSObject
類,不能夠響應事件UIView
是CALayer
的delegate
(CALayerDelegate
)UIView
主要處理事件,CALayer
負責繪製- 每一個
UIView
內部都有一個CALayer
在背後提供內容的繪製和顯示,而且UIView
的尺寸樣式都由內部的Layer
所提供。二者都有樹狀層級結構,Layer
內部有SubLayers
,View
內部有SubViews
,可是Layer
比View
多了個AnchorPoint
NSCache
和NSMutableDictionary
的相同點與區別相同點:
NSCache
和NSMutableDictionary
功能用法基本是相同的 區別:NSCache
是線程安全的,NSMutableDictionary
線程不安全,Mutable開發的類
通常都是線程不安全的 當內存不足時NSCache
會自動釋放內存(因此從緩存中取數據的時候總要判斷是否爲空)NSCache
能夠指定緩存的限額,當緩存超出限額自動釋放內存NSCache
的Key
只是對對象進行了Strong
引用,而非拷貝,因此不須要實現NSCopying
協議git
atomic
的實現機制;爲何不能保證絕對的線程安全(最好能夠結合場景來講)
atomic
會對屬性的setter/getter
方法進行加鎖,這僅僅只能保證在操做setter/getter
方法是安全的。不能保證其餘線程的安全- 例如:線程1調用了某一屬性的
setter
方法並進行到了一半,線程2調用其getter
方法,那麼會執行完setter
操做後,再執行getter
操做,線程2會獲取到線程1setter
後的完整的值;當幾個線程同時調用同一屬性的setter、getter
方法時,會獲取到一個完整的值,但獲取到的值不可控
對象在運行時獲取其類型的能力稱爲內省。內省能夠有多種方法實現 OC運行時內省的4個方法:程序員
-(BOOL) isKindOfClass: // 判斷是不是這個類或者這個類的子類的實例
-(BOOL) isMemberOfClass: // 判斷是不是這個類的實例
複製代碼
-(BOOL) respondsToSelector: // 判斷實例是否有這樣方法
+(BOOL) instancesRespondToSelector: // 判斷類是否有這個方法
複製代碼
objc
在向一個對象發送消息時,發生了什麼根據對象的isa指針找到該對象所屬的類,去objc的對應的類中找方法 1.首先,在相應操做的對象中的緩存方法列表中找調用的方法,若是找到,轉向相應實現並執行 2.若是沒找到,在相應操做的對象中的方法列表中找調用的方法,若是找到,轉向相應實現執行 3.若是沒找到,去父類指針所指向的對象中執行1,2. 4.以此類推,若是一直到根類還沒找到,轉向攔截調用,走消息轉發機制 5.若是沒有重寫攔截調用的方法,程序報錯github
class
反射Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
複製代碼
Class class = [Student class];
NSString *className = NSStringFromClass(class);
複製代碼
SEL
的反射SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
複製代碼
NSStringFromSelector(@selector(setName:));
@property (nonatomic, copy) NSMutableArray *arr;
添加,刪除,修改數組內元素的時候,程序會由於找不到對應的方法而崩潰。緣由:是由於
copy
就是複製一個不可變NSArray
的對象,不能對NSArray
對象進行添加/修改數組
copy
修飾符若想令本身所寫的對象具備拷貝功能,則需實現
NSCopying
協議。若是自定義的對象分爲可變版本與不可變版本,那麼就要同時實現NSCopying
與NSMutableCopying
協議。 具體步驟: 1.需聲明該類聽從NSCopying
協議 2.實現NSCopying
協議的方法,具體區別戳這裏緩存
NSCopying
協議方法爲:- (id)copyWithZone:(NSZone *)zone {
MyObject *copy = [[[self class] allocWithZone: zone] init];
copy.username = self.username;
return copy;
}
複製代碼
assign
不能用於修飾對象首先咱們須要明確,對象的內存通常被分配到堆上,基本數據類型和oc數據類型的內存通常被分配在棧上 若是用
assign
修飾對象,當對象被釋放後,指針的地址仍是存在的,也就是說指針並無被置爲nil
,從而形成了野指針。由於對象是分配在堆上的,堆上的內存由程序員分配釋放。而由於指針沒有被置爲nil
,若是後續的內存分配中,恰好分配到了這塊內存,就會形成崩潰 而assign
修飾基本數據類型或oc數據類型,由於基本數據類型是分配在棧上的,由系統分配和釋放,因此不會形成野指針安全
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr + 1));
複製代碼
參考答案:2,隨機值 分析:
a
表明有5個元素的數組的首地址,a[5]
的元素分別是1,2,3,4,5。接下來,a + 1
表示數據首地址加1,那麼就是a[1]
,也就是對應於值爲2,可是,這裏是&a + 1
,由於a
表明的是整個數組,它的空間大小爲5 * sizeof(int)
,所以&a + 1
就是a + 5
。a
是個常量指針,指向當前數組的首地址,指針+1就是移動sizeof(int)
個字節 所以,ptr
是指向int *
類型的指針,而ptr
指向的就是a + 5
,那麼ptr + 1
也至關於a + 6
,因此最後的*(ptr + 1)
就是一個隨機值了。而*(ptr – 1)
就至關於a + 4
,對應的值就是5bash
view
已經初始化完畢,view
上面添加了n個button
(可能使用循環建立),除用view
的tag
以外,還能夠採用什麼辦法來找到本身想要的button
來修改Button
的值第一種:若是是點擊某個按鈕後,纔會刷新它的值,其它不用修改,那麼不用引用任何按鈕,直接在回調時,就已經將接收響應的按鈕給傳過來了,直接經過它修改便可 第二種:點擊某個按鈕後,全部與之同類型的按鈕都要修改值,那麼能夠經過在建立按鈕時將按鈕存入到數組中,在須要的時候遍歷查找併發
UIViewController
的viewDidUnload、viewDidLoad
和loadView
分別何時調用?UIView
的drawRect
和layoutSubviews
分別起什麼做用第一個問題: 在控制器被銷燬前會調用
viewDidUnload
(MRC
下才會調用) 在控制器沒有任何view
時,會調用loadView
在view
加載完成時,會調用viewDidLoad
第二個問題: 在調用setNeedsDisplay
後,會調用drawRect
方法,咱們經過在此方法中能夠獲取到context
(設置上下文),就能夠實現繪圖 在調用setNeedsLayout
後,會調用layoutSubviews
方法,咱們能夠經過在此方法去調整UI。固然能引發layoutSubviews
調用的方式有不少種的,好比添加子視圖、滾動scrollview
、修改視圖的frame
等異步
自動釋放池是
NSAutorelease
類的一個實例,當向一個對象發送autorelease
消息時,該對象會自動入池,待池銷燬時,將會向池中全部對象發送一條release
消息,釋放對象[pool release]、[pool drain]
表示的是池自己不會銷燬,而是池子中的臨時對象都被髮送release
,從而將對象銷燬async
autoreleasepool
的
autoreleasepool
是由AutoreleasePoolPage
以雙向鏈表的方式實現的,主要經過下列三個函數完成:
- 由
objc_autoreleasePoolPush
做爲自動釋放池做用域的第一個函數- 使用
objc_autorelease
將對象加入自動釋放池- 由
objc_autoreleasePoolPop
做爲自動釋放池做用域的最後一個函數
autorelease
的對象什麼時候被釋放
RunLoop
在每一個事件循環結束後會去自動釋放池將全部自動釋放對象的引用計數減一,若引用計數變成了0,則會將對象真正銷燬掉,回收內存。 在沒有手動添加Autorelease Pool
的狀況下,autorelease
的對象是在每一個事件循環結束後,自動釋放池纔會對全部自動釋放的對象的引用計數減一,若引用計數變成了0,則釋放對象,回收內存。所以,若想要早一點釋放掉autorelease
對象,那麼咱們能夠在對象外加一個自動釋放池。好比,在循環處理數據時,臨時變量要快速釋放,就應該採用這種方式:
// 經過alloc建立的對象,直接加入@autoreleasepool沒有做用,需在建立對象後面顯式添加autorelease
// 經過類方法建立的對象不須要顯式添加autorelease,緣由是類方法建立的對象系統會自動添加autorelease
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *str = @"Abc";
str = [str lowercaseString];
str = [str stringByAppendingString:@"xyz"];
NSLog(@"%@", str);
} // 出了這裏,就會去遍歷該自動釋放池了
}
複製代碼
OC內存管理遵循
誰建立,誰釋放,誰引用,誰管理
的機制,當使用alloc、copy(mutableCopy)或者retian
一個對象時,你就有義務向它發送一條release或者autorelease
消息釋放該對象,其餘方法建立的對象,不須要由你來管理內存,當對象引用計數爲0時,系統將釋放該對象,這是OC的手動管理機制(MRC
) 向一個對象發送一條autorelease
消息,這個對象並不會當即銷燬,而是將這個對象放入了自動釋放池,待池子釋放時,它會向池中每個對象發送一條release
消息,以此來釋放對象 向一個對象發送release
消息,並不意味着這個對象被銷燬了,而是當這個對象的引用計數爲0時,系統纔會調用dealloc
方法釋放該對象和對象自己所擁有的實例
sizeof
關鍵字
sizeof
是在編譯階段處理,且不能被編譯爲機器碼。sizeof
的結果等於對象或類型所佔的內存字節數。sizeof
的返回值類型爲size_t
變量:int a; sizeof(a)
爲4; 指針:int *p; sizeof(p)
爲4; 數組:int b[10]; sizeof(b)
爲數組的大小4*10;int c[0]; sizeof(c)
等於0sizeof(void)
等於1sizeof(void *)
等於4
離屏渲染就是在當前屏幕緩衝區之外,新開闢一個緩衝區進行操做 離屏渲染觸發的場景有如下:
- 圓角(同時設置
layer.masksToBounds = YES、layer.cornerRadius
大於0)- 圖層蒙版
- 陰影,
layer.shadowXXX
,若是設置了layer.shadowPath
就不會產生離屏渲染- 遮罩,
layer.mask
- 光柵化,
layer.shouldRasterize = YES
離屏渲染消耗性能的緣由 須要建立新的緩衝區,離屏渲染的整個過程,須要屢次切換上下文環境,先是從當前屏幕(
On-Screen
)切換到離屏(Off-Screen
)等到離屏渲染結束之後,將離屏緩衝區的渲染結果顯示到屏幕上,又須要將上下文環境從離屏切換到當前屏幕
基本數據類型默認關鍵字是:
atomic, readwrite, assign
普通Objective-C
對象默認關鍵字是:atomic, readwrite, strong
類方法:
- 類方法是屬於類對象的
- 類方法只能經過類對象調用
- 類方法中的 self 是類對象
- 類方法能夠調用其餘的類方法
- 類方法中不能訪問成員變量
- 類方法中不能直接調用對象方法
實例方法:
- 實例方法是屬於實例對象的
- 實例方法只能經過實例對象調用
- 實例方法中的 self 是實例對象
- 實例方法中能夠訪問成員變量
- 實例方法中直接調用實例方法
- 實例方法中也能夠調用類方法(經過類名)
- 不能向編譯後獲得的類中增長實例變量
- 能向運行時建立的類中添加實例變量
- 由於編譯後的類已經註冊在
runtime
中,類結構體中的objc_ivar_list
實例變量的鏈表和instance_size
實例變量的內存大小已經肯定,同時runtime
會調用class_setIvarLayout
或class_setWeakIvarLayout
來處理strong weak
引用,因此不能向存在的類中添加實例變量 運行時建立的類是能夠添加實例變量,調用class_addIvar
函數。可是得在調用objc_allocateClassPair
以後,objc_registerClassPair
以前,緣由同上
runtime
如何經過selector
找到對應的IMP
地址(分別考慮實例方法和類方法)Selector、Method 和 IMP
的有什麼區別與聯繫對於實例方法,每一個實例的
isa
指針指向着對應類對象,而每個類對象中都有一個對象方法列表。對於類方法,每一個類對象的isa
指針都指向着對應的元類對象,而每個元類對象中都有一個類方法列表。方法列表中記錄着方法的名稱,方法實現,以及參數類型,其實selector
本質就是方法名稱,經過這個方法名稱就能夠在方法列表中找到對應的方法實現Selector、Method 和 IMP
的關係能夠這樣描述:在運行期分發消息,方法列表中的每個實體都是一個方法(Method
)它的名字叫作選擇器(SEL
)對應着一種方法實現(IMP
)
objc_msgSend、_objc_msgForward
都是作什麼的?OC 中的消息調用流程是怎樣的
objc_msgSend
是用來作消息發送的。在OC
中,對方法的調用都會被轉換成內部的消息發送執行_objc_msgForward
是IMP
類型(函數指針)用於消息轉發的:當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward
會嘗試作消息轉發- 在消息調用的過程當中,
objc_msgSend
的動做比較清晰:首先在Class
中的緩存查找IMP
(沒緩存則初始化緩存)若是沒找到,則向父類的Class
查找。若是一直查找到根類仍舊沒有實現,則用_objc_msgForward
函數指針代替IMP
。最後,執行這個IMP
。當調用一個NSObject
對象不存在的方法時,並不會立刻拋出異常,而是會通過多層轉發,層層調用對象的-resolveInstanceMethod:、-forwardingTargetForSelector:、-methodSignatureForSelector:、-forwardInvocation:
等方法。其中最後-forwardInvocation:
是會有一個NSInvocation
對象,這個NSInvocation
對象保存了這個方法調用的全部信息,包括Selector名,參數和返回值類型
,能夠從這個NSInvocation
對象裏拿到調用的全部參數值![]()
class
方法和objc_getClass
方法有什麼區別
object_getClass(obj)
返回的是obj
中的isa
指針,即指向類對象的指針;而[obj class]
則分兩種狀況:一是當obj
爲實例對象時,[obj class]
中class
是實例方法,返回的是obj
對象中的isa
指針;二是當obj
爲類對象(包括元類和根類以及根元類)時,調用的是類方法,返回的結果爲其自己
nil
對象發送消息將會發生什麼在
OC
中向nil
發送消息是徹底有效的,只是在運行時不會有任何做用;向一個nil
對象發送消息,首先在尋找對象的isa
指針時就是0地址
返回了,因此不會出現任何錯誤,也不會崩潰
_objc_msgForward
函數是作什麼的?直接調用它將會發生什麼
_objc_msgForward
是一個函數指針(和IMP
的類型同樣)用於消息轉發;當向一個對象發送一條消息,但它並無實現的時候,_objc_msgForward
會嘗試作消息轉發objc_msgSend
在消息傳遞
中的做用。在消息傳遞
過程當中,objc_msgSend
的動做比較清晰:首先在Class
中的緩存查找IMP
(沒有緩存則初始化緩存
)若是沒找到,則向父類的Class
查找。若是一直查找到根類
仍舊沒有實現,則用_objc_msgForward
函數指針代替IMP
,最後執行這個IMP
一旦調用了_objc_msgForward
,將跳過查找IMP
的過程,直接觸發消息轉發
,若是調用了_objc_msgForward
,即便這個對象確實已經實現了這個方法,你也會告訴objc_msgSend
,我沒有在這個對象裏找到這個方法的實現,若是用很差會直接致使程序Crash
unrecognized selector
的異常消息轉發
進行解決,流程見下圖
OC
在向一個對象發送消息時,runtime
庫會根據對象的isa
指針找到該對象實際所屬的類,而後在該類中的方法列表以及其父類方法列表中尋找方法運行,若是在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX
可是在這以前,OC的運行時會給出三次拯救程序崩潰的機會OC
運行時會調用+resolveInstanceMethod:
或者+resolveClassMethod:
,讓你有機會提供一個函數實現。若是你添加了函數,那運行時系統就會從新啓動一次消息發送的過程,不然,運行時就會移到下一步,消息轉發(Message Forwarding
)// 重寫 resolveInstanceMethod: 添加對象方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// 若是是執行 run 函數,就動態解析,指定新的 IMP
if (sel == NSSelectorFromString(@"run:")) {
// class: 給哪一個類添加方法
// SEL: 添加哪一個方法
// IMP: 方法實現 => 函數 => 函數入口 => 函數名
// type: 方法類型:void用v來表示,id參數用@來表示,SEL用:來表示
class_addMethod(self, sel, (IMP)runMethod, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
//新的 run 函數
void runMethod(id self, SEL _cmd, NSNumber *meter) {
NSLog(@"跑了%@", meter);
}
複製代碼
-forwardingTargetForSelector:
,Runtime
這時就會調用這個方法,給你把這個消息轉發給其餘對象的機會。只要這個方法返回的不是nil
和self
,整個消息發送的過程就會被重啓,固然發送的對象會變成你返回的那個對象。不然,就會繼續Normal Fowarding
。 這裏叫Fast
,只是爲了區別下一步的轉發機制。由於這一步不會建立任何新的對象,但下一步轉發會建立一個NSInvocation
對象,因此相對更快點// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run:)) {
return [[Person alloc] init];
// 返回 Person 對象,讓 Person 對象接收這個消息
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
Runtime
最後一次給你挽救的機會。首先它會發送-methodSignatureForSelector:
消息得到函數的參數和返回值類型。若是-methodSignatureForSelector:
返回nil
,Runtime
則會發出-doesNotRecognizeSelector:
消息,程序這時也就掛掉了。若是返回了一個函數簽名,Runtime
就會建立一個NSInvocation
對象併發送-forwardInvocation:
消息給目標對象// 獲取函數的參數和返回值類型,返回簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 從 anInvocation 中獲取消息
SEL sel = anInvocation.selector;
if (sel == NSSelectorFromString(@"run:")) {
// 1. 指定當前類的一個方法做爲IMP
// anInvocation.selector = @selector(readBook:);
// [anInvocation invoke];
// 2. 指定其餘類來執行這個IMP
Person *p = [[Person alloc] init];
// 判斷 Person 對象方法是否能夠響應 sel
if([p respondsToSelector:sel]) {
// 若能夠響應,則將消息轉發給其餘對象處理
[anInvocation invokeWithTarget:p];
} else {
// 若仍然沒法響應,則報錯:找不到響應方法
[self doesNotRecognizeSelector:sel];
}
}else{
[super forwardInvocation:anInvocation];
}
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
[super doesNotRecognizeSelector:aSelector];
}
複製代碼
既然-forwardingTargetForSelector:
和-forwardInvocation:
均可以將消息轉發給其餘對象處理,那麼二者的區別在哪? 區別就在於-forwardingTargetForSelector:
只能將消息轉發給一個對象。而-forwardInvocation:
能夠把消息存儲,在你以爲合適的時機轉發出去,或者不處理這個消息。修改消息的target,selector,參數等。將消息轉發給多個對象
iOS layoutSubviews
何時會被調用
init
方法不會調用layoutSubviews
,可是是用initWithFrame
進行初始化時,當rect
的值不爲CGRectZero
時,會觸發addSubview
會觸發layoutSubviews
方法setFrame
只有當設置的frame
的參數的size
與原來的size
不一樣,纔會觸發其view
的layoutSubviews
方法- 滑動
UIScrollView
會調用scrollview
及scrollview
上的view
的layoutSubviews
方法- 旋轉設備只會調用
VC
的view
的layoutSubviews
方法- 直接調用
[self setNeedsLayout];
(這個在上面蘋果官方文檔裏有說明)-layoutSubviews
方法:這個方法默認沒有作任何事情,須要子類進行重寫-setNeedsLayout
方法:標記爲須要從新佈局,異步調用layoutIfNeeded
刷新佈局,不當即刷新,但layoutSubviews
必定會被調用-layoutIfNeeded
方法:若是有須要刷新的標記,當即調用layoutSubviews
進行佈局(若是沒有標記,不會調用layoutSubviews
) 若是要當即刷新,要先調用[view setNeedsLayout]
,把標記設爲須要佈局,而後立刻調用[view layoutIfNeeded]
,實現佈局 在視圖第一次顯示以前,標記老是須要刷新
的,能夠直接調用[view layoutIfNeeded]
@property (nonatomic, strong) NSString *str;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];
});
}
複製代碼
會crash
。由於在並行隊列DISPATCH_QUEUE_CONCURRENT
中異步dispatch_async
對str
屬性進行賦值,就會致使str
已經被release
了,還會執行release
。這就是向已釋放內存的對象發送消息而發生crash
詳細解析:對str
屬性strong
修飾進行賦值,至關與MRC
中的
- (void)setStr:(NSString *)str{
if (str == _str) return;
id pre = _str;
[str retain];//1.先保留新值
_str = str;//2.再進行賦值
[pre release];//3.釋放舊值
}
複製代碼
那麼假如併發隊列
裏調度的線程A
執行到步驟1
,還沒到步驟2
時,線程B
執行到步驟3
,那麼當線程A
再執行步驟3
時,舊值就會被過分釋放
,致使向已釋放內存的對象發送消息而崩潰
一、使用串行隊列 將
set
方法改爲在串行隊列中執行就行,這樣即便異步,但全部block
操做追加在隊列最後依次執行 二、使用atomic
atomic
關鍵字至關於在setter
方法加鎖,這樣每次執行setter
都是線程安全的,但這只是單獨針對setter
方法而言的狹義的線程安全 三、使用weak
關鍵字weak
的setter
沒有保留新值
的操做,因此不會引起重複釋放。固然這個時候要看具體狀況可否使用weak
,可能值並非所須要的值 四、使用互斥鎖,保證數據訪問的惟一性@synchronized (self) {self.str = [NSString stringWithFormat:@"changzifuchaung:%d",i];}
五、使用Tagged Pointer
Tagged Pointer
是蘋果在64位系統引入的內存技術。簡單來講就是對於NSString
(內存小於60位的字符串)或NSNumber
(小於2^31),64位的指針有8個字節,徹底能夠直接用這個空間來直接表示值,這樣的話其實會將NSString
和NSNumber
對象由一個指針
轉換成一個值類型
,而值類型的setter和getter
又是原子的,從而線程安全
crash
嗎@property (nonatomic, strong) NSString *str;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
// 相比上面,僅字符串變短了
self.str = [NSString stringWithFormat:@"%d",i];
NSLog(@"%d, %s, %p", i, object_getClassName(self.str), self.str);
});
}
複製代碼
不會crash
。並且發現str
這個字符串類型是NSTaggedPointerString
Tagged Pointer
是一個可以提高性能、節省內存的有趣的技術 Tagged Pointer
專門用來存儲小的對象,例如NSNumber
和NSDate
(後來能夠存儲小字符串) Tagged Pointer指針的值
再也不是地址
了,而是真正的值
。因此,實際上它再也不是一個對象
了,它只是一個披着對象皮的普通變量
而已 它的內存並不存儲在堆
中,也不須要malloc和free
,因此擁有極快的讀取和建立速度