1. 單例寫法
單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。
- 一般情況下, 如果一個類是單例, 那么都會提供一個類方法用于快速創建單例對象;
- 而且這個類方法的名稱是有一定的規則: share + 類名稱 / default + 類名稱 / 類名稱開頭。
//GCD 方式創建
static id _instance;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}
2. 深拷貝淺拷貝 舉出使用實例
iOS提供了copy和mutablecopy方法,顧名思義,copy就是復制了一個imutable的對象,而mutablecopy就是復制了一個mutable的對象。
- 系統的非容器類對象(這里指的是NSString,NSNumber等一類的對象)
- 如果對一不可變對象復制,copy是淺拷貝,mutableCopy就是深拷貝。
- 如果是對可變對象復制,都是深拷貝,但是copy返回的對象是不可變的。
- 系統的容器類對象
- 對于容器類本身,上面討論的結論也是適用的,需要探討的是復制后容器內對象的變化
- 容器內的元素內容都是指針復制
3.@property 相關問題總匯:
實例變量+基本數據類型變量=成員變量
屬性 (property)有兩大概念:ivar(實例變量)+存取方法(getter + setter)
3.1 ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些
- 對應基本數據類型默認關鍵字是:
atomic,readwrite,assign - 對于普通的OC對象:
atomic,readwrite,strong
3.2 什么情況使用 weak 關鍵字,相比 assign 有什么不同
- 什么情況使用 weak 關鍵字?
- 在ARC中,在有可能出現循環引用的時候,往往要通過讓其中一端使用weak來解決,比如:delegate代理屬性
- 自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用weak, 自定義View的子控件屬性一般也使用weak;但是也可以使用strong
- 不同點:
- weak當對象銷毀的時候,指針會被自動設置為nil,而assign不會
- assigin 可以用非OC對象,而weak必須用于OC對象
3.3 NSString 屬性什么時候用copy,什么時候用strong(ARC環境)
我們定義一個類,并為其聲明兩個字符串屬性,如下所示:
@interface TestStringClass ()
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@end
首先,我們用一個不可變字符串來為這兩個屬性賦值,
- (void)test {
NSString *string = [NSString stringWithFormat:@"abc"];
self.strongString = string;
self.copyedString = string;
NSLog(@"origin string: %p, %p", string, &string);
NSLog(@"strong string: %p, %p", _strongString, &_strongString);
NSLog(@"copy string: %p, %p", _copyedString, &_copyedString);
}
其輸出結果是:
origin string: 0x7fe441592e20, 0x7fff57519a48
strong string: 0x7fe441592e20, 0x7fe44159e1f8
copy string: 0x7fe441592e20, 0x7fe44159e200
我們可以看到,這種情況下,不管是strong還是copy屬性的對象,其指向的地址都是同一個,即為string指向的地址。如果我們換作MRC環境,打印string的引用計數的話,會看到其引用計數值是3,即strong操作和copy操作都使原字符串對象的引用計數值加了1。
接下來,我們把string由不可變改為可變對象,看看會是什么結果。即將下面這一句
NSString *string = [NSString stringWithFormat:@"abc"];
改成:
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
其輸出結果是:
origin string: 0x7ff5f2e33c90, 0x7fff59937a48
strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8
copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0
可以發現,此時copy屬性字符串已不再指向string字符串對象,而是深拷貝了string字符串,并讓_copyedString對象指向這個字符串。在MRC環境下,打印兩者的引用計數,可以看到string對象的引用計數是2,而_copyedString對象的引用計數是1。
此時,我們如果去修改string字符串的話,可以看到:因為_strongString與string是指向同一對象,所以_strongString的值也會跟隨著改變(需要注意的是,此時_strongString的類型實際上是NSMutableString,而不是NSString);而_copyedString是指向另一個對象的,所以并不會改變。
結論 由于NSMutableString是NSString的子類,所以一個NSString指針可以指向NSMutableString對象——讓我們的strongString指針指向一個可變字符串是可以的。
當源字符串是NSString時:由于字符串是不可變的,所以,不管是strong還是copy屬性的對象,都是指向源對象,copy操作只是做了次淺拷貝。
當源字符串是NSMutableString時:strong屬性只是增加了源字符串的引用計數,而copy屬性則是對源字符串做了次深拷貝,產生一個新的對象,且copy屬性對象指向這個新的對象。另外需要注意的是,這個copy屬性對象的類型始終是NSString,而不是NSMutableString,因此其是不可變的。
這里還有一個性能問題,即在源字符串是NSMutableString,strong是單純的增加對象的引用計數,而copy操作是執行了一次深拷貝,所以性能上會有所差異。而如果源字符串是NSString時,則沒有這個問題。
所以,在聲明NSString屬性時,到底是選擇strong還是copy,可以根據實際情況來定。不過,一般我們將對象聲明為NSString時,都不希望它改變,所以大多數情況下,建議使用copy,以免因可變字符串的修改導致的一些非預期問題。
3.4 用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什么?如果改用strong關鍵字,可能造成什么問題?
因為父類指針可以指向子類對象NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作。使用copy的目的是為了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可變對象,我本身持有的就是一個不可變的副本。
如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性。
copy此特質所表達的所屬關系與strong類似。然而設置方法并不保留新值,而是將其“拷貝” (copy)。
當屬性類型為NSString時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個NSMutableString類的實例,這個類是NSString的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那么設置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” 的字符串,確保對象中的字符串值不會無意間變動。只要實現屬性所用的對象是“可變的” ,就應該在設置新屬性值時拷貝一份。
3.5 這個寫法會出什么問題: @property (copy) NSMutableArray *array;
兩個問題:
1、添加,刪除,修改數組內的元素的時候,程序會因為找不到對應的方法而崩潰.因為copy就是復制一個不可變NSArray的對象;
2、使用了atomic屬性會嚴重影響性能。
4. #include #import @class
#include
:C/C++
導入頭文件的關鍵字
#import
:Objective-c
導入頭文件的關鍵字,,使用#import
頭文件會自動只導入一次,不會重復導入,相當于#include
和#pragma once
;#import<>
用來包含系統的頭文件,#import””
用來包含用戶頭文件。
@class
:告訴編譯器某個類的聲明,當執行時,才去查看類的實現文件,可以解決頭文件的相互包含;
5.循環引用相關問題匯總:
循環引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前對象是一個ViewController,則在dismiss或者pop之后其dealloc無法被調用,在頻繁的push或者present之后內存暴增,然后APP就掛了
- block的使用有時會導致循環引用
- 定時器的使用有時會導致循環引用
5.1 使用block時什么情況會發生引用循環,如何解決
一個對象中強引用了block,在block中又使用了該對象,就會發射循環引用。 解決方法是將該對象使用 _weak
或者_block
修飾符修飾之后再在block中使用。
id weak weakSelf = self; 或者 __weak typeof(self) weakSelf = self;該方法可以設置宏
id __block weakSelf = self;
5.2 使用系統的某些block api(如UIView的block版本寫動畫時),是否也考慮引用循環問題?
系統的某些block api中,UIView的block版本寫動畫時不需要考慮,但也有一些api 需要考慮:
所謂“引用循環”是指雙向的強引用,所以那些“單向的強引用”(block 強引用 self )沒有問題,比如這些:
1.
[UIView animateWithDuration:duration animations:^{
[self.superview layoutIfNeeded];
}];
2.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.someProperty = xyz;
}];
3.
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz;
}];
這些情況不需要考慮“引用循環”。
但如果你使用一些參數中可能含有實例變量的系統 api 如 GCD 、NSNotificationCenter就要小心一點:
比如GCD 內部如果引用了 self,而且 GCD 的其他參數是實例變量,則要考慮到循環引用:
1.
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
類似的:
2.
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 顯然這也是一個循環引用。
這篇文章介紹了weakself和strongself的用法:到底什么時候才需要在ObjC的Block中使用weakSelf/strongSelf
5.3 NSTimer導致循環引用的例子
一方面,NSTimer經常會被作為某個類的成員變量,而NSTimer初始化時要指定self為target,容易造成循環引用。
另一方面,若timer一直處于validate的狀態,則其引用計數將始終大于0。比如當定時器銷毀的時機不對,在dealloc里面銷毀的時候,內存就不會釋放,就會造成循環引用
.h文件
#import <Foundation/Foundation.h>
@interface Friend : NSObject
{
NSTimer *_timer;
}
- (void)cleanTimer;
@end
.m文件
@implementation Friend
- (id)init
{
if (self = [super init]) {
_timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(handleTimer:)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)handleTimer:(id)sender
{
NSLog(@"%@ say: Hi!", [self class]);
}
- (void)cleanTimer
{
[_timer invalidate];
_timer = nil;
}
- (void)dealloc
{
[self cleanTimer];
NSLog(@"[Friend class] is dealloced");
}
在main.m中聲明并且調用,通過函數讓Friend類延時5秒后引用計數減一
#import "Friend.h"
//循環引用
//是一個很麻煩的一件事,完全靠經驗
int main(int argc, const char * argv[]) {
Friend *friend = [[Friend alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 95*NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
[friend release];
});
return 0;
}
我們所期待的結果是,初始化5秒后,friend對象被release,friend的dealloc方法被調用,在dealloc里面timer失效,對象被析構。但結果卻是從未停下..
這是為什么呢?主要是因為從timer的角度,timer認為調用方(Friend對象)被析構時會進入dealloc,在dealloc可以順便將timer的計時停掉并且釋放內存;但是從Friend的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。循環引用,互相等待,無窮盡。問題的癥結在于-(void)cleanTimer函數的調用時機不對,顯然不能想當然地放在調用者的dealloc中。
一個比較好的解決方法是開放這個函數,讓Friend的調用者顯式地調用來清理現場。如下:
int main(int argc, const char * argv[]) {
Friend *friend = [[Friend alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 95*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[friend cleanTimer];
[friend release];
});
return 0;
}
6.UIView和CALayer的區別與聯系
- 每個 UIView 內部都有一個 CALayer 在背后提供內容的繪制和顯示,UIView的尺寸樣式都由內部的 Layer 所提供。兩者都有樹狀層級結構,layer 內部有 SubLayers,View內部有SubViews。但是Layer比View多了個AnchorPoint。
- 在View顯示的時候,UIView做為Layer的CALayerDelegate,View的顯示內容由內部的CALayer的display。
- CALayer修改屬性是支持隱式動畫的,在給UIView的Layer做動畫的時候,View作為Layer的代理,Layer向View請求相應的action(動畫行為)
- layer 內部維護著三份 layer tree,分別是 :
presentLayer Tree(動畫樹)
modeLayer Tree(模型樹)
Render Tree (渲染樹)。
在做iOS動畫的時候,我們修改動畫的屬性,在動畫的其實是Layer的presentLayer屬性值,而最終展示在界面上的其實是提供View的modelLayer - 兩者最明顯的區別是View可以接受并處理事件,而Layer不可以
7.詳細描述一下響應者鏈的含義(最好能圖釋)
iOS 系統檢測到手指觸摸 (Touch) 操作時會將其打包成一個 UIEvent 對象,并放入當前活動Application的事件隊列,單例UIApplication會從事件隊列中取出觸摸事件并傳遞給單例UIWindow來處理,UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖,這個過程稱之為hit-test view。
hitTest:withEvent:方法的處理流程如下:
- 首先調用當前視圖的pointInside:withEvent: 方法判斷觸摸點是否在當前視圖內;
- 若返回NO, 則hitTest:withEvent: 返回 nil,若返回YES, 則向當前視圖的所有子視圖 (subviews) 發送 hitTest:withEvent: 消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;
- 若第一次有子視圖返回非空對象,則 hitTest:withEvent: 方法返回此對象,處理結束;
- 如所有子視圖都返回空,則 hitTest:withEvent: 方法返回自身 (self)。
如果最終 hit-test 沒有找到第一響應者,或者第一響應者沒有處理該事件,則該事件會沿著響應者鏈向上回溯,如果 UIWindow 實例和 UIApplication 實例都不能處理該事件,則該事件會被丟棄(這個過程即上面提到的響應值鏈);
8.如何高效的剪切圓角圖片
CALayer 的 border、圓角、陰影、遮罩(mask),CASharpLayer 的矢量圖形顯示,通常會觸發離屏渲染(offscreen rendering),而離屏渲染通常發生在 GPU 中。當一個列表視圖中出現大量圓角的 CALayer,并且快速滑動時,可以觀察到 GPU 資源已經占滿,而 CPU 資源消耗很少。這時界面仍然能正?;瑒?,但平均幀數會降到很低。為了避免這種情況,可以嘗試開啟 CALayer.shouldRasterize 屬性,但這會把原本離屏渲染的操作轉嫁到 CPU 上去。對于只需要圓角的某些場合,也可以用一張已經繪制好的圓角圖片覆蓋到原本視圖上面來模擬相同的視覺效果。最徹底的解決辦法,就是把需要顯示的圖形在后臺線程繪制為圖片,避免使用圓角、陰影、遮罩等屬性。
9.const/static分別用法,修飾類時又如何 #define
- static
- 函數體內 static 變量的作用范圍為該函數體,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
- 在模塊內的 static "全局變量"可以被模塊內所有函數訪問,但不能被模塊外其它函數訪問;
- 在模塊內的 static "函數"只可被這一模塊內的其它函數調用,這個函數的使用范圍被限制在聲明它的模塊內;
- 在類中的 static "成員變量"屬于整個類所擁有,對類的所有對象只有一份拷貝;
- 在類中的 static "成員函數"屬于整個類所擁有,這個函數不接收 this 指針,因而只能訪問類的static 成員變量。
宏:
#define HSCoder @"漢斯哈哈哈"
變量:
NSString *HSCoder = @"漢斯哈哈哈";
常量:
四種寫法:
static const NSString *HSCoder = @"漢斯哈哈哈";
const NSString *HSCoder = @"漢斯哈哈哈"; //"*HSCoder"不能被修改, "HSCoder"能被修改
NSString const *HSCoder = @"漢斯哈哈哈"; //"*HSCoder"不能被修改, "HSCoder"能被修改(與上個沒啥區別)
NSString * const HSCoder = @"漢斯哈哈哈"; //"HSCoder"不能被修改,"*HSCoder"能被修改
結論:const右邊的總不能被修改
所以一般我們定義一個常量又不想被修改應該選擇最后一種方案:
NSString * const HSCoder = @"漢斯哈哈哈";
.h文件中這樣寫:
UIKIT_EXTERN NSString *const HSCoder
10.如何使用Objective-C實現多重繼承?并給出代碼示例
- 通過組合實現“多繼承”
- 通過協議實現“多繼承”
- 通過category實現“單繼承”(大部分網上文章將此方法誤解成“多繼承”)
11.關于app性能優化你是怎么理解的?請詳細描述一下
- 入門級(這是些你一定會經常用在你app開發中的建議,8個)
- 用ARC管理內存
- 不要block主線程
- 打開gzip壓縮
- 在正確的地方使用reuseIdentifier
自iOS6起,除了UICollectionView的cells和補充views,也應該在header和footer views中使用reuseIdentifiers。 - 盡可能使Views不透明, 避免圖層混合
確??丶膐paque屬性設置為true,確保backgroundColor和父視圖顏色一致且不透明
如無特殊需要,不要設置低于1的alpha值
確保UIImage沒有alpha通道 - 避免龐大的XIB
如果你不得不XIB的話,使他們盡量簡單。嘗試為每個Controller配置一個單獨的XIB,盡可能把一個View Controller的view層次結構分散到單獨的XIB中去。
需要注意的是,當你加載一個XIB的時候所有內容都被放在了內存里,包括任何圖片。如果有一個不會即刻用到的view,你這就是在浪費寶貴的內存資源了。Storyboards就是另一碼事兒了,storyboard僅在需要時實例化一個view controller. - 在Image Views中調整圖片大小,避免臨時轉換
確保圖片大小和frame一致,不要在滑動時縮放圖片
確保圖片顏色格式被GPU支持,避免勞煩CPU轉換 - 選擇正確的Collection
Arrays: 有序的一組值。使用index來lookup很快,使用value lookup很慢, 插入/刪除很慢。
Dictionaries: 存儲鍵值對。 用鍵來查找比較快。
Sets: 無序的一組值。用值來查找很快,插入/刪除很快。
- 中級(這些是你可能在一些相對復雜情況下可能用到的)
權衡渲染方法
優化你的Table View
重用和延遲加載Views
這里我們用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次創建所有的subview,而是當需要時才創建,當它們完成了使命,把他們放進一個可重用的隊列中。Cache, Cache, 還是Cache!
NSCache和NSDictionary類似,不同的是系統回收內存的時候它會自動刪掉它的內容。-
處理內存警告
如果你的app收到了內存警告,它就需要盡可能釋放更多的內存。最佳方式是移除對緩存,圖片object和其他一些可以重創建的objects的strong references.
UIKit提供了幾種收集低內存警告的方法:- 在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
- 在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
- 注冊并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
避免反復處理數據,選擇正確的數據格式
如需要數據來展示一個table view,最好直接從服務器取array結構的數據以避免額外的中間數據結構改變。
如需要從特定key中取數據,那么就使用鍵值對的dictionary。正確地設定Background Images
如果你使用全畫幅的背景圖,你就必須使用UIImageView因為UIColor的colorWithPatternImage是用來創建小的重復的圖片作為背景的。這種情形下使用UIImageView可以節約不少的內存
如果你用小圖平鋪來創建背景,你就需要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費很多內存
重用大開銷的對象
一些objects的初始化很慢,比如NSDateFormatter和NSCalendar,想要避免使用這個對象的瓶頸你就需要重用他們,可以通過1.添加屬性到你的class里 或者2.創建靜態變量來實現。
如果你要選擇第二種方法,對象會在你的app運行時一直存在于內存中,和單例(singleton)很相似。
下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調用時它會創建一個新的實例,以后的調用則將返回已經創建的實例:
// in your .h or inside a class extension
@property (nonatomic, strong) NSDateFormatter *formatter;
// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
if (! _formatter) {
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
}
return _formatter;
}
-
進階級(這些建議只應該在你確信他們可以解決問題和得心應手的情況下采用)
- 加速啟動時間
- 使用Autorelease Pool
- 選擇是否緩存圖片
- 盡量避免日期格式轉換
-
UIKit方面
- 慎用離屏渲染
絕大多數時候離屏渲染會影響性能
重寫drawRect方法,設置圓角、陰影、模糊效果,光柵化都會導致離屏渲染
設置陰影效果是加上陰影路徑
滑動時若需要圓角效果,開啟光柵化
- 慎用離屏渲染
drawrect
繪制圖形性能的優化最好辦法就是不去繪制。
利用專有圖層代替繪圖需求。
不得不用到繪圖盡量縮小視圖面積,并且盡量降低重繪頻率。
異步繪制,推測內容,提前在其他線程繪制圖片,在主線程中直接設置圖片。
12. tableview優化
為了保證table view平滑滾動,確保你采取了以下的措施:
- 正確使用reuseIdentifier來重用cells
- 盡量使所有的view opaque,包括cell自身
- 避免漸變,圖片縮放
- 緩存行高
- 如果cell內現實的內容來自web,使用異步加載,緩存請求結果
- 使用shadowPath來畫陰影
- 減少subviews的數量
- 盡量不適用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結果
- 使用正確的數據結構來存儲數據
- 使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight來設定固定的高,不要請求delegate
13. 對象沒銷毀的原因有哪些
- 控制器中NSTimer沒有被銷毀
當控制器中存在NSTimer時,就需要注意,因為當
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateTime:)
userInfo:nil
repeats:YES];
時,這個target:self
就增加了VC的RetainCount,如果你不將這個timer invalidate,就別想調用dealloc。需要在viewWillDisappear之前需要把控制器用到的NSTimer銷毀。
[timer invalidate]; // 銷毀timer
timer = nil; // 置nil
- 控制器中的代理不是weak屬性
例如@property (nonatomic, weak) id<HCAppViewDelegate> delegate;
代理要使用弱引用,因為自定義控件是加載在視圖控制器中的,視圖控制器view對自定義控件是強引用,如果代理屬性設置為strong,則意味著delegate對視圖控制器也進行了強引用,會造成循環引用。導致控制器無法被釋放,最終導致內存泄漏。 - 控制器中block的循環引用
14.多線程操作
-
14.1 多線程處理方式及優缺
- pThread c語言框架
一套通用的多線程API,適用于Linux\Windows\Unix,跨平臺,可移植,使用C語言,生命周期需要程序員管理,IOS開發中使用很少。 - NSThread apple 封裝過,面向對象,可直接操控線程對象
優點:NSThread 比其他兩個輕量級。
缺點:需要自己管理線程的生命周期,線程同步。線程同步對數據的加鎖會有一定的系統開銷。
1.先創建線程類,再啟動
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(run:)
object:nil];
[thread start];
2.創建并自動啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
3.使用 NSObject 的方法創建并自動啟動
[self performSelectorInBackground:@selector(run:) withObject:nil];
- GCD為多核并行運算提出的解決方案,自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
- 任務:即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式: 同步執行 和 異步執行,他們之間主要區別在于會不會阻塞當前線程,直到 Block 中的任務執行完畢!
- 同步(sync) 操作,它會阻塞當前線程并等待 Block 中的任務執行完畢,然后當前線程才會繼續往下運行。
- 異步(async)操作,當前線程會直接往下執行,它不會阻塞當前線程。
- 隊列:用于存放任務。一共有兩種隊列, 串行隊列 和 并行隊列。
- 串行隊列:GCD 會 FIFO(先進先出) 地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。
- 并行隊列:放到并行隊列的任務,GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務都是一起執行的。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。
- 任務:即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式: 同步執行 和 異步執行,他們之間主要區別在于會不會阻塞當前線程,直到 Block 中的任務執行完畢!
創建隊列
1.主隊列:這是一個特殊的 `串行隊列`
dispatch_queue_t queue = dispatch_get_main_queue();
2.全局并行隊列:只要是并行任務一般都加入到這個隊列。這是系統提供的一個并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.自己創建的隊列:
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
創建任務
1.同步任務: 會阻塞當前線程 (SYNC)
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
2.異步任務:不會阻塞當前線程 (ASYNC)
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
隊列組
//1.創建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊列組的方法執行任務, 只有異步方法
//3.1.執行3次循環
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主隊列執行8次循環
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.執行5次循環
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
- NSOperation GCD的封裝 它的實例封裝了需要的操作以及操作所需要的數據
優點:不需要關心線程管理, 數據同步的事情,可以把精力放在自己需要執行的操作上。NSOperation是個抽象類,可以使用它定義好的兩個子類:NSInvocationOperation
和NSBlockOperation
,或者用它的子類(比較高級,暫不提),創建NSOperation子類的對象。
NSOperation
和NSOperationQueue
分別對應 GCD 的任務
和隊列
添加任務
1.NSInvocationOperation : 需要傳入一個方法名
//1.創建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執行
[operation start];
2.NSBlockOperation
//1.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務
[operation start];
NSBlockOperation還有一個方法:addExecutionBlock: 通過這個方法可以給Operation添加多個執行Block。
這樣Operation中的任務會并發執行,它會在主線程和其它的多個線程執行這些任務
創建隊列:
只要添加到隊列,會自動調用任務的 start() 方法
1.主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
2.其他隊列
//1.創建一個其他隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//4.隊列添加任務
[queue addOperation:operation];
將NSOperationQueue與GCD的隊列相比較就會發現,這里沒有串行隊列,那如果我想要10個任務在其他線程串行的執行怎么辦?
這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue有一個參數maxConcurrentOperationCount最大并發數,用來設置最多可以讓多少個任務同時執行。當你把它設置為1的時候,他不就是串行了嘛!
NSOperation 有一個非常實用的功能,那就是添加依賴。比如有 3 個任務:A: 從服務器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務器。這時就可以用到依賴了
-
14.2什么時候使用GCD,什么時候使用NSOperation?如何按照指定的執行順序完成任務?例如,c任務需要在a、b任務完成之后執行,請分別使用GCD和NSOperation寫書代碼示例
-
14.3 有個圖片下載未完成劃過后重新出發沒下載圖片會怎樣
-
14.4 GCD的隊列(dispatch_queue_t)分哪兩種類型?
串行隊列和并行隊列
-
14.5 如何用GCD同步若干個異步調用?(如根據若干個url異步加載多張圖片,然后在都下載完成后合成一張整圖)
總體上說: 使用 dispatch group,然后 wait forever 等待完成, 或者采取 group notify 來通知回調。
細節:
1. 創建異步隊列
2. 創建dispatch_group dispatch_group_t = dispatch_group_create()
3. 通過組來執行異步下載任務 dispatch_group_async(queueGroup, aQueue, ^{
NSLog(@"下載圖片."); });
4.等到所有任務完成 dispatch_group_wait(queueGroup, DISPATCH_TIME_FOREVER);
5.合成圖片
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
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(), ^{
// 合并圖片
});
######15. ARC內存管理的理解
《Effective Objective-C 2.0》
理解引用計數
- 引用計數工作原理
- 屬性存取方法中的內存管理
- 自動釋放池
- 保留環
以arc簡化引用計數
- 使用ARC時必須遵循的方法命名規則
- 變量的內存管理語義
- arc如何清理實例變量
- 覆寫內存管理方法
- 用“僵尸對象”調試內存管理問題
- 不要使用retain count
在dealloc方法中只釋放引用并解除監聽
編寫"異常安全代碼"時注意內存管理問題
以弱引用避免保留環
以"自動釋放池塊"降低內存峰值
《Objective-C高級編程》
>Apple 在Objective-C中采用ARC機制,讓編譯器來進行內存管理。在新一代Apple LLVM編譯器中設置ARC為有效狀態,就無需再次鍵入retain或release代碼,這在降低程序崩潰,內存泄漏等風險的同時,很大程度上減少了開發程序的工作量。編譯器完全清楚目標對象,并能立刻釋放那些不再被使用的對象。如此依賴,應用程序將具有可預測性,且能流程運行,速度也將大幅提升。
- 自己生成的對象,自己持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
- ######15.1.內存泄漏有哪些情況
- 循環引用
- Delegate
我們在使用代理設計模式的時候,一定要注意將 delegate 變量聲明為 weak 類型,像這樣 @property (nonatomic, weak) id delegate;
- Block
__weak typeof(self)weakSelf = self;
- NSTimer
在一個ViewController里創建了一個定時器,并且repeats:值為YES,一定記得在 pop/dismiss當前ViewController將timer設為invalidate,否則會造成循環引用,這里要特別需要注意的一點是:我們不要在ViewController的dealloc方法里調用[timer invalidate]; 因為從來不會調到這里,我們應該在viewWillDisappear里邊調用
- performSelector延時調用導致的內存泄露
假設你延時10s觸發某個方法,但是在3s的時候,用戶點擊了back,這時對象不能馬上被回收,而是要等到10s后才有可能被回收。所以在項目中如果要延時觸發方法,我不會選擇該方法,而是使用GCD
__weak typeof(self) weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds *NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
[weakSelf dodd];
});
- 代理未清空引起野指針
iOS的一些API,發現delegate都是assign的,這樣就會引起野指針的問題,可能會引起一些莫名其妙的crash。那么這是怎么引起的,當一個對象被回收時,對應的delegate實體也就被回收,但是delegate的指針確沒有被nil,從而就變成了游蕩的野指針了。所以在delloc方法中要將對應的assign代理設置為nil
一般自己寫的一些delegate,我們會用weak,而不是assign,weak的好處是當對應的對象被回收時,指針也會自動被設置為nil。
- ######15.2 什么時候用@autoreleasepool
- 寫基于命令行的的程序時,就是沒有UI框架,如AppKit等Cocoa框架時。
- 寫循環,循環里面包含了大量臨時創建的對象。(500000次循環,每次循環創建一個NSNumber實例和兩個NSString實例)
- 創建了新的線程。(非Cocoa程序創建線程時才需要)
- 長時間在后臺運行的任務。
- ######15.3. 頁面使用過多內存過多閃退
- ######15.4. 1000張圖片內存
- ######15.5.界面交互優化騰訊 banner100頁怎么辦
######16. 詳細描述一下KVO的實現機制,代理 通知和kvo區別 代理和block區別 block 訪問外部變量原理
######17. runtime的理解
- runtime作用
- 發送消息
消息機制原理:對象根據方法編號SEL去映射表查找對應的方法實現
- 交換方法
開發使用場景:系統自帶的方法功能不夠,給系統自帶的方法擴展一些功能,并且保持原有的功能。
方式一:繼承系統的類,重寫方法.
方式二:使用runtime,交換方法.
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址,相當于交換實現方式
method_exchangeImplementations(imageWithName, imageName);
- 動態添加方法
開發使用場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。
經典面試題:有沒有使用performSelector,其實主要想問你有沒有動態添加過方法。
簡單使用
@implementation Person
// void(*)()
// 默認方法都有兩個隱式參數,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當一個對象調用未實現的方法,會調用這個方法處理,并且會把對應的方法列表傳過來.
// 剛好可以用來判斷,未實現的方法是不是我們想要動態添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 動態添加eat方法
// 第一個參數:給哪個類添加方法
// 第二個參數:添加方法的方法編號
// 第三個參數:添加方法的函數實現(函數地址)
// 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
- 給分類添加屬性
原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,并不是直接把這個值的內存空間添加到類存空間。
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根據關聯的key,獲取關聯的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一個參數:給哪個對象添加關聯
// 第二個參數:關聯的key,通過這個key獲取
// 第三個參數:關聯的value
// 第四個參數:關聯的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 字典轉模型
- runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和實例方法)
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現.
- category中能不能使用聲明屬性?為什么?如果能,怎么實現?
給分類(Category)添加屬性利用Runtime實現getter/setter 方法
@interface ClassName (CategoryName)
@property (nonatomic, strong) NSString *str;
@end
//實現文件
import "ClassName + CategoryName.h"
import <objc/runtime.h>
static void *strKey = &strKey;
@implementation ClassName (CategoryName)
-(void)setStr:(NSString *)str
{
objc_setAssociatedObject(self, & strKey, str, OBJC_ASSOCIATION_COPY);
}
-(NSString *)str
{
return objc_getAssociatedObject(self, &strKey);
}
@end
- 什么時候會報unrecognized selector的異常?
簡單來說:當該對象上某個方法,而該對象上沒有實現這個方法的時候, 可以通過“消息轉發”進行解決。
簡單的流程如下:objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector)。
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會:
`Method resolution`
objc運行時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加了函數并返回 YES,那運行時系統就會重新啟動一次消息發送的過程,如果 resolve 方法返回 NO ,運行時就會移到下一步,消息轉發(Message Forwarding)。
`Fast forwarding`
如果目標對象實現了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會。 只要這個方法返回的不是nil和self,整個消息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。否則,就會繼續Normal Fowarding。 這里叫Fast,只是為了區別下一步的轉發機制。因為這一步不會創建任何新的對象,但下一步轉發會創建一個NSInvocation對象,所以相對更快點。
`Normal forwarding`
這一步是Runtime最后一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數簽名,Runtime就會創建一個NSInvocation對象并發送-forwardInvocation:消息給目標對象。
######18. runloop的理解
- RunLoop 的概念
如果我們需要一個機制,讓線程能隨時處理事件但并不退出,這種模型通常被稱作 Event Loop。Event Loop 在很多系統和框架里都有實現,比如 OSX/iOS 里的 RunLoop。實現這種模型的關鍵點在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立刻被喚醒。
- RunLoop 與線程的關系
蘋果不允許直接創建 RunLoop,它只提供了兩個自動獲取的函數:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
線程和 RunLoop 之間是一一對應的,其關系是保存在一個全局的 Dictionary 里。線程剛創建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創建是發生在第一次獲取時,RunLoop 的銷毀是發生在線程結束時。你只能在一個線程的內部獲取其 RunLoop(主線程除外)。
######19. contentoffset contentinset contentsize
######20. 說說知道哪些設計模式,說說簡單工廠和抽象工廠
######21.第三方分享的調用接口
######22.動畫的實現方式
######23. 錯誤提示sympol
######24. 使用定時器注意問題
######25. 頁面滑動丟幀的原因有多少種
######26.推送的原理具體實現
######27. 如果不用第三方庫實現圖片緩存設計一種方法
######28. 獲得App閃退log怎么做
######29. 完整發布流程
- 如何重寫帶 copy 關鍵字的 setter?
重寫copy的setter方法時候,一定要調用一下傳入的對象的copy方法,然后在賦值給該setter的方法對應的成員變量
- @synthesize和@dynamic分別有什么作用?
- @property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;
- @synthesize的語義是如果你沒有手動實現setter方法和getter方法,那么編譯器會自動為你加上這兩個方法。
- @dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現,不自動生成。(當然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var,然后你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程序運行到instance.var =someVar,由于缺setter方法會導致程序崩潰;或者當運行到 someVar = var時,由于缺getter方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
- BAD_ACCESS在什么情況下出現,如何調試?
1. 死循環了
2. 訪問一個僵尸對象
設置全局斷點快速定位問題代碼所在行
- 談談instancetype和id的異同
1、相同點
都可以作為方法的返回類型
2、不同點
①instancetype可以返回和方法所在類相同類型的對象,id只能返回未知類型的對象;②instancetype只能作為返回值,不能像id那樣作為參數
- @protocol 和 category 中如何使用 @property
1)在protocol中使用property只會生成setter和getter方法聲明,我們使用屬性的目的,是希望遵守我協議的對象能實現該屬性
2)category 使用 @property 也是只會生成setter和getter方法的聲明,如果我們真的需要給category增加屬性的實現,需要借助于運行時的兩個函數:
①objc_setAssociatedObject
②objc_getAssociatedObject