內(nèi)容均轉(zhuǎn)自標(biāo)哥的技術(shù)博客 只是按照自己的習(xí)慣進(jìn)行簡(jiǎn)單的整理
1、對(duì)數(shù)組中的元素去重復(fù)
NSArray *array = @[@"12-11", @"12-11", @"12-11", @"12-12", @"12-13", @"12-14"];
- 1.第一種方法:開辟新的內(nèi)存空間株茶,然后判斷是否存在洽胶,若不存在則添加到數(shù)組中,得到最終結(jié)果的順序不發(fā)生變化佳镜。效率分析:時(shí)間復(fù)雜度為O ( n2 ):
NSMutableArray *resultArray = [[NSMutableArray alloc] initWithCapacity:array.count];
// 外層一個(gè)循環(huán)
for (NSString *item in array) {
// 調(diào)用-containsObject:本質(zhì)也是要循環(huán)去判斷僚稿,因此本質(zhì)上是雙層遍歷
// 時(shí)間復(fù)雜度為O ( n^2 )而不是O (n)
if (![resultArray containsObject:item]) {
[resultArray addObject:item];
}
}
NSLog(@"resultArray: %@", resultArray);
//補(bǔ)充:原來集合操作可以通過valueForKeyPath來實(shí)現(xiàn)的,去重可以一行代碼實(shí)現(xiàn):
array = [array valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", array);
但是返回的結(jié)果是無序的蟀伸,與原來的順序不同蚀同。大家可以閱讀:Collection Operators
- 2.第二種方法:利用NSDictionary去重,字典在設(shè)置key-value時(shí)啊掏,若已存在則更新值蠢络,若不存在則插入值,然后獲取allValues迟蜜。若不要求有序刹孔,則可以采用此種方法娜睛。若要求有序髓霞,還得進(jìn)行排序卦睹。效率分析:只需要一個(gè)循環(huán)就可以完成放入字典,若不要求有序方库,時(shí)間復(fù)雜度為O(n)结序。若要求排序,則效率與排序算法有關(guān):
NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] initWithCapacity:array.count];
for (NSString *item in array) {
[resultDict setObject:item forKey:item];
}
NSArray *resultArray = resultDict.allValues;
NSLog(@"%@", resultArray);
//需要結(jié)果有序的話 就用快速枚舉排序
- 3.利用集合NSSet的特性(確定性纵潦、無序性徐鹤、互異性),放入集合就自動(dòng)去重了酪穿。但是它與字典擁有同樣的無序性凳干,所得結(jié)果順序不再與原來一樣。如果不要求有序被济,使用此方法與字典的效率應(yīng)該是差不多的救赐。效率分析:時(shí)間復(fù)雜度為O (n):
NSSet *set = [NSSet setWithArray:array];
NSArray *resultArray = [set allObjects];
NSLog(@"%@", resultArray);
//使用過有序集合
NSOrderedSet *set = [NSOrderedSet orderedSetWithArray:array];
NSLog(@"%@", set.array);
2、說說以下元素的特性和作用
NSArray NSSet NSDictionary與NSMutableArray NSMutableSet NSMutableDictionary
特性:
- NSArray表示不可變數(shù)組只磷,是有序元素集经磅,只能存儲(chǔ)對(duì)象類型,可通過索引直接訪問元素钮追,而且元素類型可以不一樣预厌,但是不能進(jìn)行增、刪元媚、改操作轧叽;NSMutableArray是可變數(shù)組,能進(jìn)行增刊棕、刪炭晒、改操作。通過索引查詢值很快甥角,但是插入网严、刪除等效率很低。
- NSSet表示不可變集合嗤无,具有確定性震束、互異性、無序性的特點(diǎn)当犯,只能訪問而不能修改集合垢村;NSMutableSet表示可變集合,可以對(duì)集合進(jìn)行增嚎卫、刪肝断、改操作。集合通過值查詢很快驰凛,插入胸懈、刪除操作極快。
- NSDictionary表示不可變字典恰响,具有無序性的特點(diǎn)趣钱,每個(gè)key對(duì)應(yīng)的值是唯一的,可通過key直接獲取值胚宦;NSMutableDictionary表示可變字典首有,能對(duì)字典進(jìn)行增、刪枢劝、改操作井联。通過key查詢值、插入您旁、刪除值都很快烙常。
作用 - 數(shù)組用于處理一組有序的數(shù)據(jù)集,比如常用的列表的dataSource要求有序鹤盒,可通過索引直接訪問蚕脏,效率高。
- 集合要求具有確定性侦锯、互異性驼鞭、無序性,在iOS開發(fā)中是比較少使用到的尺碰,筆者也不清楚如何說明其作用
- 字典是鍵值對(duì)數(shù)據(jù)集挣棕,操作字典效率極高,時(shí)間復(fù)雜度為常量亲桥,但是值是無序的洛心。在ios中,常見的JSON轉(zhuǎn)字典两曼,字典轉(zhuǎn)模型就是其中一種應(yīng)用皂甘。
3、簡(jiǎn)單描述一下XIB與Storyboards悼凑,說一下他們的優(yōu)缺點(diǎn)偿枕。
參考答案:
筆者傾向于純代碼開發(fā),所以所提供的參考答案可能具有一定的個(gè)人感情户辫,不過還是給大家說說筆者的想法渐夸。
優(yōu)點(diǎn):
- XIB:在編譯前就提供了可視化界面,可以直接拖控件渔欢,也可以直接給控件添加約束墓塌,更直觀一些,而且類文件中就少了創(chuàng)建控件的代碼,確實(shí)簡(jiǎn)化不少苫幢,通常每個(gè)XIB對(duì)應(yīng)一個(gè)類访诱。
- Storyboard:在編譯前提供了可視化界面,可拖控件韩肝,可加約束触菜,在開發(fā)時(shí)比較直觀,而且一個(gè)storyboard可以有很多的界面哀峻,每個(gè)界面對(duì)應(yīng)一個(gè)類文件涡相,通過storybard,可以直觀地看出整個(gè)App的結(jié)構(gòu)剩蟀。
缺點(diǎn):
- XIB:需求變動(dòng)時(shí)催蝗,需要修改XIB很大,有時(shí)候甚至需要重新添加約束育特,導(dǎo)致開發(fā)周期變長(zhǎng)丙号。XIB載入相比純代碼自然要慢一些。對(duì)于比較復(fù)雜邏輯控制不同狀態(tài)下顯示不同內(nèi)容時(shí)且预,使用XIB是比較困難的槽袄。當(dāng)多人團(tuán)隊(duì)或者多團(tuán)隊(duì)開發(fā)時(shí),如果XIB文件被發(fā)動(dòng)锋谐,極易導(dǎo)致沖突遍尺,而且解決沖突相對(duì)要困難很多。
- Storyboard:需求變動(dòng)時(shí)涮拗,需要修改storyboard上對(duì)應(yīng)的界面的約束乾戏,與XIB一樣可能要重新添加約束,或者添加約束會(huì)造成大量的沖突,尤其是多團(tuán)隊(duì)開發(fā)。對(duì)于復(fù)雜邏輯控制不同顯示內(nèi)容時(shí)帆啃,比較困難台囱。當(dāng)多人團(tuán)隊(duì)或者多團(tuán)隊(duì)開發(fā)時(shí)牧挣,大家會(huì)同時(shí)修改一個(gè)storyboard,導(dǎo)致大量沖突,解決起來相當(dāng)困難。
4摆出、請(qǐng)把字符串2015-04-10格式化日期轉(zhuǎn)為NSDate類型
參考答案:
NSString *timeStr = @"2015-04-10";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd";
formatter.timeZone = [NSTimeZone defaultTimeZone];
NSDate *date = [formatter dateFromString:timeStr];
// 2015-04-09 16:00:00 +0000
NSLog(@"%@", date);
5、在App中混合HTML5開發(fā)App如何實(shí)現(xiàn)的首妖。在App中使用HTML5的優(yōu)缺點(diǎn)是什么偎漫?
參考答案:
在iOS中,通常是通常UIWebView來實(shí)現(xiàn)有缆,當(dāng)然在iOS8以后可以使用WKWebView來實(shí)現(xiàn).有以下幾種實(shí)現(xiàn)方法:
- 通過實(shí)現(xiàn)UIWebView的代理方法來攔截象踊,判斷scheme是否是約定好的温亲,然后iOS調(diào)用本地相關(guān)API來實(shí)現(xiàn):
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
- 在iOS7以后,可以直接通過JavaScripteCore這個(gè)庫來實(shí)現(xiàn)杯矩,通過往JS DOM注入對(duì)象栈虚,而這個(gè)對(duì)象對(duì)應(yīng)于我們iOS的某個(gè)類的實(shí)例。更詳細(xì)請(qǐng)閱讀:
OC JavaScriptCore與js交互
WKWebView新特性及JS交互
Swift JavaScriptCore與JS交互 - 可以通過WebViewJavascriptBridge來實(shí)現(xiàn)菊碟。具體如何使用节芥,請(qǐng)大家去其它博客搜索吧!
優(yōu)缺點(diǎn):
- iOS加入H5響應(yīng)比原生要慢很多逆害,體驗(yàn)不太好,這是缺點(diǎn)蚣驼。
- iOS加入H5可以實(shí)現(xiàn)嵌入別的功能入口魄幕,可隨時(shí)更改,不用更新版本就可以上線颖杏,這是最大的優(yōu)點(diǎn)
6纯陨、請(qǐng)描述一下同步和異步,說說它們之間的區(qū)別留储。
參考答案:
首先翼抠,我們要明確一點(diǎn),同步和異步都是在線程中使用的获讳。在iOS開發(fā)中阴颖,比如網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)時(shí),若使用同步請(qǐng)求丐膝,則只有請(qǐng)求成功或者請(qǐng)求失敗得到響應(yīng)返回后量愧,才能繼續(xù)往下走,也就是才能訪問其它資源(會(huì)阻塞了線程)帅矗。網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)異步請(qǐng)求時(shí)偎肃,不會(huì)阻塞線程,在調(diào)用請(qǐng)求后浑此,可以繼續(xù)往下執(zhí)行累颂,而不用等請(qǐng)求有結(jié)果才能繼續(xù)。
區(qū)別:
- 線程同步:是多個(gè)線程訪問同一資源時(shí)凛俱,只有當(dāng)前正在訪問的線程訪問結(jié)束之后紊馏,其他線程才能開始訪問(被阻塞)。
- 線程異步:是多個(gè)線程在訪問競(jìng)爭(zhēng)資源時(shí)最冰,可以在空閑等待時(shí)去訪問其它資源(不被阻塞)瘦棋。
7、請(qǐng)簡(jiǎn)單描述一下隊(duì)列和多線程的使用原理暖哨。
參考答案:
在iOS中隊(duì)列分為以下幾種:
- 串行隊(duì)列:隊(duì)列中的任務(wù)只會(huì)順序執(zhí)行
dispatch_queue_t q = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL);
- 并行隊(duì)列: 隊(duì)列中的任務(wù)通常會(huì)并發(fā)執(zhí)行
dispatch_queue_t q = dispatch_queue_create("......", DISPATCH_QUEUE_CONCURRENT);
- 全局隊(duì)列:是系統(tǒng)的赌朋,直接拿過來(GET)用就可以凰狞;與并行隊(duì)列類似
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 主隊(duì)列:每一個(gè)應(yīng)用程序?qū)?yīng)唯一一個(gè)主隊(duì)列,直接GET即可沛慢;在多線程開發(fā)中赡若,使用主隊(duì)列更新UI
dispatch_queue_t q = dispatch_get_main_queue();
上面這四種是針對(duì)GCD來講的,串行隊(duì)列中的任務(wù)只能一個(gè)個(gè)地執(zhí)行团甲,在前一個(gè)沒有執(zhí)行完畢之前逾冬,下一個(gè)只能等待。并行隊(duì)列可以并發(fā)地執(zhí)行任務(wù)躺苦,因此多個(gè)任務(wù)之間執(zhí)行的順序不能確定身腻,當(dāng)添加一個(gè)新的任務(wù)時(shí),交由GCD來判斷是否要?jiǎng)?chuàng)建新的新的線程匹厘。
大家可以閱讀圖片多線程嘀趟,也許更明了:
8、描述一下iOS的內(nèi)存管理愈诚,在開發(fā)中對(duì)于內(nèi)存的使用和優(yōu)化包含哪些方面她按。我們?cè)陂_發(fā)中應(yīng)該注意哪些問題。
參考答案:
內(nèi)存管理準(zhǔn)則:誰強(qiáng)引用過炕柔,誰就在不再使用時(shí)使引用計(jì)數(shù)減一酌泰。
對(duì)于內(nèi)存的使用和優(yōu)化常見的有以下方面:
- 重用問題:如UITableViewCells、UICollectionViewCells匕累、UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier陵刹,充分重用。
- 盡量把views設(shè)置為不透明:當(dāng)opque為NO的時(shí)候哩罪,圖層的半透明取決于圖片和其本身合成的圖層為結(jié)果授霸,可提高性能。
- 不要使用太復(fù)雜的XIB/Storyboard:載入時(shí)就會(huì)將XIB/storyboard需要的所有資源际插,包括圖片全部載入內(nèi)存碘耳,即使未來很久才會(huì)使用。那些相比純代碼寫的延遲加載框弛,性能及內(nèi)存就差了很多辛辨。
- 選擇正確的數(shù)據(jù)結(jié)構(gòu):學(xué)會(huì)選擇對(duì)業(yè)務(wù)場(chǎng)景最合適的數(shù)組結(jié)構(gòu)是寫出高效代碼的基礎(chǔ)。比如瑟枫,數(shù)組: 有序的一組值斗搞。使用索引來查詢很快,使用值查詢很慢慷妙,插入/刪除很慢僻焚。字典: 存儲(chǔ)鍵值對(duì),用鍵來查找比較快膝擂。集合: 無序的一組值虑啤,用值來查找很快隙弛,插入/刪除很快。
- gzip/zip壓縮:當(dāng)從服務(wù)端下載相關(guān)附件時(shí)狞山,可以通過gzip/zip壓縮后再下載全闷,使得內(nèi)存更小,下載速度也更快萍启。
- 延遲加載:對(duì)于不應(yīng)該使用的數(shù)據(jù)总珠,使用延遲加載方式。對(duì)于不需要馬上顯示的視圖勘纯,使用延遲加載方式局服。比如,網(wǎng)絡(luò)請(qǐng)求失敗時(shí)顯示的提示界面屡律,可能一直都不會(huì)使用到腌逢,因此應(yīng)該使用延遲加載。
- 數(shù)據(jù)緩存:對(duì)于cell的行高要緩存起來超埋,使得reload數(shù)據(jù)時(shí),效率也極高佳鳖。而對(duì)于那些網(wǎng)絡(luò)數(shù)據(jù)霍殴,不需要每次都請(qǐng)求的,應(yīng)該緩存起來系吩,可以寫入數(shù)據(jù)庫来庭,也可以通過plist文件存儲(chǔ)。
- 處理內(nèi)存警告:一般在基類統(tǒng)一處理內(nèi)存警告穿挨,將相關(guān)不用資源立即釋放掉
- 重用大開銷對(duì)象:一些objects的初始化很慢月弛,比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它們科盛。通常是作為屬性存儲(chǔ)起來帽衙,防止反復(fù)創(chuàng)建。
- 避免反復(fù)處理數(shù)據(jù):許多應(yīng)用需要從服務(wù)器加載功能所需的常為JSON或者XML格式的數(shù)據(jù)贞绵。在服務(wù)器端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要厉萝。
- 使用Autorelease Pool:在某些循環(huán)創(chuàng)建臨時(shí)變量處理數(shù)據(jù)時(shí),自動(dòng)釋放池以保證能及時(shí)釋放內(nèi)存榨崩。
- 正確選擇圖片加載方式:詳情閱讀細(xì)讀UIImage加載方式
9谴垫、plist文件是用來做什么的。一般用它來處理一些什么方面的問題母蛛。
參考答案:
plist是iOS系統(tǒng)中特有的文件格式翩剪。我們常用的NSUserDefaults偏好設(shè)置實(shí)質(zhì)上就是plist文件操作。plist文件是用來持久化存儲(chǔ)數(shù)據(jù)的彩郊。
我們通常使用它來存儲(chǔ)偏好設(shè)置前弯,以及那些少量的蚪缀、數(shù)組結(jié)構(gòu)比較復(fù)雜的不適合存儲(chǔ)數(shù)據(jù)庫的數(shù)據(jù)。比如博杖,我們要存儲(chǔ)全國城市名稱和id椿胯,那么我們要優(yōu)先選擇plist直接持久化存儲(chǔ),因?yàn)楦?jiǎn)單剃根。
10哩盲、iOS中緩存一定量的數(shù)據(jù)以便下次可以快速執(zhí)行,那么數(shù)據(jù)會(huì)存儲(chǔ)在什么地方狈醉,有多少種存儲(chǔ)方式廉油?
參考答案:
- 偏好設(shè)置(NSUserDefaults)
- plist文件存儲(chǔ)
- 歸檔
- SQLite3
- Core Data
詳情請(qǐng)閱讀:iOS常用的持久化存儲(chǔ)方式
11、請(qǐng)簡(jiǎn)單寫出增苗傅、刪抒线、改、查的SQL語句渣慕。
參考答案:
數(shù)據(jù)庫的簡(jiǎn)單操作嘶炭,還是會(huì)的,大學(xué)可沒白學(xué)逊桦。
增:
insert into tb_blogs(name, url) values('aaa','http://www.baidu.com');
刪:
delete from tb_blogs where blogid = 1;
改:
update tb_blogs set url = 'www.baidu.com' where blogid = 1;
查:
select name, url from tb_blogs where blogid = 1;
12眨猎、在提交蘋果審核時(shí),遇到哪些問題被拒絕强经,對(duì)于被拒絕的問題是如何處理的睡陪。
參考答案:
·····
13、請(qǐng)寫出有多少有方法給UIImageView添加圓角匿情?
1.最直接的方法就是使用如下屬性設(shè)置:
imgView.layer.cornerRadius = 10;
// 這一行代碼是很消耗性能的
imgView.clipsToBounds = YES;
好處是使用簡(jiǎn)單兰迫,操作方便。壞處是離屏渲染(off-screen-rendering)需要消耗性能炬称。對(duì)于圖片比較多的視圖上汁果,不建議使用這種方法來設(shè)置圓角。通常來說转砖,計(jì)算機(jī)系統(tǒng)中CPU须鼎、GPU、顯示器是協(xié)同工作的府蔗。CPU計(jì)算好顯示內(nèi)容提交到GPU晋控,GPU渲染完成后將渲染結(jié)果放入幀緩沖區(qū)。
簡(jiǎn)單來說姓赤,離屏渲染赡译,導(dǎo)致本該GPU干的活,結(jié)果交給了CPU來干不铆,而CPU又不擅長(zhǎng)GPU干的活蝌焚,于是拖慢了UI層的FPS(數(shù)據(jù)幀率)裹唆,并且離屏需要?jiǎng)?chuàng)建新的緩沖區(qū)和上下文切換,因此消耗較大的性能只洒。
2.給UIImage添加生成圓角圖片的擴(kuò)展API:
- (UIImage *)hyb_imageWithCornerRadius:(CGFloat)radius {
CGRect rect = (CGRect){0.f, 0.f, self.size};
UIGraphicsBeginImageContextWithOptions(self.size, NO, UIScreen.mainScreen.scale);
CGContextAddPath(UIGraphicsGetCurrentContext(),
[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius].CGPath);
CGContextClip(UIGraphicsGetCurrentContext());
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
//然后調(diào)用時(shí)就直接傳一個(gè)圓角來處理:
gView.image = [[UIImage imageNamed:@"test"] hyb_imageWithCornerRadius:4];
這么做就是on-screen-rendering了许帐,通過模擬器->debug->Color Off-screen-rendering看到?jīng)]有離屏渲染了!(黃色的小圓角沒有顯示了,說明這個(gè)不是離屏渲染了)
3.在畫之前先通過UIBezierPath添加裁剪毕谴,但是這種不實(shí)用:
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip];
[self.image drawInRect:bounds];
}
通過mask遮罩實(shí)現(xiàn)
這里不細(xì)說了成畦,個(gè)人感覺不如第二種好用、通用
14.請(qǐng)描述事件響應(yīng)者鏈的工作原理
參考答案:
iOS使用hit-testing尋找觸摸的view涝开。 Hit-Testing通過檢查觸摸點(diǎn)是否在關(guān)聯(lián)的view邊界內(nèi)循帐,如果在,則遞歸地檢查該view的所有子view舀武。在層級(jí)上處于lowest(就是離用戶最近的view)且邊界范圍包含觸摸點(diǎn)的view成為hit-test view拄养。確定hit-test view后,它傳遞觸摸事件給該view银舱。
官方小例子事件響應(yīng)者鏈如下圖所示:
- 觸摸點(diǎn)在view A中瘪匿,所以要先檢查子view B和C。
- 觸摸點(diǎn)不在view B中寻馏,但在C中柿顶,所以檢查C的子view D和E。
- 觸摸點(diǎn)不在D中操软,但在E中。View E是這個(gè)層級(jí)上處于lowest的view的邊界范圍包含觸摸點(diǎn)宪祥,所以它成為了hit-test view聂薪。
Hit-test view是處理觸摸事件的第一選擇,如果hit-test view不能處理事件蝗羊,該事件將從事件響應(yīng)鏈中尋找響應(yīng)器藏澳,直到系統(tǒng)找到一個(gè)處理事件的對(duì)象。若不能處理耀找,則就有事件傳遞鏈了翔悠,繼續(xù)看下面的事件傳遞鏈。
事件傳遞鏈如下圖所示:
左半圖:
- initial view若不能處理事件野芒,則傳到其父視圖view
- view若不能處理蓄愁,則傳到其父視圖,因?yàn)樗€不是最上層視圖
- 這里view的父視圖是view controller的view狞悲,因?yàn)檫@個(gè)view也不能處理事件撮抓,因此傳給view controller
- 若view controller也不能處理此事件,則傳到window
- 若window也不能處理此事件摇锋,則傳到app單例對(duì)象Application
- 若UIApplication單例對(duì)象也不能處理丹拯,則表示無效事件
右半圖:
- initial view一直傳遞直到最上層view(原話:A view passes an event up its view controller’s view hierarchy until it reaches the topmost view.)
- topmost view傳遞事件到它所在的控制器(原話:The topmost view passes the event to its view controller.)
- view controller傳遞事件到topmost view的父視圖站超,重復(fù)前三步,走到到達(dá)root controller(原話:passes the event to its topmost view’s superview. Steps 1-3 repeat until the event reaches the root view controller.)
- 由root控制器傳遞事件到window(原話:The root view controller passes the event to the window object.)
- 若window也不能處理此事件乖酬,則傳到app單例對(duì)象Application
- 若UIApplication單例對(duì)象也不能處理死相,則表示無效事件
為了解答這個(gè)小題目,翻閱了官方文檔咬像,由于內(nèi)容較多算撮,這里不說那么多,若要了解更多施掏,參考官方文檔吧:Event Handling Guide for iOS
15如何避免使用block時(shí)發(fā)生循環(huán)引用
參考答案:
關(guān)于block循環(huán)引用問題是非常常見的钮惠,但是很多人沒有深入研究過,xcode沒有提示警告就以為沒有形成循環(huán)引用了七芭。筆者也見過很多高級(jí)iOS開發(fā)工程師的同事素挽,使用block并不會(huì)分析是否形成循環(huán)引用。
16狸驳、請(qǐng)比較GCD與NSOperation的異同
參考答案:
- 相同點(diǎn):GCD和NSOperation都是蘋果提供的多線程實(shí)現(xiàn)方案预明。
- 不同點(diǎn):GCD是輕量級(jí)的純C寫的多線程實(shí)現(xiàn)方案,使用起來非常方便耙箍,在開發(fā)中大量使用撰糠,但是對(duì)于取消和暫停線程就比較麻煩些。而NSOperation是面向?qū)ο蟮谋缋ィ嫒軰VO阅酪,對(duì)于取消和暫停任務(wù)是非常容易的。
更詳細(xì)地汁针,推薦閱讀:iOS圖解多線程
17术辐、請(qǐng)寫出NSTimer使用時(shí)的注意事項(xiàng)(兩項(xiàng)即可)
說到NSTimer這個(gè)定時(shí)器類,要使用好它施无,還得了解Run Loop辉词,因?yàn)樵诓煌膔un loop mode下,定時(shí)器不都會(huì)回調(diào)的猾骡。
mode主要是用來指定事件在運(yùn)行循環(huán)中的優(yōu)先級(jí)的瑞躺,分為:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn),空閑狀態(tài)
UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)會(huì)切換到該Mode
UIInitializationRunLoopMode:run loop啟動(dòng)時(shí)兴想,會(huì)切換到該mode
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
蘋果公開提供的Mode有兩個(gè):NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
如果我們把一個(gè)NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)中的時(shí)候, ScrollView滾動(dòng)過程中會(huì)因?yàn)閙ode的切換幢哨,而導(dǎo)致NSTimer將不再被調(diào)度。當(dāng)我們滾動(dòng)的時(shí)候襟企,也希望不調(diào)度嘱么,那就應(yīng)該使用默認(rèn)模式。但是,如果希望在滾動(dòng)時(shí)曼振,定時(shí)器也要回調(diào)几迄,那就應(yīng)該使用common mode。
如果想更深入地了解RunLoop冰评,請(qǐng)參考iOS之Run Loop詳解
如果想要銷毀timer映胁,則必須先將timer置為失效,否則timer就一直占用內(nèi)存而不會(huì)釋放甲雅。造成邏輯上的內(nèi)存泄漏解孙。該泄漏不能用xcode及instruments測(cè)出來。 另外對(duì)于要求必須銷毀timer的邏輯處理抛人,未將timer置為失效弛姜,若每次都創(chuàng)建一次,則之前的不能得到釋放妖枚,則會(huì)同時(shí)存在多個(gè)timer的實(shí)例在內(nèi)存中廷臼。
參考答案:
注意timer添加到runloop時(shí)應(yīng)該設(shè)置為什么mode
注意timer在不需要時(shí),一定要調(diào)用invalidate方法使定時(shí)器失效绝页,否則得不到釋放
18荠商、說說Core Animation是如何開始和結(jié)束動(dòng)畫的
19、線程和進(jìn)程的區(qū)別不正確的是
- A 進(jìn)程和線程都是由操作系統(tǒng)所提供的程序運(yùn)行的基本單元
- B 線程之間有單獨(dú)的地址空間
- C 進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式
- D 線程有自己的堆棧和局部變量
參考答案:B
這是學(xué)習(xí)操作系統(tǒng)知識(shí)的時(shí)候經(jīng)常會(huì)考試的內(nèi)容续誉,但是在工作中經(jīng)常會(huì)遇到多線程處理問題莱没。通常來說,一個(gè)進(jìn)程就代表著一個(gè)應(yīng)用程序酷鸦,而操作系統(tǒng)為了更好的利用資源饰躲,提供了線程用于處理并發(fā)。線程之間沒有有單獨(dú)的地址空間臼隔,處理完成之后還得回到主線程属铁,所以,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉躬翁。進(jìn)程和線程都是操作系統(tǒng)的基本單元,只是分工不同盯拱,是兩種不同的資源管理方式盒发。線程所需要的資源都來自于進(jìn)程,它沒有自己獨(dú)立的資源狡逢,也就沒有自己的堆棧和局部變量宁舰。
修正:這里參考答案與描述不符合的問題。
20奢浑、堆和棧的區(qū)別正確的是
- A 對(duì)于棧來講蛮艰,我們需要手工控制,容易產(chǎn)生memory leak
- B 對(duì)于堆來說雀彼,釋放工作由編譯器自動(dòng)管理壤蚜,無需我們手工控制
- C 在Windows下,棧是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)即寡,是連續(xù)的內(nèi)存區(qū)域,棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的
- D 對(duì)于堆來講袜刷,頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù)聪富,從而造成大量的碎片,使程序效率降低
參考答案:D
棧是由編譯器管理的著蟹,不是我們手動(dòng)控制墩蔓,但是棧所能分配的內(nèi)存是比較少的,如果要處理大數(shù)據(jù)萧豆,則需要在堆上分配奸披,因此在棧上比較容易出現(xiàn)Memory Leak;
對(duì)于堆涮雷,需要我們自己申請(qǐng)內(nèi)存阵面,同時(shí)也需要我們自己手動(dòng)釋放,否則會(huì)造成內(nèi)存泄露份殿;對(duì)于堆膜钓,如果過多地申請(qǐng)內(nèi)存空間,會(huì)導(dǎo)致內(nèi)存空間不連接卿嘲,從而造成內(nèi)存碎片颂斜,使程序效率降低。
21拾枣、下列回調(diào)機(jī)制的理解不正確的是
- A target-action:當(dāng)兩個(gè)對(duì)象之間有?較緊密的關(guān)系時(shí)沃疮,如視圖控制器與其下的某個(gè)視圖。
- B delegate:當(dāng)某個(gè)對(duì)象收到多個(gè)事件梅肤,并要求同一個(gè)對(duì)象來處理所有事件時(shí)司蔬。委托機(jī)制必須依賴于某個(gè)協(xié)議定義的?法來發(fā)送消息。
- C NSNotification:當(dāng)需要多個(gè)對(duì)象或兩個(gè)無關(guān)對(duì)象處理同一個(gè)事件時(shí)姨蝴。
- D Block:適?于回調(diào)只發(fā)?生一次的簡(jiǎn)單任務(wù)俊啼。
參考答案:B
對(duì)于Target-Action機(jī)制,要求兩個(gè)對(duì)象之間有比較緊密的聯(lián)系左医,比如在控制器與cell之間授帕,可通過設(shè)置target為控制器對(duì)象,而action則為控制器中的某個(gè)回調(diào)方法浮梢;
對(duì)于Delegator機(jī)制跛十,它是蘋果提供的標(biāo)準(zhǔn)回調(diào)機(jī)制,通常會(huì)提供一個(gè)標(biāo)準(zhǔn)的協(xié)議秕硝,然后由代理類遵守協(xié)議芥映,最常用的用法是反向傳值,比如打開藍(lán)牙后要反饋給前一個(gè)界面藍(lán)牙的開關(guān)狀態(tài);
對(duì)于通知奈偏,通常是多對(duì)多的關(guān)系坞嘀,它并不關(guān)心是誰要處理消息,任意對(duì)象都可以注冊(cè)通知到通知中心霎苗,當(dāng)發(fā)送通知時(shí)姆吭,所有注冊(cè)了該通知的對(duì)象都可以收到消息。最常用的場(chǎng)景是跨模塊唁盏,比如登錄模塊與其它模塊有著非常緊密的聯(lián)系内狸,但是登錄成功后各個(gè)地方可能需要做一些處理,因此通常會(huì)在登錄成功或者登出成功后發(fā)送通知厘擂,以便各個(gè)需要處理的模塊得到正確的處理昆淡;
對(duì)于Block是相當(dāng)簡(jiǎn)單的,它只適用于一對(duì)一的關(guān)系刽严,比如在做某個(gè)操作成功或者失敗后回調(diào)昂灵。
22、對(duì)于runloop的理解不正確的是
- A 每一個(gè)線程都有其對(duì)應(yīng)的RunLoop
- B 默認(rèn)非主線程的RunLoop是沒有運(yùn)行的
- C 在一個(gè)單獨(dú)的線程中沒有必要去啟用RunLoop
- D 可以將NSTimer添加到runloop中
參考答案:C
說到RunLoop舞萄,它可是多線程的法寶眨补。通常來說,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù)倒脓,執(zhí)行完任務(wù)后就會(huì)退出線程撑螺。但是,對(duì)于主線程是不能退出的崎弃,因此我們需要讓主線程即時(shí)任務(wù)執(zhí)行完畢甘晤,也可以繼續(xù)等待接收事件而不退出,那么RunLoop就是關(guān)鍵法寶了饲做。但是非主線程通常來說就是為了執(zhí)行某一任務(wù)的线婚,執(zhí)行完畢就需要?dú)w還資源,因此默認(rèn)是不運(yùn)行RunLoop的盆均。
每一個(gè)線程都有其對(duì)應(yīng)的RunLoop的塞弊,只是默認(rèn)只有主線程的RunLoop是啟動(dòng)的,其它子線程的RunLoop默認(rèn)是不啟動(dòng)的泪姨,若要啟動(dòng)則需要手動(dòng)啟動(dòng)居砖。
在一個(gè)單獨(dú)的線程中,如果需要在處理完某個(gè)任務(wù)后不退出驴娃,繼續(xù)等待接收事件,則需要啟用RunLoop循集。
NSRunLoop提供了一個(gè)添加NSTimer的方法唇敞,可以指定Mode,如果要讓任何情況下都回調(diào),則需要設(shè)置Mode為Common模式疆柔。
實(shí)質(zhì)上咒精,對(duì)于子線程的runloop默認(rèn)是不存在的,因?yàn)樘O果采用了懶加載的方式旷档。如果我們沒有手動(dòng)調(diào)用[NSRunLoop currentRunLoop]模叙,就不會(huì)去查詢是否存在當(dāng)前線程的RunLoop,也就不會(huì)去加載鞋屈,更不會(huì)創(chuàng)建范咨。
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
關(guān)鍵加載過程如下:
1.檢查全局字典里是否存在該線程的runLoop,如果有則退出厂庇,否則
2.創(chuàng)建一個(gè)新的runLoop放到全局字典中
23渠啊、Apple用什么方式實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?
Apple 的文檔對(duì) KVO 實(shí)現(xiàn)的描述:
Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …
從Apple 的文檔可以看出:Apple 并不希望過多暴露 KVO 的實(shí)現(xiàn)細(xì)節(jié)权旷。不過替蛉,要是借助 runtime 提供的方法去深入挖掘,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露:
當(dāng)你觀察一個(gè)對(duì)象時(shí)拄氯,一個(gè)新的類會(huì)被動(dòng)態(tài)創(chuàng)建躲查。這個(gè)類繼承自該對(duì)象的原本的類,并重寫了被觀察屬性的 setter 方法译柏。重寫的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后镣煮,通知所有觀察對(duì)象:值的更改。最后通過 isa 混寫(isa-swizzling) 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類艇纺,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例怎静。我畫了一張示意圖,如下所示:
KVO 確實(shí)有點(diǎn)黑魔法:
Apple 使用了 isa 混寫(isa-swizzling)來實(shí)現(xiàn) KVO 黔衡。
下面做下詳細(xì)解釋:
鍵值觀察通知依賴于 NSObject 的兩個(gè)方法: willChangeValueForKey: 和 didChangevlueForKey: 蚓聘。在一個(gè)被觀察屬性發(fā)生改變之前, willChangeValueForKey: 一定會(huì)被調(diào)用盟劫,這就會(huì)記錄舊的值夜牡。而當(dāng)改變發(fā)生后, didChangeValueForKey: 會(huì)被調(diào)用侣签,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用塘装。可以手動(dòng)實(shí)現(xiàn)這些調(diào)用影所,但很少有人這么做蹦肴。一般我們只在希望能控制回調(diào)的調(diào)用時(shí)機(jī)時(shí)才會(huì)這么做。大部分情況下猴娩,改變通知會(huì)自動(dòng)調(diào)用阴幌。
比如調(diào)用 setNow: 時(shí)勺阐,系統(tǒng)還會(huì)以某種方式在中間插入 wilChangeValueForKey: 、 didChangeValueForKey: 和 observeValueForKeyPath:ofObject:change:context: 的調(diào)用矛双。大家可能以為這是因?yàn)?setNow: 是合成方法渊抽,有時(shí)候我們也能看到人們這么寫代碼:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"]; // 沒有必要
_now = aDate;
[self didChangeValueForKey:@"now"];// 沒有必要
}
這是完全沒有必要的代碼,不要這么做议忽,這樣的話懒闷,KVO代碼會(huì)被調(diào)用兩次。KVO在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey: 栈幸,之后總是調(diào)用 didChangeValueForkey: 愤估。怎么做到的呢?答案是通過 isa 混寫(isa-swizzling)。第一次對(duì)一個(gè)對(duì)象調(diào)用 addObserver:forKeyPath:options:context: 時(shí)侦镇,框架會(huì)創(chuàng)建這個(gè)類的新的 KVO 子類灵疮,并將被觀察對(duì)象轉(zhuǎn)換為新子類的對(duì)象。在這個(gè) KVO 特殊子類中壳繁, Cocoa 創(chuàng)建觀察屬性的 setter 震捣,大致工作原理如下:
- (void)setNow:(NSDate *)aDate {
[self willChangeValueForKey:@"now"];
[super setValue:aDate forKey:@"now"];
[self didChangeValueForKey:@"now"];
}
這種繼承和方法注入是在運(yùn)行時(shí)而不是編譯時(shí)實(shí)現(xiàn)的。這就是正確命名如此重要的原因闹炉。只有在使用KVC命名約定時(shí)蒿赢,KVO才能做到這一點(diǎn)。
KVO 在實(shí)現(xiàn)中通過 isa 混寫(isa-swizzling) 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類渣触,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例羡棵。這在Apple 的文檔可以得到印證:
Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …
然而 KVO 在實(shí)現(xiàn)中使用了 isa 混寫( isa-swizzling) ,這個(gè)的確不是很容易發(fā)現(xiàn):Apple 還重寫嗅钻、覆蓋了 -class 方法并返回原來的類皂冰。 企圖欺騙我們:這個(gè)類沒有變,就是原本那個(gè)類养篓。秃流。。
但是柳弄,假設(shè)“被監(jiān)聽的對(duì)象”的類對(duì)象是 MYClass 舶胀,有時(shí)候我們能看到對(duì) NSKVONotifying_MYClass 的引用而不是對(duì) MYClass 的引用。借此我們得以知道 Apple 使用了 isa 混寫(isa-swizzling)碧注。具體探究過程可參考這篇博文嚣伐。