1.技術基礎
1、為什麼說Objective-C是一門動态的語言?
這裡的動态和靜态是相對的,動态的意思是不需要在編譯時确定所有的東西,在運作時也可以動态添加變量,屬性,方法和類. Objective-C 可以通過Runtime這個運作時機制,在運作時動态的添加變量,方法和類等,是以說Objective-C是一門動态的語言.
Objective-C的動态性,讓程式在運作時判斷其該有的行為,而不是像c等靜态語言在編譯建構時就确定下來。它的動态性主要展現在3個方面:
1.動态類型:如id類型。實際上靜态類型因為其固定性和可預知性而使用的特别廣泛。靜态類型是強類型,動态類型是弱類型,運作時決定接收者。
2.動态綁定:讓代碼在運作時判斷需要調用什麼方法,而不是在編譯時。與其他面向對象語言一樣,方法調用和代碼并沒有在編譯時連接配接在一起,而是在消息發送時才進行連接配接。運作時決定調用哪個方法。
3.動态載入。讓程式在運作時添加代碼子產品以及其他資源。使用者可以根據需要執行一些可執行代碼和資源,而不是在啟動時就加載所有元件。可執行代碼中可以含有和程式運作時整合的新類。
2、講一下MVC和MVVM,MVP?
• Models(模型) — 資料層,或者負責處理資料的 資料接口層。比如 Person 和 PersonDataProvider 類
• Views(視圖) - 展示層(GUI)。對于 iOS 來說所有以 UI 開頭的類基本都屬于這層。
• Controller/Presenter/ViewModel(控制器/展示器/視圖模型) - 它是 Model 和 View 之間的膠水或者說是中間人。一般來說,當使用者對 View 有操作時它負責去修改相應 Model;當 Model 的值發生變化時它負責去更新對應 View。
MVC
Controller: 控制器層,它是 Model 和 View 之間的膠水或者說是中間人。一般來說,當使用者對 View 有操作時它負責去修改相應 Model;當 Model 的值發生變化時它負責去更新對應 View。
MVVM
在MVVM 中,view 和 view controller正式聯系在一起,我們把它們視為一個元件
view 和 view controller 都不能直接引用model,而是引用視圖模型(viewModel)
viewModel 是一個放置使用者輸入驗證邏輯,視圖顯示邏輯,發起網絡請求和其他代碼的地方
MVP
MVP 是從經典的模式MVC演變而來,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供資料,View負責顯示。作為一種新的模式,MVP與MVC有着一個重大的差別:在MVP中View并不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的互動都發生在Presenter内部,而在MVC中View會從直接Model中讀取資料而不是通過 Controller。
參考資料:
* https://blog.coding.net/blog/ios-architecture-patterns
* https://www.jianshu.com/p/eedbc820d40a
* http://blog.csdn.net/li_shuang_ls/article/details/50176891
* https://www.jianshu.com/p/33c7e2f3a613
3、為什麼代理要用weak?代理的delegate和dataSource有什麼差別?block和代理的差別?
1.為什麼代理要用weak?
防止循環引用。例如View有一個協定,需要一個代理實作回調。一個Controller添加這個View,并且遵守協定,成為View的代理。如果不用week,用strong,Controller ->View -> delegate -> Controller,就循環引用了。
2.代理的delegate和dataSource有什麼差別?
delegate偏重于與使用者互動的回調,有那些方法可以供我使用,例如UITableviewDelegate;dataSource偏重于資料的回調,view裡面有什麼東西,屬性都是什麼,例如UITableviewDatasource;
3.block和代理的差別?
1.block簡介
在 iOS中, block一共分三種。
(1)全局靜态 block,不會通路任何外部變量,執行完就銷毀。
^{
NSLog(@"Hello World!");
}();
- 1
- 2
- 3
(2)儲存在棧中的 block,當函數傳回時會被銷毀,和第一種的差別就是調用了外部變量。
[UIView animateWithDuration: animations:^{
self.view.backgroundColor = [UIColor redColor];
}];
- 1
- 2
- 3
- 4
(3)儲存在堆中的 block,當引用計數為 0 時會被銷毀。例如按鈕的點選事件,一直存在,即使執行過,也不銷毀,因為按鈕還可能被點選,持有按鈕的View被銷毀,它才會被銷毀。
2.block優點
block的代碼可讀性更好。因為應用block和實作block的地方在一起。代理的聲明和實作就分開來了,在兩個類中。代理使用起來也更麻煩,因為要聲明協定、聲明代理、遵守協定、實作協定裡的方法。block不需要聲明,也不需要遵守,隻需要聲明和實作就可以了。
block是一種輕量級的回調,可以直接通路上下文,由于block的代碼是内聯的,運作效率更高。block就是一個對象,實作了匿名函數的功能。是以我們可以把block當做一個成員變量、屬性、參數使用,使用起來非常靈活。像用AFNetworking請求資料和GCD實作多線程,都使用了block回調。
3.block缺點
blcok的運作成本高。block出棧需要将使用的資料從棧記憶體拷貝到堆記憶體,當然對象的話就是引用計數加1,使用完或者block置nil後才銷毀。delegate隻是儲存了一個對象指針(一定要用week修飾delegate,不然也會循環引用),直接回調,沒有額外消耗。就像C的函數指針,隻多做了一個查表動作。
block容易造成循環引用,而且不易察覺。因為為了blcok不被系統回收,是以我們都用copy關鍵字修飾,實行強引用。block對捕獲的變量也都是強引用,是以就會造成循環引用。
4.如何使用
優先使用block。
如果回調函數很多,多餘三個使用代理。
如果回調的很頻繁,次數很多,像UITableview,每次初始化、滑動、點選都會回調,使用代理。
5. 代理和Block的差別
相同點:代理和Block大多是我們都可以用來做倒序傳值的。我們都得注意避免循環引用。不然我們去使用代理還是Block的時候,都需要判斷它們是否實作
不同點:代理使用weak修飾,代理必須先聲明方法。當我們調用代理的時候要判斷是否已經實作。
block:使用的是copy來修飾,block儲存的是一段代碼,其實也就是一個函數。并且可以自動捕捉自動變量,如果想修改此自動變量,還必須使用__block修飾。
參考資料:
* https://www.jianshu.com/p/6bba9b4a25d5
4、屬性的實質是什麼?包括哪幾個部分?屬性預設的關鍵字都有哪些?@dynamic關鍵字和@synthesize關鍵字是用來做什麼的?
屬性是一個類中用來描述對象的抽象概念。
屬性包括的部分有setter和getter方法
atomic:
nonatomic:
@synthesize
@dynamic
getter=getterName
setter=setterName
readwrite
readonly
assign
retain
copy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
@synthesize
如果沒有實作setter和getter方法,編譯器将會自動在生産setter和getter方法。
@dynamic
表示變量對應的屬性通路器方法 , 是動态實 現的 , 你需要在 NSObject 中繼承而來的 +(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 動态實作的方法或者函數。
屬性修飾其他關鍵字:
參考
*http://www.wimhe.com/archives/46
*http://www.wimhe.com/archives/47
5、屬性的預設關鍵字是什麼?
預設關鍵字,基本資料: atomic,readwrite,assign
普通的 OC 對象: atomic,readwrite,strong
6、NSString為什麼要用copy關鍵字,如果用strong會有什麼問題?(注意:這裡沒有說用strong就一定不行。使用copy和strong是看情況而定的)
>
// 深複制
Person *xiaoMing = [[Person alloc] init];
NSMutableString * name = [[NSMutableStringalloc] initWithString:@"xiaoming"];
//name.string = @"xiaoming";
xiaoMing.name = name;
NSLog(@"%@", xiaoMing.name);
[name appendString:@"hah"];
//此時名字這個屬性被修改了
NSLog(@"%@", xiaoMing.name);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果用Copy來修飾name這個屬性不會改變,
如果使用Strong,當name這個字元串改變的時候,name這個屬性也會随着改變。
補充:這其實也是看需求,看被指派的字元串是否需要随着指派字元串的變化而變化,而大多數情況下我們不希望被指派的字元串如某個對象的某個字元串類型的屬性會随着指派字元串的變化而變化。 反之,如果我們希望被指派的字元串随着指派字元串的變化而變化,那麼我們也可以使用strong來修飾字元串
參考:
*https://www.jianshu.com/p/499f2927717c
7、如何令自己所寫的對象具有拷貝功能?
需實作 NSCopying 協定。如果自定義的對象分為可變版本與不可變版本,那麼就要同時實作 NSCopying與 NSMutableCopying協定。
具體步驟:
需聲明該類遵從 NSCopying 協定
實作 NSCopying 協定。該協定隻有一個方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實真正需要實作的卻是 “copyWithZone” 方法。
至于如何重寫帶 copy 關鍵字的 setter這個問題,
如果抛開本例來回答的話,如下:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
- 1
- 2
- 3
- 4
參考:
*https://dayon.gitbooks.io/-ios/content/chapter5.html
8、可變集合類 和 不可變集合類的 copy 和 mutablecopy有什麼差別?如果是集合是内容複制的話,集合裡面的元素也是内容複制麼?
使用copy時 可變集合的指針位址以及記憶體位址都不相同 深複制 不可變集合的指針位址不一樣但是記憶體位址一樣 屬于淺複制
使用mutableCopy的時候無論是可變集合還是不可變集合的指針位址和記憶體位址都不同 都屬于深複制
- (void)testCopy {
NSMutableArray *mutableArray = [NSMutableArray arrayWithObject:@"mutableArray"];
NSArray *array = [NSArray arrayWithObject:@"array"];
id copy_mutableArray = mutableArray.copy;
id copy_array = array.copy;
id mutableCopy_mutableArray = mutableArray.mutableCopy;
id mutableCopy_array = array.mutableCopy;
NSLog(@"mutableArray:%@ ,記憶體位址%p -- 指針位址%p",mutableArray,mutableArray,&mutableArray);
NSLog(@"array:%@ ,記憶體位址%p -- 指針位址%p",array,array,&array);
NSLog(@"copy_mutableArray:%@ ,記憶體位址%p -- 指針位址%p",copy_mutableArray,copy_mutableArray,©_mutableArray);
NSLog(@"copy_array:%@ ,記憶體位址%p -- 指針位址%p",copy_array,copy_array,©_array);
NSLog(@"mutableCopy_mutableArray:%@ ,記憶體位址%p -- 指針位址%p",mutableCopy_mutableArray,mutableCopy_mutableArray,&mutableCopy_mutableArray);
NSLog(@"mutableCopy_array:%@ ,記憶體位址%p -- 指針位址%p",mutableCopy_array,mutableCopy_array,&mutableCopy_array);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
列印輸出:
2018-02-28 08:12:02.322754+0800 XWBlogsDemos[6646:275862] mutableArray:(
mutableArray
) ,記憶體位址0x60400025ec90 -- 指針位址0x7ffee2eb0b48
2018-02-28 08:12:02.322913+0800 XWBlogsDemos[6646:275862] array:(
array
) ,記憶體位址0x604000019010 -- 指針位址0x7ffee2eb0b40
2018-02-28 08:12:02.323038+0800 XWBlogsDemos[6646:275862] copy_mutableArray:(
mutableArray
) ,記憶體位址0x604000019090 -- 指針位址0x7ffee2eb0b38
2018-02-28 08:12:02.323140+0800 XWBlogsDemos[6646:275862] copy_array:(
array
) ,記憶體位址0x604000019010 -- 指針位址0x7ffee2eb0b30
2018-02-28 08:12:02.323236+0800 XWBlogsDemos[6646:275862] mutableCopy_mutableArray:(
mutableArray
) ,記憶體位址0x60400025ef90 -- 指針位址0x7ffee2eb0b28
2018-02-28 08:12:02.323333+0800 XWBlogsDemos[6646:275862] mutableCopy_array:(
array
) ,記憶體位址0x60400025ee70 -- 指針位址0x7ffee2eb0b20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
9、為什麼IBOutlet修飾的UIView也适用weak關鍵字?
在xib或者Sb拖控件時,其實控件就加載到了父控件的subviews數組裡面,進行了強引用,使用weak,避免循環引用
10、nonatomic和atomic的差別?atomic是絕對的線程安全麼?為什麼?如果不是,那應該如何實作?
nonatomic:表示非原子,不安全,但是效率高。
atomic:表示原子行,安全,但是效率定。
atomic,不能絕對保證線程的安全,當多線程同時通路的時候,會造成線程不安全。可以使用線程鎖來保證線程的安全。
11、UICollectionView自定義layout如何實作?
實作一個自定義layout的正常做法是繼承UICollectionViewLayout類,然後重載下列方法:
-(CGSize)collectionViewContentSize
傳回collectionView的内容的尺寸
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
傳回rect中的所有的元素的布局屬性
傳回的是包含UICollectionViewLayoutAttributes的NSArray
UICollectionViewLayoutAttributes可以是cell,追加視圖或裝飾視 圖的資訊,通過不同的UICollectionViewLayoutAttributes初始化方法可以得到不同類型的UICollectionViewLayoutAttributes:
layoutAttributesForCellWithIndexPath:
layoutAttributesForSupplementaryViewOfKind:withIndexPath:
layoutAttributesForDecorationViewOfKind:withIndexPath:
-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
傳回對應于indexPath的位置的cell的布局屬性
-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
傳回對應于indexPath的位置的追加視圖的布局屬性,如果沒有追加視圖可不重載
-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
傳回對應于indexPath的位置的裝飾視圖的布局屬性,如果沒有裝飾視圖可不重載
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
當邊界發生改變時,是否應該重新整理布局。如果YES則在邊界變化(一般是scroll到其他地方)時,将重新計算需要的布局資訊。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
12、用StoryBoard開發界面有什麼弊端?如何避免?
使用簡單邏輯頁面的跳轉是可以使用sb的,開發比較塊。
但是SB對于邏輯項目比較複雜的時候,開發起來比較慢。不适合多人合作開發;也不利于版本的梗系和後期的維護。使用sb在項目變異編譯的時候,也都會直接加載到記憶體中,造成記憶體的浪費。
可以使用xib來代替,編輯複雜邏輯界面時候可以使用純碼編寫。
13、程序和線程的差別?同步異步的差別?并行和并發的差別?
程序:是具有一定獨立功能的程式關于某個資料集合上的一次運作活動,程序是系統進行資源配置設定和排程的一個獨立機關.
線程:是程序的一個實體,是CPU排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關.線程自己基本上不擁有系統資源,隻擁有一點在運作中必不可少的資源(如程式計數器,一組寄存器和棧),但是它可與同屬一個程序的其他的線程共享程序所擁有的全部資源.
同步:阻塞目前線程操作,不能開辟線程。
異步:不阻礙線程繼續操作,可以開辟線程來執行任務。
并發:當有多個線程在操作時,如果系統隻有一個CPU,則它根本不可能真正同時進行一個以上的線程,它隻能把CPU運作時間劃分成若幹個時間段,再将時間 段配置設定給各個線程執行,在一個時間段的線程代碼運作時,其它線程處于挂起狀。.這種方式我們稱之為并發(Concurrent)。
并行:當系統有一個以上CPU時,則線程的操作有可能非并發。當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行,這種方式我們稱之為并行(Parallel)。
差別:并發和并行是即相似又有差別的兩個概念,并行是指兩個或者多個事件在同一時刻發生;而并發是指兩個或多個事件在同一時間間隔内發生。在多道程式環境下,并發性是指在一段時間内宏觀上有多個程式在同時運作,但在單處理機系統中,每一時刻卻僅能有一道程式執行,故微觀上這些程式隻能是分時地交替執行。倘若在計算機系統中有多個處理機,則這些可以并發執行的程式便可被配置設定到多個處理機上,實作并行執行,即利用每個處理機來處理一個可并發執行的程式,這樣,多個程式便可以同時執行。
14、線程間通信?
當使用dispath-async函數開辟線程執行任務的完成時,我們需要使用dispatch_async(dispatch_get_main_queue(), ^{ });函數會到主線程内重新整理UI。并完成通信
15、GCD的一些常用的函數?(group,barrier,信号量,線程同步)
>
group:
我們使用隊列組來開辟線程時,隊列組中的隊列任務是并發,當所有的隊列組中的所有任務完成時候,才可以調用隊列組完成任務。
/**建立自己的隊列*/
dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next", DISPATCH_QUEUE_CONCURRENT);
/**建立一個隊列組*/
dispatch_group_t dispatchGroup = dispatch_group_create();
/**将隊列任務添加到隊列組中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dispatch-1");
});
/**将隊列任務添加到隊列組中*/
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dspatch-2");
});
/**隊列組完成調用函數*/
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
>
barrier:
表示栅欄,當在并發隊列裡面使用栅欄時候,栅欄之前的并發任務開始并發執行,執行完畢後,執行栅欄内的任務,等栅欄任務執行完畢後,再并發執行栅欄後的任務。
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-2");
});
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"dispatch-4");
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
>
信号量:
Semaphore是通過‘計數’的方式來辨別線程是否是等待或繼續執行的。信号量
dispatch_semaphore_create(int) // 建立一個信号,并初始化信号的計數大小
/* 等待信号,并且判斷信号量,如果信号量計數大于等于你建立時候的信号量的計數,就可以通過,繼續執行,并且将你傳入的信号計數減1,
* 如果傳入的信号計數小于你建立的計數,就表示等待,等待信号計數的變化
* 如果等待的時間超過你傳入的時間,也會繼續下面操作
* 第一個參數:semaphore 表示信号量
* 第二個參數:表示等待的時間
* 傳回int 如果傳入的信号計數大于等于你建立信号的計數時候,傳回0. 反之,傳回的不等于0
*/
int result = dispatch_semaphore_wait(dispatch_semaphore_t semaphore,time outTime);// 表示等待,也是阻礙線程
// 表示将信号技術+1
dispatch_semaphore_signl(dispatch_semaphore_t semaphore);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- (void)semaphoreDemo1 {
dispatch_semaphore_t semaphore = dispatch_semaphore_create();
for (int i = ; i < ; i++) {
由于是異步執行的,是以每次循環Block裡面的dispatch_semaphore_signal根本還沒有執行就會執行dispatch_semaphore_wait,進而semaphore-1.當循環此後,semaphore等于,則會阻塞線程,直到執行了Block的dispatch_semaphore_signal 才會繼續執行
NSLog(@"i %zd",i);
/// 執行十次之後阻塞目前線程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
- (void)semaphoreDemo2 {
dispatch_semaphore_t goOnSemaphore = dispatch_semaphore_create();
NSLog(@"ready");
[self network:^(id result) {
NSLog(@"net return:%@",result);
dispatch_semaphore_signal(goOnSemaphore);
}];
dispatch_semaphore_wait(goOnSemaphore, DISPATCH_TIME_FOREVER);
NSLog(@"go on");
}
- (void)network:(void(^)(id result))block {
sleep();
block(@(arc4random_uniform()));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
實作線程的同步的方法:
串行隊列,分組,信号量。也是可以使用并發隊列。
//加入隊列
dispatch_async(concurrentQueue, ^{
//1.先去網上下載下傳圖檔
dispatch_sync(concurrentQueue, ^{
});
//2.在主線程展示到界面裡
dispatch_sync(dispatch_get_main_queue(), ^{
});
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
16、如何使用隊列來避免資源搶奪?
使用線程鎖。也是可以使用串行隊列來完成。如:fmdb就是使用FMDatabaseQueue,來解決多線程搶奪資源。
17、資料持久化的幾個方案(fmdb用沒用過)
持久化方案:
plist,存儲字典,數組比較好用
preference:偏好設定,實質也是plist
NSKeyedArchiver:歸檔,可以存儲對象
sqlite:資料庫,經常使用第三方來操作,也就是fmdb
coreData:也是資料庫儲存,蘋果官方的
18、說一下AppDelegate的幾個方法?從背景到前台調用了哪些方法?第一次啟動調用了哪些方法?從前台到背景調用了哪些方法?
19、NSCache優于NSDictionary的幾點?
1.nscache 是可以自動釋放記憶體的。
2.nscache是線程安全的,我們可以在不同的線程中添加,删除和查詢緩存中的對象。
3.一個緩存對象不會拷貝key對象。
20、知不知道Designated Initializer?使用它的時候有什麼需要注意的問題?
初始化器. 建立子類時需要調用父類的的初始化器,并且需要重寫父類的Designated Initializer,将其指向子類新的初始化器
//Designated Initializer
- (instancetype)initWithFrame:(CGRect)frame andName:(NSString *)name{
//incorrect
if (self = [super initWithFrame:frame]){
self.name=name;
}
return self;
}
//super override
- (id)initWithFrame:(CGRect)frame
{
return [self initWithFrame:frame andName:@""];
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
參考:
* https://www.jianshu.com/p/57db46f013d7
* http://www.cnblogs.com/smileEvday/p/designated_initializer.html
21、實作description方法能取到什麼效果?
description是nsobject的一個執行個體的方法,傳回的是一個nsstring。當我們使用nslog列印的時候,列印出來的一般都是對象的記憶體位址,如果我們實作description方法時,我們就可以使用nslog列印對象的時候,我們可以把它裡面的屬性值和記憶體位址一起列印出來.列印什麼,就是看你寫什麼了。
22、objc使用什麼機制管理對象記憶體?
引用計數. 當記憶體管理計數器為0的時候,對象會被釋放
中級Block
1、block的實質是什麼?一共有幾種block?都是什麼情況下生成的?
>
block的實質是什麼?
Block實質上就是Objective-C對象;
一共有幾種block?
block一共有3種類型的block
_NSConcreteGlobalBlock 全局靜态
_NSConcreteStackBlock 儲存在棧中,出函數作用域就銷毀
_NSConcreteMallocBlock 儲存在堆中,retainCount == 0銷毀而ARC和MRC中,還略有不同;
參考:
* https://www.jianshu.com/p/bb163c433f7c
* https://www.jianshu.com/p/dfdb1b379ea2
2、為什麼在預設情況下無法修改被block捕獲的變量? __block都做了什麼?
Block隻捕獲Block中會用到的變量。由于隻捕獲了自動變量(自動變量是以值傳遞方式傳遞到Block的構造函數裡面)的值,并非記憶體位址,是以Block内部不能改變自動變量的值。Block捕獲的外部變量可以改變值的是靜态變量,靜态全局變量,全局變量
參考:
*https://halfrost.com/ios_block/?utm_source=tuicool&utm_medium=referral
3、模拟一下循環引用的一個情況?blok實作界面反向傳值如何實作?
>
模拟一下循環引用的一個情況?
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",student.name);
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
循環引用問題集合
blok實作界面反向傳值如何實作?
1,定義一個block屬性 2,一級界面實作block需要執行的操作 3,在需要回調的時候調用block
參考
*http://blog.csdn.net/x32sky/article/details/73826662
Runtime
1、objc在向一個對象發送消息時,發生了什麼?
objc是動态語言,每個方法在運作時會被動态轉為消息發送,即:objc_msgSend(receiver, selector)。
根據對象的isa指針找到類對象id,在查詢類對象裡面的methodLists方法函數清單,如果沒有找到,在沿着superClass,尋找父類,再在父類methodLists方法清單裡面查詢,最終找到SEL,根據id和SEL确認IMP(指針函數),在發送消息;
2、什麼時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
簡單來說:
當調用該對象上某個方法,而該對象上沒有實作這個方法的時候, 可以通過“消息轉發”進行解決。
簡單的流程如下,在上一題中也提到過:
objc是動态語言,每個方法在運作時會被動态轉為消息發送,即:objc_msgSend(receiver, selector)。
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然後在該類中的方法清單以及其父類方法清單中尋找方法運作,如果,在最頂層的父類中依然找不到相應的方法時,程式在運作時會挂掉并抛出異常unrecognized selector sent to XXX 。但是在這之前,objc的運作時會給出三次拯救程式崩潰的機會:
1. Method resolution
objc運作時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實作。如果你添加了函數,那運作時系統就會重新啟動一次消息發送的過程,否則 ,運作時就會移到下一步,消息轉發(Message Forwarding)。
2. Fast forwarding
如果目标對象實作了-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會。 隻要這個方法傳回的不是nil和self,整個消息發送的過程就會被重新開機,當然發送的對象會變成你傳回的那個對象。否則,就會繼續Normal Fowarding。 這裡叫Fast,隻是為了差別下一步的轉發機制。因為這一步不會建立任何新的對象,但下一步轉發會建立一個NSInvocation對象,是以相對更快點。
3. Normal forwarding
這一步是Runtime最後一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息獲得函數的參數和傳回值類型。如果-methodSignatureForSelector:傳回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程式這時也就挂掉了。如果傳回了一個函數簽名,Runtime就會建立一個NSInvocation對象并發送-forwardInvocation:消息給目标對象。
#import "XWTest.h"
#import <objc/runtime.h>
@interface XWTest2 : NSObject
- (void)unrecognized;
@end
@implementation XWTest2
- (void)unrecognized {
NSLog(@"unrecognized");
}
@end
@implementation XWTest
void myMethod(id self, SEL _cmd) {
NSLog(@"myMethod 被調用!");
}
/// 第一次
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@" %s",__func__);
if ([NSStringFromSelector(sel) isEqualToString:@"unrecognized"]) {
class_addMethod([self class], sel, (IMP)myMethod,"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
/// 第二次
-(id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorName = NSStringFromSelector(aSelector);
if ([selectorName isEqualToString:@"unrecognized"]) {
XWTest2 *myobject = [[XWTest2 alloc] init];
return myobject;
}
return [super forwardingTargetForSelector:aSelector];
}
/// 第三次
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if([XWTest2 instancesRespondToSelector:aSelector])
{
signature = [XWTest2 instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([XWTest2 instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[XWTest2 alloc] init]];
}
}
@end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
參考:
* https://www.jianshu.com/p/1c8f708653c0
3、能否向編譯後得到的類中增加執行個體變量?能否向運作時建立的類中添加執行個體變量?為什麼?
不能向編譯後得到的類中增加執行個體變量;
能向運作時建立的類中添加執行個體變量;
解釋下:
因為編譯後的類已經注冊在 runtime 中,類結構體中的 objc_ivar_list 執行個體變量的連結清單 和 instance_size 執行個體變量的記憶體大小已經确定,同時runtime 會調用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用。是以不能向存在的類中添加執行個體變量;
運作時建立的類是可以添加執行個體變量,調用 class_addIvar 函數。但是得在調用 objc_allocateClassPair 之後,objc_registerClassPair 之前,原因同上。
4、runtime如何實作weak變量的自動置nil?
runtime 對注冊的類, 會進行布局,對于 weak 對象會放入一個 hash 表中。 用 weak 指向的對象記憶體位址作為 key,當此對象的引用計數為0的時候會 dealloc, 在這個 weak 表中搜尋,找到所有以a為鍵的 weak 對象,進而設定為 nil。
weak 修飾的指針預設值是 nil (在Objective-C中向nil發送消息是安全的)
5、給類添加一個屬性後,在類結構體裡哪些元素會發生變化?
instance_size :執行個體的記憶體大小
objc_ivar_list *ivars:屬性清單
RunLoop
1.runloop是來做什麼的?runloop和線程有什麼關系?主線程預設開啟了runloop麼?子線程呢?
runloop:字面意思就是跑圈,其實也就是一個循環跑圈,用來處理線程裡面的事件和消息。
runloop和線程的關系:每個線程如果想繼續運作,不被釋放,就必須有一個runloop來不停的跑圈,以來處理線程裡面的各個事件和消息。
主線程預設是開啟一個runloop。也就是這個runloop才能保證我們程式正常的運作。子線程是預設沒有開始runloop的
2.runloop的mode是用來做什麼的?有幾種mode?
model:是runloop裡面的模式,不同的模式下的runloop處理的事件和消息有一定的差别。
系統預設注冊了5個Mode:
(1)kCFRunLoopDefaultMode: App的預設 Mode,通常主線程是在這個 Mode 下運作的。
(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響。
(3)UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成後就不再使用。
(4)GSEventReceiveRunLoopMode: 接受系統事件的内部 Mode,通常用不到。
(5)kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用。
注意iOS 對以上5中model進行了封裝
NSDefaultRunLoopMode;
NSRunLoopCommonModes
3.為什麼把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運作循環以後,滑動scrollview的時候NSTimer卻不動了?
nstime對象是在 NSDefaultRunLoopMode下面調用消息的,但是當我們滑動scrollview的時候,NSDefaultRunLoopMode模式就自動切換到UITrackingRunLoopMode模式下面,卻不可以繼續響應nstime發送的消息。是以如果想在滑動scrollview的情況下面還調用nstime的消息,我們可以把nsrunloop的模式更改為NSRunLoopCommonModes
4.蘋果是如何實作Autorelease Pool的?
Autorelease Pool作用:緩存池,可以避免我們經常寫relase的一種方式。其實就是延遲release,将建立的對象,添加到最近的autoreleasePool中,等到autoreleasePool作用域結束的時候,會将裡面所有的對象的引用計數器-1.
autorelease
參考:
* https://www.jianshu.com/p/74bb8e1f7036
類結構
1、isa指針?(對象的isa,類對象的isa,元類的isa都要說)
對象的isa指針指向所屬的類
類的isa指針指向了所屬的元類
元類的isa指向了根元類,根元類指向了自己。
參考:
*http://www.zhimengzhe.com/IOSkaifa/253119.html
2、類方法和執行個體方法有什麼差別?
調用的方式不同,類方法必須使用類調用,在方法裡面不能調用屬性,類方法裡面也必須調用類方法。存儲在元類結構體裡面的methodLists裡面
執行個體方法必須使用執行個體對象調用,可以在執行個體方法裡面使用屬性,執行個體方法也必須調用執行個體方法。存儲在類結構體裡面的methodLists裡面
參考:
* http://blog.csdn.net/youshaoduo/article/details/55253041
3、介紹一下分類,能用分類做什麼?内部是如何實作的?它為什麼會覆寫掉原來的方法?
類别(Category)主要有3個作用:
将類的實作分散到多個不同檔案或多個不同架構中。
建立對私有方法的前向引用。
向對象添加非正式協定。
聲明:@interface 類名(分類名稱) @end
實作:@implementation 類名(分類名稱) @end
注意:
(1)在分類隻能增加方法,不能增加成員變量,如果要增加成員變量的話該考慮用繼承去實作
(2)在分類實作方法中可以通路類中的成員變量但是不能通路類中的屬性@property
(3)在分類中可以重新實作原類中的方法,但會将原類中的方法覆寫而失效。
(4)如果一個類有多個分類,而且分類中有同名的方法那麼最後編譯的分類會将前面編譯的分類覆寫而執行輸出
因為在執行對象成員方法的時候會優先去分類中查找,然後再去原類中去查找,最後去父類中去查找
// 另外一份解釋來自《招聘一個靠譜的 iOS》—參考答案(上)---24
// 類方法:
- 類方法是屬于類對象的
- 類方法隻能通過類對象調用
- 類方法中的self是類對象
- 類方法可以調用其他的類方法
- 類方法中不能通路成員變量
- 類方法中不能直接調用對象方法
// 執行個體方法:
- 執行個體方法是屬于執行個體對象的
- 執行個體方法隻能通過執行個體對象調用
- 執行個體方法中的self是執行個體對象
- 執行個體方法中可以通路成員變量
- 執行個體方法中直接調用執行個體方法
- 執行個體方法中也可以調用類方法(通過類名)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
參考:
* https://tech.meituan.com/DiveIntoCategory.html
4、運作時能增加成員變量麼?能增加屬性麼?如果能,如何增加?如果不能,為什麼?
Category中不能動态添加成員變量;
在Objective-C提供的runtime函數中,确實有一個class_addIvar()函數用于給類添加成員變量,但是閱讀過蘋果的官方文檔的人應該會看到:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
大概的意思說,這個函數隻能在“建構一個類的過程中”調用。一旦完成類定義,就不能再添加成員變量了。經過編譯的類在程式啟動後就被runtime加載,沒有機會調用addIvar。程式在運作時動态建構的類需要在調用objc_registerClassPair之後才可以被使用,同樣沒有機會再添加成員變量。
因為方法和屬性并不“屬于”類執行個體,而成員變量“屬于”類執行個體。我們所說的“類執行個體”概念,指的是一塊記憶體區域,包含了isa指針和所有的成員變量。是以假如允許動态修改類成員變量布局,已經建立出的類執行個體就不符合類定義了,變成了無效對象。但方法定義是在objc_class中管理的,不管如何增删類方法,都不影響類執行個體的記憶體布局,已經建立出的類執行個體仍然可正常使用。
然而如果在運作時動态生成一個類,就可以為其添加成員變量和方法, 如下圖所示:
添加的方法必須是已經實作的,是以先手寫這個方法
5、objc中向一個nil對象發送消息将會發生什麼?(傳回值是對象,是标量,結構體)
objc中向一個nil對象發送消息将會發生什麼?
參考:
* http://blog.csdn.net/x32sky/article/details/73826662
* https://www.jianshu.com/p/19f280afcb24
進階
1、UITableview的優化方法(緩存高度,異步繪制,減少層級,hide,避免離屏渲染)
緩存高度:當我們建立frame模型的時候,計算出來cell的高度的時候,我們可以将cell的高度緩存到字典裡面,以cell的indexpath和Identifier作為為key。
NSString *key = [[HeightCache shareHeightCache] makeKeyWithIdentifier:@"YwywProductGradeCell" indexPath:indexPath];
if ([[HeightCache shareHeightCache] existInCacheByKey:key]) {
return [[HeightCache shareHeightCache] heightFromCacheWithKey:key];
}else{
YwywProductGradeModelFrame *modelFrame = self.gradeArray[indexPath.row];
[[HeightCache shareHeightCache] cacheHieght:modelFrame.cellHight key:key];
return modelFrame.cellHight;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
參考:
* https://www.jianshu.com/p/93085c0de4c9
2、有沒有用過運作時,用它都能做什麼?(交換方法,建立類,給新建立的類增加方法,改變isa指針)
>
交換方式:一般寫在類的+(void)load方法裡面
Method originalM = class_getInstanceMethod([self class], @selector(originMethod));
Method exchangeM = class_getInstanceMethod([self class], @selector(exChangeMethod));
method_exchangeImplementations(originalM, exchangeM);
- 1
- 2
- 3
>
建立類:
/// 建立類
- (void)creatClassMethod {
Class Person = objc_allocateClassPair([NSObject class], "Person", );
//添加屬性
objc_property_attribute_t type = { "T", "@\"NSString\"" };
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t backingivar = { "V", "_privateName" };
objc_property_attribute_t attrs[] = { type, ownership, backingivar };
class_addProperty(Person, "name", attrs, );
//添加方法
class_addMethod(Person, @selector(name), (IMP)nameGetter, "@@:");
class_addMethod(Person, @selector(setName:), (IMP)nameSetter, "v@:@");
//注冊該類
objc_registerClassPair(Person);
//擷取執行個體
id instance = [[Person alloc] init];
NSLog(@"%@", instance);
[instance setName:@"hxn"];
NSLog(@"%@", [instance name]);
}
//get方法
NSString *nameGetter(id self, SEL _cmd) {
Ivar ivar = class_getInstanceVariable([self class], "_privateName");
return object_getIvar(self, ivar);
}
//set方法
void nameSetter(id self, SEL _cmd, NSString *newName) {
Ivar ivar = class_getInstanceVariable([self class], "_privateName");
id oldName = object_getIvar(self, ivar);
if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
>
添加方法
/**參數一、類名參數
二、SEL 添加的方法名字參數
三、IMP指針 (IMP就是Implementation的縮寫,它是指向一個方法實作的指針,每一個方法都有一個對應的IMP)
參數四、其中types參數為"i@:@“,按順序分别表示:具體類型可參照[官方文檔](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)i 傳回值類型int,若是v則表示void@ 參數id(self): SEL(_cmd)@ id(str)
V@:表示傳回值是void 帶有SEL參數 (An object (whether statically typed or typed id))
*/
class_addMethod(Person, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
>
添加執行個體變量
/**參數一、類名參數
二、屬性名稱參數
三、開辟位元組長度參數
四、對其方式參數
五、參數類型 “@” 官方解釋 An object (whether statically typed or typed id) (對象 靜态類型或者id類型) 具體類型可參照[官方文檔](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)return: BOOL 是否添加成功
*/
BOOL isSuccess = class_addIvar(Person, "name", sizeof(NSString *), , "@");
isSuccess?NSLog(@"添加變量成功"):NSLog(@"添加變量失敗");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
3、看過哪些第三方架構的源碼?都是如何實作的?(如果沒有,問一下多圖下載下傳的設計)
iOS常用開源架構、技術部落格、軟體、插件等
* SDWebImage *
iOS之SDWebImage的實作原理
* AFNetworking*
AFNetworking到底做了什麼?
* MBProcessHUD*
MBProcessHUD-分析、模仿與學習
* Masonry *
iOS自動布局架構-Masonry詳解
* MJRefresh*
iOS 李明傑 MJRefresh源碼解析
* YYKit *
第三方架構學習—YYKit
* FMDB*
iOS資料庫第三方架構FMDB詳細講解
詳解 iOS 多圖下載下傳的緩存機制
4、SDWebImage的緩存政策?
sd加載一張圖檔的時候,會先在記憶體裡面查找是否有這張圖檔,如果沒有會根據圖檔的md5(url)後的名稱去沙盒裡面去尋找,是否有這張圖檔,如果沒有會開辟線程去下載下傳,下載下傳完畢後加載到imageview上面,并md(url)為名稱緩存到沙盒裡面。
附:(代碼解讀)
SDWebImage緩存機制
5、AFN為什麼添加一條常駐線程?
如果沒有常住線程的話,就會每次請求網絡就去開辟線程,完成之後銷毀開辟線程,這樣就造成資源的浪費,而開辟一條常駐線程,就可以避免這種浪費,我們可以在每次的網絡請求都添加到這條線程。
6、KVO的使用?實作原理?(為什麼要建立子類來實作)
iOS開發 – KVO的實作原理與具體應用
建立子類
每個對象都有isa 指針,指向該對象的類,它告訴 Runtime 系統這個對象的類是什麼。是以對象注冊為觀察者時,isa指針指向新子類,那麼這個被觀察的對象就神奇地變成新子類的對象(或執行個體)了。
7、KVC的使用?實作原理?(KVC拿到key以後,是如何指派的?知不知道集合操作符,能不能通路私有屬性,能不能直接通路_ivar)
iOS開發技巧系列—詳解KVC(我告訴你KVC的一切)