KVO 內部實現原理
- KVO是基于runtime機制實現的。
- 當某個類的對象第一次被觀察時,系統就會在運行期動態地創建該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。
- 派生類在被重寫的 setter 方法實現真正的通知機制(Person?? NSKVONotifying_Person)
不用中間變量,用兩種方法交換 A 和 B 的值
A=A+B;
B=A-B;
A=A-B;
或者
A = A^B;
B = A^B;
A = A^B;
runtime 實現的機制是什么
runtime,運行時機制,它是一套C語言庫。
實際上我們編寫的所有OC代碼,最終都是轉成了runtime庫的東西,
比如:
類轉成了runtime庫里面的結構體等數據類型,
方法轉成了 runtime庫里面的C語言函數,平時調方法都是轉成了objc_msgSend 函數(所以說OC有個消息發送機制)
因此,可以說runtime是OC的底層實現,是OC的幕后執行者 有了runtime庫,能做什么事情呢?
runtime庫里面包含了跟類、成員 變量、方法相關的API,比如獲取類里面的所有成員變量,為類動態 添加成員變量,動態改變類的方法實現,為類動態添加新的方法等 因此,有了runtime,想怎么改就怎么改。
是否使用 Core Text 或者 Core Image
CoreText
- 隨意修改文本的樣式
- 圖文混排(純C語言)
- 國外:Niumb
Core Image(濾鏡處理)
- 能調節圖片的各種屬性(對比度, 色溫, 色差等)
NSNotification、KVO、delegate 的區別和用法是什么?
通知比較靈活:1個通知能被多個對象接收, 1個對象能接收多個通知
KVO性能不好(底層會動態產生新的類),只能監聽某個對象屬性的改 變, 不推薦使用(1個對象的屬性能被多個對象監聽, 1個 對象能監聽 多個對象的其他屬性)
代理比較規范,但是代碼多(默認是1對1)
delegate 代理的作用?
代理的目的是改變或傳遞控制鏈。
允許一個類在某些特定時刻通知到其他類,而不 需要獲取到那些類的指針。可以減少框架復雜度。
另外一點,代理可以理解為java 中的回調監聽機制的一種類似。
簡單說一下事件響應的流程?
- 一個 UIView 發出一個事件之后,首先上傳給其父視圖;
- 父視圖上傳給其所在的控制器;
- 如果其控制器對事件進行處理,事件傳遞將終止,否則繼續上傳父視圖;
- 直到遇到響應者才會停止,否則事件將一直上傳,直到 UIWindow。
用@property 聲明的 NSString(或 NSArray, NSDictionary)經常使用 copy 關鍵字,為什么? 如果改用 strong 關鍵字,可能造成什么問題?
因為父類指針可以指向子類對象,使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個 不可變的副本。
如果我們使用是 strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
【復制詳解】
淺復制(shallow copy):在淺復制操作時,對于被復制對象的每一層都是指 針復制。
深復制(one-level-deep copy):在深復制操作時,對于被復制對象,至少有 一層是深復制。
完全復制(real-deep copy):在完全復制操作時,對于被復制對象的每一層都 是對象復制。
非集合類對象的 copy 與 mutableCopy [不可變對象 copy] // 淺復制 [不可變對象 mutableCopy] //深復制
[可變對象 copy] //深復制 [可變對象 mutableCopy] //深復制
集合類對象的 copy 與 mutableCopy [不可變對象 copy] // 淺復制 [不可變對象 mutableCopy] //單層深復制
[可變對象 copy] //單層深復制
[可變對象 mutableCopy] //單層深復制
這里需要注意的是集合對象的內容復制僅限于對象本身,對象元素仍然是 指針復制
這個寫法會出什么問題: @property (copy) NSMutableArray *array;
因為 copy 策略拷貝出來的是一個不可變對象,然而卻把它當成可變對象使用,很容 易造成程序奔潰。
這里還有一個問題,該屬性使用了同步鎖,會在創建時生成一些額外的代碼用于幫 助編寫多線程程序,這會帶來性能問題,通過聲明 nonatomic 可以節省這些雖然
很小但是不必要額外開銷,在 iOS 開發中應該使用 nonatomic 替代 atomic
如何讓自定義類可以用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對 象分為可變版本與不可變版本,那么就要同時實現 NSCopyiog 與 NSMutableCopying 協議,不過一般沒什么必要,實現 NSCopying 協議就夠了
// 實現不可變版本拷貝
- (id)copyWithZone:(NSZone *)zone; // 實現可變版本拷貝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重寫帶 copy 關鍵字的 setter
- (void)setName:(NSString *)name {
_name = [name copy];
}
+(void)load; +(void)initialize;有什 么用處?
+(void)load; 當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息。
load 方法會在每一個類甚至分類被引入時僅調用一次,調用的順序:父類優先于子 類, 子類優先于分類
由于 load 方法會在類被 import 時調用一次,而這時往往是改變類的行為的最佳時 機,在這里可以使用例如 method swizlling 來修改原有的方法
+(void)initialize;
也是在第一次使用這個類的時候會調用這個方法,也就是說 initialize 也是懶加載。
總結:
在 Objective-C 中,runtime 會自動調用每個類的這兩個方法
+load 會在類初始加載時調用
+initialize 會在第一次調用類的類方法或實例方法之前被調用
IBOutlet 連出來的視圖屬性為什么可以被設置成 weak?
因為父控件的 subViews 數組已經對它有一個強引用
1.首先要明確什么時候可以使用weak修飾?
一種情況是 : 在 ARC 中,在有可能出現循環引用的時候
另一種情況是 : 自身已經對它進行一次強引用,沒有必要再強引用一次
2.然后看IBOutlet連出來的視圖的引用關系
UIViewController →強引用→ UIView →強引用→ IBOutlet連出視圖
3.綜上所訴
所以問題場景滿足第二種使用weak的條件, 既UIViewController不需要對IBOutlet連出來的視圖再用strong修飾, 用weak即可。
什么情況使用 weak 關鍵字
在 ARC 中,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性
自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong
weak修飾的屬性所指的對象遭到摧毀時,屬性值也會清空(nil)
請簡述 UITableView 的復用機制
每次創建 cell 的時候通過 dequeueReusableCellWithIdentifier:方法創建 cell,
它先到 緩存池中找指定標識的 cell,
如果沒有找到指定標識的 cell,就直接返回 nil,且會通過 initWithStyle:reuseIdentifier:創建一個 cell
當 cell 離開界面就會被放到緩存池中,以供下次復用
如何高性能的給 UIImageView 加個圓角?
不好的解決方案:
使用下面的方式會強制 Core Animation 提前渲染屏幕的離屏繪制, 而離 屏繪制就會給性能帶來負面影響,會有卡頓的現象出現
self.view.layer.cornerRadius = 5;
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)];
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];
描述下 SDWebImage 里面給 UIImageView 加 載圖片的邏輯
SDWebImage 中為 UIImageView 提供了一個分類 UIImageView+WebCache.h
, 這個分類中有一個最常用的接口 sd_setImageWithURL:placeholderImage:
,會在真實圖片出現前會先顯示占位圖片,當真實圖片被加載出來后在替換占位圖片 加載圖片的過程大致如下:
首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以 url 作為數據的索引先在內存中尋找是否有對應的緩存
如果緩存未找到就會利用通過 MD5 處理過的 key 來繼續在磁盤中查詢對應的數據,
如果找到了, 就會把磁盤中的數據加載到內存中,并將圖片顯示出來
如果在內存和磁盤緩存中都沒有找到,就會向遠程服務器發送請求,開始下載圖片下載后的圖片會加入緩存中,并寫入磁盤中 整個獲取圖片的過程都是在子線程中執行,獲取到圖片后回到主線程將圖片顯示出來。
你是怎么封裝一個 view 的
可以通過純代碼或者 xib 的方式來封裝子控件建立一個跟 view 相關的模型,然后將模型數據傳給 view,通過模型上的數據給 view 的子控件賦值。
/**
* 純代碼初始化控件時一定會走這個方法
*/
- (instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
/**
* 通過 xib 初始化控件時一定會走這個方法
*/
- (id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
- (void)setup {
// 初始化代碼
}
觸摸事件的傳遞
觸摸事件的傳遞是從父控件傳遞到子控件 如果父控件不能接收觸摸事件,那么子控件就不可能接收到觸摸事件 不能接受觸摸事件的四種情況 不接收用戶交互,即:userInteractionEnabled = NO 隱藏,即:hidden = YES
透明,即:alpha <= 0.01
未啟用,即:enabled = NO
提示:UIImageView 的 userInteractionEnabled 默認就是 NO,因此 UIImageView 以 及它的子控件默認是不能接收觸摸事件的
如何找到最合適處理事件的控件:
首先,判斷自己能否接收觸摸事件 可以通過重寫 hitTest:withEvent:方法驗證 其次,判斷觸摸點是否在自己身上
對應方法 pointInside:withEvent: 從后往前(先遍歷最后添加的子控件)遍歷子控件,重復前面的兩個步驟 如果沒有符合條件的子控件,那么就自己處理
事件響應者鏈
如果當前 view 是控制器的 view,那么就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父控件。
在視圖層次結構的最頂層視圖也不能處理接收到的事件或消息,則將事件或消息傳 遞給 UIWindow 對象進行處理。
如果 UIWindow 對象也不處理,則將事件或消息傳遞給 UIApplication 對象
如果 UIApplication 也不能處理該事件或消息,則將其丟棄
補充:如何判斷上一個響應者
如果當前這個 view 是控制器的 view,那么控制器就是上一個響應者 如果當前這個 view 不是控制器的 view,那么父控件就是上一個響應者
一個 objc 對象的 isa 的指針指向什么?有什 么作用?
每一個對象內部都有一個 isa 指針,這個指針是指向它的真實類型 根據這個指針就能知道將來調用哪個類的方法
下面的代碼輸出什么?
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); //Son
}
return self;
}
@end
// 答案:都輸出 Son
這個題目主要是考察關于 objc 中對 self 和 super 的理解:
self 是類的隱藏參數,指向當前調用方法的這個類的實例。而 super 本質是一個編譯器標示符,和 self 是指向的同一個消息接受者
當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中 再找;
而當使用 super 時,則從父類的方法列表中開始找。然后調用父類的這個方法 調用[self class] 時,會轉化成 objc_msgSend 函數 id objc_msgSend(id self, SEL op, ...)
調 用 [super class] 時 , 會 轉 化 成 objc_msgSendSuper 函 數 id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一個參數是 objc_super 這樣一個結構體,其定義如下 struct objc_super { __unsafe_unretained id receiver;
__unsafe_unretained Class super_class; };
第一個成員是 receiver, 類似于上面的 objc_msgSend 函數第一個參數 self 第二個成員是記錄當前類的父類是什么,告訴程序從父類中開始找方法,找到方法 后,最后內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調用, 此時已經和[self class]調用相同了,故上述輸出結果仍然返回 Son
objc Runtime 開源代碼對- (Class)class 方法的實現
- (Class)class {
return object_getClass(self);
}
以下代碼運行結果如何?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
// 答案:主線程死鎖
使用 block 時什么情況會發生引用循環,如 何解決?
系統的某些 block api 中,UIView 的 block 版本寫動畫時不需要考慮,但也有一 些 api 需要考慮
以下這些使用方式不會引起循環引用的問題
[UIView animateWithDuration:duration animations:^ {
[self.superview layoutIfNeeded];
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
self.someProperty = xyz;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * notification)
{
self.someProperty = xyz;
}];
但如果方法中的一些參數是成員變量,那么可以造成循環引用,如:GCD 、 NSNotificationCenter 調用就要小心一點,比如 GCD 內部如果引用了 self,而且 GCD 的參數是 成員變量,則要考慮到循環引用,舉例如下:
// 【GCD】分析:self-->_operationsQueue-->block-->self 形成閉環,就造成了循環引用
__weak typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
[weakSelf doSomething];
[weakSelf doSomethingElse];
});
// 【NSNotificationCenter】分析:self-->_observer-->block-->self 形成閉環,就造成了循環引用
__weak typeof(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
[weakSelf dismissModalViewControllerAnimated:YES];
}];