1. 單例寫法
單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例。
- 一般情況下, 如果一個類是單例, 那么都會提供一個類方法用于快速創(chuàng)建單例對象蝌麸;
- 而且這個類方法的名稱是有一定的規(guī)則: share + 類名稱 / default + 類名稱 / 類名稱開頭。
//GCD 方式創(chuàng)建
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的對象。
- 系統(tǒng)的非容器類對象(這里指的是NSString,NSNumber等一類的對象)
- 如果對一不可變對象復制讼育,copy是淺拷貝,mutableCopy就是深拷貝。
- 如果是對可變對象復制蹲缠,都是深拷貝娜谊,但是copy返回的對象是不可變的纱皆。
- 系統(tǒng)的容器類對象
- 對于容器類本身,上面討論的結(jié)論也是適用的,需要探討的是復制后容器內(nèi)對象的變化
- 容器內(nèi)的元素內(nèi)容都是指針復制
3.@property 相關(guān)問題總匯:
實例變量+基本數(shù)據(jù)類型變量=成員變量
屬性 (property)有兩大概念:ivar(實例變量)+存取方法(getter + setter)
3.1 ARC下钳踊,不顯式指定任何屬性關(guān)鍵字時助琐,默認的關(guān)鍵字都有哪些
- 對應(yīng)基本數(shù)據(jù)類型默認關(guān)鍵字是:
atomic,readwrite,assign - 對于普通的OC對象:
atomic,readwrite,strong
3.2 什么情況使用 weak 關(guān)鍵字兵钮,相比 assign 有什么不同
- 什么情況使用 weak 關(guān)鍵字呻拌?
- 在ARC中,在有可能出現(xiàn)循環(huán)引用的時候,往往要通過讓其中一端使用weak來解決,比如:delegate代理屬性
- 自身已經(jīng)對它進行一次強引用,沒有必要再強引用一次,此時也會使用weak, 自定義View的子控件屬性一般也使用weak;但是也可以使用strong
- 不同點:
- weak當對象銷毀的時候,指針會被自動設(shè)置為nil,而assign不會
- assigin 可以用非OC對象,而weak必須用于OC對象
3.3 NSString 屬性什么時候用copy靴拱,什么時候用strong(ARC環(huán)境)
我們定義一個類,并為其聲明兩個字符串屬性偎窘,如下所示:
@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);
}
其輸出結(jié)果是:
origin string: 0x7fe441592e20, 0x7fff57519a48
strong string: 0x7fe441592e20, 0x7fe44159e1f8
copy string: 0x7fe441592e20, 0x7fe44159e200
我們可以看到登刺,這種情況下南窗,不管是strong還是copy屬性的對象窒悔,其指向的地址都是同一個,即為string指向的地址。如果我們換作MRC環(huán)境祭玉,打印string的引用計數(shù)的話,會看到其引用計數(shù)值是3,即strong操作和copy操作都使原字符串對象的引用計數(shù)值加了1铺韧。
接下來塔逃,我們把string由不可變改為可變對象,看看會是什么結(jié)果。即將下面這一句
NSString *string = [NSString stringWithFormat:@"abc"];
改成:
NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
其輸出結(jié)果是:
origin string: 0x7ff5f2e33c90, 0x7fff59937a48
strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8
copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0
可以發(fā)現(xiàn),此時copy屬性字符串已不再指向string字符串對象胜卤,而是深拷貝了string字符串澈段,并讓_copyedString對象指向這個字符串芒率。在MRC環(huán)境下德玫,打印兩者的引用計數(shù),可以看到string對象的引用計數(shù)是2,而_copyedString對象的引用計數(shù)是1显熏。
此時,我們?nèi)绻バ薷膕tring字符串的話,可以看到:因為_strongString與string是指向同一對象歧寺,所以_strongString的值也會跟隨著改變(需要注意的是,此時_strongString的類型實際上是NSMutableString棘脐,而不是NSString)斜筐;而_copyedString是指向另一個對象的,所以并不會改變荆残。
結(jié)論 由于NSMutableString是NSString的子類奴艾,所以一個NSString指針可以指向NSMutableString對象——讓我們的strongString指針指向一個可變字符串是可以的。
當源字符串是NSString時:由于字符串是不可變的,所以遮婶,不管是strong還是copy屬性的對象,都是指向源對象,copy操作只是做了次淺拷貝。
當源字符串是NSMutableString時:strong屬性只是增加了源字符串的引用計數(shù)猜年,而copy屬性則是對源字符串做了次深拷貝,產(chǎn)生一個新的對象稳析,且copy屬性對象指向這個新的對象。另外需要注意的是平委,這個copy屬性對象的類型始終是NSString劳曹,而不是NSMutableString溉浙,因此其是不可變的吼渡。
這里還有一個性能問題,即在源字符串是NSMutableString命黔,strong是單純的增加對象的引用計數(shù)隅忿,而copy操作是執(zhí)行了一次深拷貝,所以性能上會有所差異。而如果源字符串是NSString時,則沒有這個問題。
所以坤候,在聲明NSString屬性時尼酿,到底是選擇strong還是copy,可以根據(jù)實際情況來定。不過拉岁,一般我們將對象聲明為NSString時,都不希望它改變惰爬,所以大多數(shù)情況下喊暖,建議使用copy,以免因可變字符串的修改導致的一些非預期問題撕瞧。
3.4 用@property聲明的NSString(或NSArray陵叽,NSDictionary)經(jīng)常使用copy關(guān)鍵字狞尔,為什么?如果改用strong關(guān)鍵字咨跌,可能造成什么問題沪么?
因為父類指針可以指向子類對象NSMutableString、NSMutableArray锌半、NSMutableDictionary,他們之間可能進行賦值操作寇漫。使用copy的目的是為了讓本對象的屬性不受外界影響,使用copy無論給我傳入是一個可變對象還是不可變對象刊殉,我本身持有的就是一個不可變的副本。
如果我們使用是strong,那么這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改了,那么會影響該屬性州胳。
copy此特質(zhì)所表達的所屬關(guān)系與strong類似记焊。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)栓撞。
當屬性類型為NSString時遍膜,經(jīng)常用此特質(zhì)來保護其封裝性,因為傳遞給設(shè)置方法的新值有可能指向一個NSMutableString類的實例瓤湘,這個類是NSString的子類瓢颅,表示一種可修改其值的字符串,此時若是不拷貝字符串弛说,那么設(shè)置完屬性之后挽懦,字符串的值就可能會在對象不知情的情況下遭人更改。所以木人,這時就要拷貝一份“不可變” 的字符串信柿,確保對象中的字符串值不會無意間變動。只要實現(xiàn)屬性所用的對象是“可變的” 醒第,就應(yīng)該在設(shè)置新屬性值時拷貝一份渔嚷。
3.5 這個寫法會出什么問題: @property (copy) NSMutableArray *array;
兩個問題:
1、添加,刪除,修改數(shù)組內(nèi)的元素的時候,程序會因為找不到對應(yīng)的方法而崩潰.因為copy就是復制一個不可變NSArray的對象稠曼;
2形病、使用了atomic屬性會嚴重影響性能。
4. #include #import @class
#include
:C/C++
導入頭文件的關(guān)鍵字
#import
:Objective-c
導入頭文件的關(guān)鍵字蒲列,,使用#import
頭文件會自動只導入一次窒朋,不會重復導入,相當于#include
和#pragma once
蝗岖;#import<>
用來包含系統(tǒng)的頭文件侥猩,#import””
用來包含用戶頭文件。
@class
:告訴編譯器某個類的聲明抵赢,當執(zhí)行時欺劳,才去查看類的實現(xiàn)文件唧取,可以解決頭文件的相互包含;
5.循環(huán)引用相關(guān)問題匯總:
循環(huán)引用可以簡單理解為A引用了B划提,而B又引用了A枫弟,雙方都同時保持對方的一個引用,導致任何時候引用計數(shù)都不為0鹏往,始終無法釋放淡诗。若當前對象是一個ViewController,則在dismiss或者pop之后其dealloc無法被調(diào)用伊履,在頻繁的push或者present之后內(nèi)存暴增韩容,然后APP就掛了
- block的使用有時會導致循環(huán)引用
- 定時器的使用有時會導致循環(huán)引用
5.1 使用block時什么情況會發(fā)生引用循環(huán),如何解決
一個對象中強引用了block唐瀑,在block中又使用了該對象群凶,就會發(fā)射循環(huán)引用。 解決方法是將該對象使用 _weak
或者_block
修飾符修飾之后再在block中使用哄辣。
id weak weakSelf = self; 或者 __weak typeof(self) weakSelf = self;該方法可以設(shè)置宏
id __block weakSelf = self;
5.2 使用系統(tǒng)的某些block api(如UIView的block版本寫動畫時)请梢,是否也考慮引用循環(huán)問題?
系統(tǒng)的某些block api中力穗,UIView的block版本寫動畫時不需要考慮毅弧,但也有一些api 需要考慮:
所謂“引用循環(huán)”是指雙向的強引用,所以那些“單向的強引用”(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;
}];
這些情況不需要考慮“引用循環(huán)”形真。
但如果你使用一些參數(shù)中可能含有實例變量的系統(tǒng) api 如 GCD 、NSNotificationCenter就要小心一點:
比如GCD 內(nèi)部如果引用了 self超全,而且 GCD 的其他參數(shù)是實例變量咆霜,則要考慮到循環(huán)引用:
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 顯然這也是一個循環(huán)引用。
這篇文章介紹了weakself和strongself的用法:到底什么時候才需要在ObjC的Block中使用weakSelf/strongSelf
5.3 NSTimer導致循環(huán)引用的例子
一方面嘶朱,NSTimer經(jīng)常會被作為某個類的成員變量蛾坯,而NSTimer初始化時要指定self為target,容易造成循環(huán)引用疏遏。
另一方面脉课,若timer一直處于validate的狀態(tài),則其引用計數(shù)將始終大于0财异。比如當定時器銷毀的時機不對倘零,在dealloc里面銷毀的時候,內(nèi)存就不會釋放,就會造成循環(huán)引用
.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中聲明并且調(diào)用戳寸,通過函數(shù)讓Friend類延時5秒后引用計數(shù)減一
#import "Friend.h"
//循環(huán)引用
//是一個很麻煩的一件事呈驶,完全靠經(jīng)驗
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;
}
我們所期待的結(jié)果是,初始化5秒后疫鹊,friend對象被release袖瞻,friend的dealloc方法被調(diào)用司致,在dealloc里面timer失效,對象被析構(gòu)聋迎。但結(jié)果卻是從未停下..
這是為什么呢脂矫?主要是因為從timer的角度,timer認為調(diào)用方(Friend對象)被析構(gòu)時會進入dealloc霉晕,在dealloc可以順便將timer的計時停掉并且釋放內(nèi)存庭再;但是從Friend的角度,他認為timer不停止計時不析構(gòu)牺堰,那我永遠沒機會進入dealloc佩微。循環(huán)引用,互相等待萌焰,無窮盡。問題的癥結(jié)在于-(void)cleanTimer函數(shù)的調(diào)用時機不對谷浅,顯然不能想當然地放在調(diào)用者的dealloc中扒俯。
一個比較好的解決方法是開放這個函數(shù),讓Friend的調(diào)用者顯式地調(diào)用來清理現(xiàn)場一疯。如下:
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的區(qū)別與聯(lián)系
- 每個 UIView 內(nèi)部都有一個 CALayer 在背后提供內(nèi)容的繪制和顯示撼玄,UIView的尺寸樣式都由內(nèi)部的 Layer 所提供。兩者都有樹狀層級結(jié)構(gòu)墩邀,layer 內(nèi)部有 SubLayers掌猛,View內(nèi)部有SubViews。但是Layer比View多了個AnchorPoint眉睹。
- 在View顯示的時候荔茬,UIView做為Layer的CALayerDelegate,View的顯示內(nèi)容由內(nèi)部的CALayer的display竹海。
- CALayer修改屬性是支持隱式動畫的慕蔚,在給UIView的Layer做動畫的時候,View作為Layer的代理斋配,Layer向View請求相應(yīng)的action(動畫行為)
- layer 內(nèi)部維護著三份 layer tree孔飒,分別是 :
presentLayer Tree(動畫樹)
modeLayer Tree(模型樹)
Render Tree (渲染樹)。
在做iOS動畫的時候艰争,我們修改動畫的屬性坏瞄,在動畫的其實是Layer的presentLayer屬性值,而最終展示在界面上的其實是提供View的modelLayer - 兩者最明顯的區(qū)別是View可以接受并處理事件甩卓,而Layer不可以
7.詳細描述一下響應(yīng)者鏈的含義(最好能圖釋)
iOS 系統(tǒng)檢測到手指觸摸 (Touch) 操作時會將其打包成一個 UIEvent 對象鸠匀,并放入當前活動Application的事件隊列,單例UIApplication會從事件隊列中取出觸摸事件并傳遞給單例UIWindow來處理猛频,UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View)狮崩,即需要將觸摸事件傳遞給其處理的視圖蛛勉,這個過程稱之為hit-test view。
hitTest:withEvent:方法的處理流程如下:
- 首先調(diào)用當前視圖的pointInside:withEvent: 方法判斷觸摸點是否在當前視圖內(nèi)睦柴;
- 若返回NO, 則hitTest:withEvent: 返回 nil诽凌,若返回YES, 則向當前視圖的所有子視圖 (subviews) 發(fā)送 hitTest:withEvent: 消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖坦敌,即從subviews數(shù)組的末尾向前遍歷侣诵,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;
- 若第一次有子視圖返回非空對象狱窘,則 hitTest:withEvent: 方法返回此對象杜顺,處理結(jié)束;
- 如所有子視圖都返回空蘸炸,則 hitTest:withEvent: 方法返回自身 (self)躬络。
如果最終 hit-test 沒有找到第一響應(yīng)者,或者第一響應(yīng)者沒有處理該事件搭儒,則該事件會沿著響應(yīng)者鏈向上回溯穷当,如果 UIWindow 實例和 UIApplication 實例都不能處理該事件,則該事件會被丟棄(這個過程即上面提到的響應(yīng)值鏈)淹禾;
8.如何高效的剪切圓角圖片
CALayer 的 border馁菜、圓角、陰影铃岔、遮罩(mask)汪疮,CASharpLayer 的矢量圖形顯示,通常會觸發(fā)離屏渲染(offscreen rendering)毁习,而離屏渲染通常發(fā)生在 GPU 中智嚷。當一個列表視圖中出現(xiàn)大量圓角的 CALayer,并且快速滑動時蜓洪,可以觀察到 GPU 資源已經(jīng)占滿纤勒,而 CPU 資源消耗很少。這時界面仍然能正陈√矗滑動摇天,但平均幀數(shù)會降到很低。為了避免這種情況恐仑,可以嘗試開啟 CALayer.shouldRasterize 屬性泉坐,但這會把原本離屏渲染的操作轉(zhuǎn)嫁到 CPU 上去。對于只需要圓角的某些場合裳仆,也可以用一張已經(jīng)繪制好的圓角圖片覆蓋到原本視圖上面來模擬相同的視覺效果腕让。最徹底的解決辦法,就是把需要顯示的圖形在后臺線程繪制為圖片,避免使用圓角纯丸、陰影偏形、遮罩等屬性。
9.const/static分別用法觉鼻,修飾類時又如何 #define
- static
- 函數(shù)體內(nèi) static 變量的作用范圍為該函數(shù)體俊扭,該變量的內(nèi)存只被分配一次,因此其值在下次調(diào)用時仍維持上次的值坠陈;
- 在模塊內(nèi)的 static "全局變量"可以被模塊內(nèi)所有函數(shù)訪問萨惑,但不能被模塊外其它函數(shù)訪問;
- 在模塊內(nèi)的 static "函數(shù)"只可被這一模塊內(nèi)的其它函數(shù)調(diào)用仇矾,這個函數(shù)的使用范圍被限制在聲明它的模塊內(nèi)庸蔼;
- 在類中的 static "成員變量"屬于整個類所擁有,對類的所有對象只有一份拷貝贮匕;
- 在類中的 static "成員函數(shù)"屬于整個類所擁有姐仅,這個函數(shù)不接收 this 指針,因而只能訪問類的static 成員變量刻盐。
宏:
#define HSCoder @"漢斯哈哈哈"
變量:
NSString *HSCoder = @"漢斯哈哈哈";
常量:
四種寫法:
static const NSString *HSCoder = @"漢斯哈哈哈";
const NSString *HSCoder = @"漢斯哈哈哈"; //"*HSCoder"不能被修改萍嬉, "HSCoder"能被修改
NSString const *HSCoder = @"漢斯哈哈哈"; //"*HSCoder"不能被修改, "HSCoder"能被修改(與上個沒啥區(qū)別)
NSString * const HSCoder = @"漢斯哈哈哈"; //"HSCoder"不能被修改隙疚,"*HSCoder"能被修改
結(jié)論:const右邊的總不能被修改
所以一般我們定義一個常量又不想被修改應(yīng)該選擇最后一種方案:
NSString * const HSCoder = @"漢斯哈哈哈";
.h文件中這樣寫:
UIKIT_EXTERN NSString *const HSCoder
10.如何使用Objective-C實現(xiàn)多重繼承?并給出代碼示例
- 通過組合實現(xiàn)“多繼承”
- 通過協(xié)議實現(xiàn)“多繼承”
- 通過category實現(xiàn)“單繼承”(大部分網(wǎng)上文章將此方法誤解成“多繼承”)
11.關(guān)于app性能優(yōu)化你是怎么理解的磕道?請詳細描述一下
- 入門級(這是些你一定會經(jīng)常用在你app開發(fā)中的建議,8個)
- 用ARC管理內(nèi)存
- 不要block主線程
- 打開gzip壓縮
- 在正確的地方使用reuseIdentifier
自iOS6起供屉,除了UICollectionView的cells和補充views,也應(yīng)該在header和footer views中使用reuseIdentifiers伶丐。 - 盡可能使Views不透明, 避免圖層混合
確保控件的opaque屬性設(shè)置為true疯特,確保backgroundColor和父視圖顏色一致且不透明
如無特殊需要邻吞,不要設(shè)置低于1的alpha值
確保UIImage沒有alpha通道 - 避免龐大的XIB
如果你不得不XIB的話抱冷,使他們盡量簡單旺遮。嘗試為每個Controller配置一個單獨的XIB赵讯,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨的XIB中去盈咳。
需要注意的是,當你加載一個XIB的時候所有內(nèi)容都被放在了內(nèi)存里边翼,包括任何圖片鱼响。如果有一個不會即刻用到的view,你這就是在浪費寶貴的內(nèi)存資源了讯私。Storyboards就是另一碼事兒了热押,storyboard僅在需要時實例化一個view controller. - 在Image Views中調(diào)整圖片大小,避免臨時轉(zhuǎn)換
確保圖片大小和frame一致,不要在滑動時縮放圖片
確保圖片顏色格式被GPU支持斤寇,避免勞煩CPU轉(zhuǎn)換 - 選擇正確的Collection
Arrays: 有序的一組值桶癣。使用index來lookup很快,使用value lookup很慢娘锁, 插入/刪除很慢牙寞。
Dictionaries: 存儲鍵值對。 用鍵來查找比較快莫秆。
Sets: 無序的一組值间雀。用值來查找很快,插入/刪除很快镊屎。
- 中級(這些是你可能在一些相對復雜情況下可能用到的)
權(quán)衡渲染方法
優(yōu)化你的Table View
重用和延遲加載Views
這里我們用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次創(chuàng)建所有的subview惹挟,而是當需要時才創(chuàng)建,當它們完成了使命缝驳,把他們放進一個可重用的隊列中连锯。Cache, Cache, 還是Cache!
NSCache和NSDictionary類似用狱,不同的是系統(tǒng)回收內(nèi)存的時候它會自動刪掉它的內(nèi)容唾那。-
處理內(nèi)存警告
如果你的app收到了內(nèi)存警告觉壶,它就需要盡可能釋放更多的內(nèi)存官觅。最佳方式是移除對緩存刻肄,圖片object和其他一些可以重創(chuàng)建的objects的strong references.
UIKit提供了幾種收集低內(nèi)存警告的方法:- 在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
- 在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
- 注冊并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
避免反復處理數(shù)據(jù),選擇正確的數(shù)據(jù)格式
如需要數(shù)據(jù)來展示一個table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變溺忧。
如需要從特定key中取數(shù)據(jù)咏连,那么就使用鍵值對的dictionary。正確地設(shè)定Background Images
如果你使用全畫幅的背景圖鲁森,你就必須使用UIImageView因為UIColor的colorWithPatternImage是用來創(chuàng)建小的重復的圖片作為背景的捻勉。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存
如果你用小圖平鋪來創(chuàng)建背景,你就需要用UIColor的colorWithPatternImage來做了刀森,它會更快地渲染也不會花費很多內(nèi)存
重用大開銷的對象
一些objects的初始化很慢踱启,比如NSDateFormatter和NSCalendar,想要避免使用這個對象的瓶頸你就需要重用他們,可以通過1.添加屬性到你的class里 或者2.創(chuàng)建靜態(tài)變量來實現(xiàn)埠偿。
如果你要選擇第二種方法透罢,對象會在你的app運行時一直存在于內(nèi)存中,和單例(singleton)很相似冠蒋。
下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調(diào)用時它會創(chuàng)建一個新的實例羽圃,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實例:
// 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;
}
-
進階級(這些建議只應(yīng)該在你確信他們可以解決問題和得心應(yīng)手的情況下采用)
- 加速啟動時間
- 使用Autorelease Pool
- 選擇是否緩存圖片
- 盡量避免日期格式轉(zhuǎn)換
-
UIKit方面
- 慎用離屏渲染
絕大多數(shù)時候離屏渲染會影響性能
重寫drawRect方法,設(shè)置圓角抖剿、陰影朽寞、模糊效果,光柵化都會導致離屏渲染
設(shè)置陰影效果是加上陰影路徑
滑動時若需要圓角效果斩郎,開啟光柵化
- 慎用離屏渲染
drawrect
繪制圖形性能的優(yōu)化最好辦法就是不去繪制脑融。
利用專有圖層代替繪圖需求。
不得不用到繪圖盡量縮小視圖面積缩宜,并且盡量降低重繪頻率肘迎。
異步繪制,推測內(nèi)容锻煌,提前在其他線程繪制圖片妓布,在主線程中直接設(shè)置圖片。
12. tableview優(yōu)化
為了保證table view平滑滾動宋梧,確保你采取了以下的措施:
- 正確使用reuseIdentifier來重用cells
- 盡量使所有的view opaque匣沼,包括cell自身
- 避免漸變,圖片縮放
- 緩存行高
- 如果cell內(nèi)現(xiàn)實的內(nèi)容來自web捂龄,使用異步加載肛著,緩存請求結(jié)果
- 使用shadowPath來畫陰影
- 減少subviews的數(shù)量
- 盡量不適用cellForRowAtIndexPath:,如果你需要用到它跺讯,只用一次然后緩存結(jié)果
- 使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
- 使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight來設(shè)定固定的高,不要請求delegate
13. 對象沒銷毀的原因有哪些
- 控制器中NSTimer沒有被銷毀
當控制器中存在NSTimer時殉农,就需要注意刀脏,因為當
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateTime:)
userInfo:nil
repeats:YES];
時,這個target:self
就增加了VC的RetainCount超凳,如果你不將這個timer invalidate愈污,就別想調(diào)用dealloc。需要在viewWillDisappear之前需要把控制器用到的NSTimer銷毀轮傍。
[timer invalidate]; // 銷毀timer
timer = nil; // 置nil
- 控制器中的代理不是weak屬性
例如@property (nonatomic, weak) id<HCAppViewDelegate> delegate;
代理要使用弱引用暂雹,因為自定義控件是加載在視圖控制器中的,視圖控制器view對自定義控件是強引用创夜,如果代理屬性設(shè)置為strong杭跪,則意味著delegate對視圖控制器也進行了強引用,會造成循環(huán)引用。導致控制器無法被釋放涧尿,最終導致內(nèi)存泄漏系奉。 - 控制器中block的循環(huán)引用
14.多線程操作
-
14.1 多線程處理方式及優(yōu)缺
- pThread c語言框架
一套通用的多線程API,適用于Linux\Windows\Unix,跨平臺姑廉,可移植缺亮,使用C語言,生命周期需要程序員管理桥言,IOS開發(fā)中使用很少萌踱。 - NSThread apple 封裝過,面向?qū)ο蠛虐ⅲ芍苯硬倏鼐€程對象
優(yōu)點:NSThread 比其他兩個輕量級并鸵。
缺點:需要自己管理線程的生命周期,線程同步倦西。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷能真。
1.先創(chuàng)建線程類,再啟動
NSThread *thread = [[NSThread alloc] initWithTarget:self
selector:@selector(run:)
object:nil];
[thread start];
2.創(chuàng)建并自動啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
3.使用 NSObject 的方法創(chuàng)建并自動啟動
[self performSelectorInBackground:@selector(run:) withObject:nil];
- GCD為多核并行運算提出的解決方案扰柠,自動管理線程的生命周期(創(chuàng)建線程粉铐、調(diào)度任務(wù)、銷毀線程)
- 任務(wù):即操作卤档,你想要干什么蝙泼,說白了就是一段代碼,在 GCD 中就是一個 Block劝枣,所以添加任務(wù)十分方便汤踏。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間主要區(qū)別在于會不會阻塞當前線程舔腾,直到 Block 中的任務(wù)執(zhí)行完畢溪胶!
- 同步(sync) 操作,它會阻塞當前線程并等待 Block 中的任務(wù)執(zhí)行完畢稳诚,然后當前線程才會繼續(xù)往下運行哗脖。
- 異步(async)操作,當前線程會直接往下執(zhí)行扳还,它不會阻塞當前線程才避。
- 隊列:用于存放任務(wù)。一共有兩種隊列氨距, 串行隊列 和 并行隊列桑逝。
- 串行隊列:GCD 會 FIFO(先進先出) 地取出來一個,執(zhí)行一個俏让,然后取下一個楞遏,這樣一個一個的執(zhí)行茬暇。
- 并行隊列:放到并行隊列的任務(wù),GCD 也會 FIFO的取出來橱健,但不同的是而钞,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程拘荡。這樣由于取的動作很快臼节,忽略不計,看起來珊皿,所有的任務(wù)都是一起執(zhí)行的网缝。不過需要注意,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量蟋定,所以如果任務(wù)很多粉臊,它并不會讓所有任務(wù)同時執(zhí)行。
- 任務(wù):即操作卤档,你想要干什么蝙泼,說白了就是一段代碼,在 GCD 中就是一個 Block劝枣,所以添加任務(wù)十分方便汤踏。任務(wù)有兩種執(zhí)行方式: 同步執(zhí)行 和 異步執(zhí)行,他們之間主要區(qū)別在于會不會阻塞當前線程舔腾,直到 Block 中的任務(wù)執(zhí)行完畢溪胶!
創(chuàng)建隊列
1.主隊列:這是一個特殊的 `串行隊列`
dispatch_queue_t queue = dispatch_get_main_queue();
2.全局并行隊列:只要是并行任務(wù)一般都加入到這個隊列驶兜。這是系統(tǒng)提供的一個并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.自己創(chuàng)建的隊列:
//串行隊列
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);
創(chuàng)建任務(wù)
1.同步任務(wù): 會阻塞當前線程 (SYNC)
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
2.異步任務(wù):不會阻塞當前線程 (ASYNC)
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
隊列組
//1.創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
//2.創(chuàng)建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用隊列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主隊列執(zhí)行8次循環(huán)
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.執(zhí)行5次循環(huán)
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的封裝 它的實例封裝了需要的操作以及操作所需要的數(shù)據(jù)
優(yōu)點:不需要關(guān)心線程管理扼仲, 數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上抄淑。NSOperation是個抽象類,可以使用它定義好的兩個子類:NSInvocationOperation
和NSBlockOperation
屠凶,或者用它的子類(比較高級,暫不提)肆资,創(chuàng)建NSOperation子類的對象矗愧。
NSOperation
和NSOperationQueue
分別對應(yīng) GCD 的任務(wù)
和隊列
添加任務(wù)
1.NSInvocationOperation : 需要傳入一個方法名
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
2.NSBlockOperation
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務(wù)
[operation start];
NSBlockOperation還有一個方法:addExecutionBlock: 通過這個方法可以給Operation添加多個執(zhí)行Block。
這樣Operation中的任務(wù)會并發(fā)執(zhí)行郑原,它會在主線程和其它的多個線程執(zhí)行這些任務(wù)
創(chuàng)建隊列:
只要添加到隊列唉韭,會自動調(diào)用任務(wù)的 start() 方法
1.主隊列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
2.其他隊列
//1.創(chuàng)建一個其他隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.創(chuàng)建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.隊列添加任務(wù)
[queue addOperation:operation];
將NSOperationQueue與GCD的隊列相比較就會發(fā)現(xiàn),這里沒有串行隊列犯犁,那如果我想要10個任務(wù)在其他線程串行的執(zhí)行怎么辦属愤?
這就是蘋果封裝的妙處,你不用管串行酸役、并行住诸、同步、異步這些名詞簇捍。NSOperationQueue有一個參數(shù)maxConcurrentOperationCount最大并發(fā)數(shù),用來設(shè)置最多可以讓多少個任務(wù)同時執(zhí)行俏拱。當你把它設(shè)置為1的時候暑塑,他不就是串行了嘛!
NSOperation 有一個非常實用的功能锅必,那就是添加依賴事格。比如有 3 個任務(wù):A: 從服務(wù)器上下載一張圖片惕艳,B:給這張圖片加個水印,C:把圖片返回給服務(wù)器驹愚。這時就可以用到依賴了
-
14.2什么時候使用GCD远搪,什么時候使用NSOperation?如何按照指定的執(zhí)行順序完成任務(wù)逢捺?例如谁鳍,c任務(wù)需要在a、b任務(wù)完成之后執(zhí)行劫瞳,請分別使用GCD和NSOperation寫書代碼示例
-
14.3 有個圖片下載未完成劃過后重新出發(fā)沒下載圖片會怎樣
-
14.4 GCD的隊列(dispatch_queue_t)分哪兩種類型倘潜?
串行隊列和并行隊列
-
14.5 如何用GCD同步若干個異步調(diào)用?(如根據(jù)若干個url異步加載多張圖片志于,然后在都下載完成后合成一張整圖)
總體上說: 使用 dispatch group涮因,然后 wait forever 等待完成, 或者采取 group notify 來通知回調(diào)伺绽。
細節(jié):
1. 創(chuàng)建異步隊列
2. 創(chuàng)建dispatch_group dispatch_group_t = dispatch_group_create()
3. 通過組來執(zhí)行異步下載任務(wù) dispatch_group_async(queueGroup, aQueue, ^{
NSLog(@"下載圖片."); });
4.等到所有任務(wù)完成 dispatch_group_wait(queueGroup, DISPATCH_TIME_FOREVER);
5.合成圖片
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執(zhí)行完畢养泡,就會執(zhí)行Main Dispatch Queue中的結(jié)束處理的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內(nèi)存管理的理解
《Effective Objective-C 2.0》
理解引用計數(shù)
- 引用計數(shù)工作原理
- 屬性存取方法中的內(nèi)存管理
- 自動釋放池
- 保留環(huán)
以arc簡化引用計數(shù)
- 使用ARC時必須遵循的方法命名規(guī)則
- 變量的內(nèi)存管理語義
- arc如何清理實例變量
- 覆寫內(nèi)存管理方法
- 用“僵尸對象”調(diào)試內(nèi)存管理問題
- 不要使用retain count
在dealloc方法中只釋放引用并解除監(jiān)聽
編寫"異常安全代碼"時注意內(nèi)存管理問題
以弱引用避免保留環(huán)
以"自動釋放池塊"降低內(nèi)存峰值
《Objective-C高級編程》
>Apple 在Objective-C中采用ARC機制奈应,讓編譯器來進行內(nèi)存管理澜掩。在新一代Apple LLVM編譯器中設(shè)置ARC為有效狀態(tài),就無需再次鍵入retain或release代碼钥组,這在降低程序崩潰输硝,內(nèi)存泄漏等風險的同時,很大程度上減少了開發(fā)程序的工作量程梦。編譯器完全清楚目標對象点把,并能立刻釋放那些不再被使用的對象。如此依賴屿附,應(yīng)用程序?qū)⒕哂锌深A測性郎逃,且能流程運行,速度也將大幅提升挺份。
- 自己生成的對象褒翰,自己持有
- 非自己生成的對象,自己也能持有
- 不再需要自己持有的對象時釋放
- 非自己持有的對象無法釋放
- ######15.1.內(nèi)存泄漏有哪些情況
- 循環(huán)引用
- Delegate
我們在使用代理設(shè)計模式的時候匀泊,一定要注意將 delegate 變量聲明為 weak 類型优训,像這樣 @property (nonatomic, weak) id delegate;
- Block
__weak typeof(self)weakSelf = self;
- NSTimer
在一個ViewController里創(chuàng)建了一個定時器,并且repeats:值為YES各聘,一定記得在 pop/dismiss當前ViewController將timer設(shè)為invalidate揣非,否則會造成循環(huán)引用,這里要特別需要注意的一點是:我們不要在ViewController的dealloc方法里調(diào)用[timer invalidate]; 因為從來不會調(diào)到這里躲因,我們應(yīng)該在viewWillDisappear里邊調(diào)用
- performSelector延時調(diào)用導致的內(nèi)存泄露
假設(shè)你延時10s觸發(fā)某個方法早敬,但是在3s的時候忌傻,用戶點擊了back,這時對象不能馬上被回收搞监,而是要等到10s后才有可能被回收水孩。所以在項目中如果要延時觸發(fā)方法,我不會選擇該方法琐驴,而是使用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俘种,發(fā)現(xiàn)delegate都是assign的,這樣就會引起野指針的問題棍矛,可能會引起一些莫名其妙的crash安疗。那么這是怎么引起的,當一個對象被回收時够委,對應(yīng)的delegate實體也就被回收荐类,但是delegate的指針確沒有被nil,從而就變成了游蕩的野指針了茁帽。所以在delloc方法中要將對應(yīng)的assign代理設(shè)置為nil
一般自己寫的一些delegate玉罐,我們會用weak,而不是assign潘拨,weak的好處是當對應(yīng)的對象被回收時吊输,指針也會自動被設(shè)置為nil。
- ######15.2 什么時候用@autoreleasepool
- 寫基于命令行的的程序時铁追,就是沒有UI框架季蚂,如AppKit等Cocoa框架時。
- 寫循環(huán)琅束,循環(huán)里面包含了大量臨時創(chuàng)建的對象扭屁。(500000次循環(huán),每次循環(huán)創(chuàng)建一個NSNumber實例和兩個NSString實例)
- 創(chuàng)建了新的線程涩禀。(非Cocoa程序創(chuàng)建線程時才需要)
- 長時間在后臺運行的任務(wù)料滥。
- ######15.3. 頁面使用過多內(nèi)存過多閃退
- ######15.4. 1000張圖片內(nèi)存
- ######15.5.界面交互優(yōu)化騰訊 banner100頁怎么辦
######16. 詳細描述一下KVO的實現(xiàn)機制,代理 通知和kvo區(qū)別 代理和block區(qū)別 block 訪問外部變量原理
######17. runtime的理解
- runtime作用
- 發(fā)送消息
消息機制原理:對象根據(jù)方法編號SEL去映射表查找對應(yīng)的方法實現(xiàn)
- 交換方法
開發(fā)使用場景:系統(tǒng)自帶的方法功能不夠艾船,給系統(tǒng)自帶的方法擴展一些功能葵腹,并且保持原有的功能。
方式一:繼承系統(tǒng)的類屿岂,重寫方法.
方式二:使用runtime,交換方法.
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址践宴,相當于交換實現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
- 動態(tài)添加方法
開發(fā)使用場景:如果一個類方法非常多,加載類到內(nèi)存的時候也比較耗費資源爷怀,需要給每個方法生成映射表阻肩,可以使用動態(tài)給某個類,添加方法解決霉撵。
經(jīng)典面試題:有沒有使用performSelector磺浙,其實主要想問你有沒有動態(tài)添加過方法。
簡單使用
@implementation Person
// void(*)()
// 默認方法都有兩個隱式參數(shù)徒坡,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當一個對象調(diào)用未實現(xiàn)的方法撕氧,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來.
// 剛好可以用來判斷,未實現(xiàn)的方法是不是我們想要動態(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 動態(tài)添加eat方法
// 第一個參數(shù):給哪個類添加方法
// 第二個參數(shù):添加方法的方法編號
// 第三個參數(shù):添加方法的函數(shù)實現(xiàn)(函數(shù)地址)
// 第四個參數(shù):函數(shù)的類型喇完,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
- 給分類添加屬性
原理:給一個類聲明屬性伦泥,其實本質(zhì)就是給這個類添加關(guān)聯(lián),并不是直接把這個值的內(nèi)存空間添加到類存空間锦溪。
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name
{
// 根據(jù)關(guān)聯(lián)的key不脯,獲取關(guān)聯(lián)的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一個參數(shù):給哪個對象添加關(guān)聯(lián)
// 第二個參數(shù):關(guān)聯(lián)的key刻诊,通過這個key獲取
// 第三個參數(shù):關(guān)聯(lián)的value
// 第四個參數(shù):關(guān)聯(lián)的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 字典轉(zhuǎn)模型
- runtime如何通過selector找到對應(yīng)的IMP地址防楷?(分別考慮類方法和實例方法)
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型,其實selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實現(xiàn).
- category中能不能使用聲明屬性?為什么则涯?如果能复局,怎么實現(xiàn)?
給分類(Category)添加屬性利用Runtime實現(xiàn)getter/setter 方法
@interface ClassName (CategoryName)
@property (nonatomic, strong) NSString *str;
@end
//實現(xiàn)文件
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的異常粟判?
簡單來說:當該對象上某個方法,而該對象上沒有實現(xiàn)這個方法的時候亿昏, 可以通過“消息轉(zhuǎn)發(fā)”進行解決。
簡單的流程如下:objc是動態(tài)語言档礁,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送角钩,即:objc_msgSend(receiver, selector)。
objc在向一個對象發(fā)送消息時呻澜,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類递礼,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,如果易迹,在最頂層的父類中依然找不到相應(yīng)的方法時宰衙,程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前睹欲,objc的運行時會給出三次拯救程序崩潰的機會:
`Method resolution`
objc運行時會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:供炼,讓你有機會提供一個函數(shù)實現(xiàn)。如果你添加了函數(shù)并返回 YES窘疮,那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程袋哼,如果 resolve 方法返回 NO ,運行時就會移到下一步闸衫,消息轉(zhuǎn)發(fā)(Message Forwarding)涛贯。
`Fast forwarding`
如果目標對象實現(xiàn)了-forwardingTargetForSelector:,Runtime 這時就會調(diào)用這個方法蔚出,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會弟翘。 只要這個方法返回的不是nil和self虫腋,整個消息發(fā)送的過程就會被重啟,當然發(fā)送的對象會變成你返回的那個對象稀余。否則悦冀,就會繼續(xù)Normal Fowarding。 這里叫Fast睛琳,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機制盒蟆。因為這一步不會創(chuàng)建任何新的對象,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象师骗,所以相對更快點历等。
`Normal forwarding`
這一步是Runtime最后一次給你挽救的機會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型辟癌。如果-methodSignatureForSelector:返回nil寒屯,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了黍少。如果返回了一個函數(shù)簽名浩螺,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標對象。
######18. runloop的理解
- RunLoop 的概念
如果我們需要一個機制仍侥,讓線程能隨時處理事件但并不退出要出,這種模型通常被稱作 Event Loop。Event Loop 在很多系統(tǒng)和框架里都有實現(xiàn)农渊,比如 OSX/iOS 里的 RunLoop患蹂。實現(xiàn)這種模型的關(guān)鍵點在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用砸紊、在有消息到來時立刻被喚醒传于。
- RunLoop 與線程的關(guān)系
蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()醉顽。
線程和 RunLoop 之間是一一對應(yīng)的沼溜,其關(guān)系是保存在一個全局的 Dictionary 里。線程剛創(chuàng)建時并沒有 RunLoop游添,如果你不主動獲取系草,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時唆涝,RunLoop 的銷毀是發(fā)生在線程結(jié)束時找都。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)。
######19. contentoffset contentinset contentsize
######20. 說說知道哪些設(shè)計模式廊酣,說說簡單工廠和抽象工廠
######21.第三方分享的調(diào)用接口
######22.動畫的實現(xiàn)方式
######23. 錯誤提示sympol
######24. 使用定時器注意問題
######25. 頁面滑動丟幀的原因有多少種
######26.推送的原理具體實現(xiàn)
######27. 如果不用第三方庫實現(xiàn)圖片緩存設(shè)計一種方法
######28. 獲得App閃退log怎么做
######29. 完整發(fā)布流程
- 如何重寫帶 copy 關(guān)鍵字的 setter能耻?
重寫copy的setter方法時候,一定要調(diào)用一下傳入的對象的copy方法,然后在賦值給該setter的方法對應(yīng)的成員變量
- @synthesize和@dynamic分別有什么作用?
- @property有兩個對應(yīng)的詞,一個是@synthesize晓猛,一個是@dynamic饿幅。如果@synthesize和@dynamic都沒寫,那么默認的就是@syntheszie var = _var;
- @synthesize的語義是如果你沒有手動實現(xiàn)setter方法和getter方法戒职,那么編譯器會自動為你加上這兩個方法诫睬。
- @dynamic告訴編譯器,屬性的setter與getter方法由用戶自己實現(xiàn),不自動生成帕涌。(當然對于readonly的屬性只需提供getter即可)。假如一個屬性被聲明為@dynamic var续徽,然后你沒有提供@setter方法和@getter方法蚓曼,編譯的時候沒問題,但是當程序運行到instance.var =someVar钦扭,由于缺setter方法會導致程序崩潰纫版;或者當運行到 someVar = var時,由于缺getter方法同樣會導致崩潰客情。編譯時沒問題其弊,運行時才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定膀斋。
- BAD_ACCESS在什么情況下出現(xiàn)梭伐,如何調(diào)試?
1. 死循環(huán)了
2. 訪問一個僵尸對象
設(shè)置全局斷點快速定位問題代碼所在行
- 談?wù)刬nstancetype和id的異同
1仰担、相同點
都可以作為方法的返回類型
2糊识、不同點
①instancetype可以返回和方法所在類相同類型的對象,id只能返回未知類型的對象摔蓝;②instancetype只能作為返回值赂苗,不能像id那樣作為參數(shù)
- @protocol 和 category 中如何使用 @property
1)在protocol中使用property只會生成setter和getter方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對象能實現(xiàn)該屬性
2)category 使用 @property 也是只會生成setter和getter方法的聲明,如果我們真的需要給category增加屬性的實現(xiàn),需要借助于運行時的兩個函數(shù):
①objc_setAssociatedObject
②objc_getAssociatedObject