MVC是一種架構模式,使用此架構模式的好處在於:業務邏輯、數據、視圖的分離。做到高內聚低耦合
摘自《Cocoa Design Patterns》:
MVC的主要目的是解除模型子系統和視圖之間的耦合,這樣它們就可以獨立變化。
摘自《設計模式》:
M-Model-模型 V-View-視圖MVC通過建立一個”訂購/通知”協議來分離視圖和模型。視圖必須保證它的顯示正確地反映了模型的狀態。一旦模型的數據發生變化,模型將通知有關的視圖,每個模型的數據發生變化,模型將通知有關的視圖,每個視圖相應地得到刷新自己的機會。這種方法可以讓你為一個模型提供不同的多個視圖表現形式,也能夠為一個模型創建新的視圖而無須重寫模型。
C-Controller-控制器
Controller與Model之間,Controller一側是虛線,代表Controller可以直接訪問Model;Model一側是白色實線,代表不能直接訪問Controller,但模型發生改變,Controller想要知道,這個時候可以采用觀察者模式,間接地去通知Controller
Controller與View之間,Controller一側是虛線,代表Controller可以直接訪問View(如果有Storyboard,需要通過Outlet訪問);View一側是白色實線,代表不能直接訪問Controller。如果View接收到用戶交互時,可以通過目標選擇器(Target、Selector/Action)進行回調Controller;如果View需要向Controller獲取數據(cell個數、給你一個indexPath,你給我一個我要顯示的Cell、TextField的文字發生變化,TextField接收到了一個”Return”按鍵)時,通過Delegate或DataSource向Controller回調,注:此圖當中少了一個block!
View與Model之間,是一條黃色雙實線,代表兩者之間互相禁止訪問或間接回調
典型案例:
Controller向Model當中取數據,顯示到View(它的子類也可以,例如UIButton)上,button添加一個目標選擇器,當button按下時,向Controller進行回調,Controller再去更新Model,當Model發生變化時,Controller會收到觀察者消息或通知消息,再去更新View顯示樣式
延伸面試題:什麼是高內聚低耦合?
延伸面試題:iOS中常用的設計模式?
延伸面試題:如果簡歷當中或自我介紹當中,或談話過程當中,出現MVVM,會提問兩者的區別。有些面試者會回答MVC架構模式當中的Controller層,會顯得越來越臃腫,而MVVM則不會。此種回答會扣分,因為MVC架構模式只有當模塊的職責劃分不明確,才會造成Controller的臃腫,也就意味著:面試者的技術能力不足。所以MVVM這一概念一定要真的用過才能回答!
陷阱:MVC是一種架構模式,注意不是設計模式
陷阱:Notification和KVO有什麼區別?在什麼情況下使用Notification,在什麼情況下使用KVO?
回答思路:
NSThread NSOperation延伸面試題:NSOperation與GCD的區別
延伸面試題:NSOperation被發送了cancel方法,會立即地停止嗎?
延伸面試題:如果會立即停止,那還有什麼意義呢?
不會立即停止,只是向NSOperation對象發送了一個”我要取消你”的消息。NSOperation內部需要在合適的時機,判斷自己是否接收到”取消”這種消息,做一些掃尾的操作,再自己把自己停止掉。
這種機制類似於用戶雙擊了Home鍵,將App劃出了屏幕,殺掉了這個App,但是App還是會在被殺掉之前接收到-(void)applicationWillTerminate消息,做數據的保存工作
工廠模式
單例模式
觀察者模式
適配器模式(即delegate)
原型模式(即copy)
延伸面試題1:工廠模式與單例模式的區別
延伸面試題2:觀察者模式與適配器模式的區別
延伸面試題3:KVC和KVO之間的聯系
延伸面試題4:KVO和NSNotificationCenter的區別
延伸面試題5:單例模式怎樣實現
陷阱:一些面試者會回答MVC、KVO、KVC、Notification,注意MVC是架構模式,KVO和Notification是觀察者模式在iOS當中的實現方式,KVC是鍵值觀察編碼,也不是設計模式。另,設計模式是在所有的程序語言全部適用的,既然全部適用,KVO也會在Java當中可以使用嗎?
方式1:
+ (instancetype)sharedInstance {
static id sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClient = [[self alloc] init];
});
return sharedClient;
}
方式2:
+ (instancetype)sharedInstance {
static id sharedClient = nil;
@synchronized (self) {
sharedClient = [[self alloc] init];
}
return sharedClient;
}
方式3:
+ (instancetype)sharedInstance {
static id sharedClient = nil;
if (sharedClient == nil) {
sharedClient = [[self alloc] init];
}
return sharedClient;
}
內聚就是一個模塊內各個元素彼此結合的緊密程度,高內聚就是一個模塊內各個元素彼此結合的緊密程度高。
所謂高內聚是指一個軟件模塊是由相關性很強的代碼組成,只負責一項任務,也就是常說的單一責任原則。
耦合:一個軟件結構內不同模塊之間互連程度的度量(耦合性也叫塊間聯系。指軟件系統結構中各模塊間相互聯系緊密程度的一種度量。模塊之間聯系越緊密,其耦合性就越強,模塊的獨立性則越差,模塊間耦合的高低取決於模塊間接口的復雜性,調用的方式以及傳遞的信息。)
對於低耦合,粗淺的理解是:一個完整的系統,模塊與模塊之間,盡可能的使其獨立存在。也就是說,讓每個模塊,盡可能的獨立完成某個特定的子功能。模塊與模塊之間的接口,盡量的少而簡單。如果某兩個模塊間的關系比較復雜的話,最好首先考慮進一步的模塊劃分。這樣有利於修改和組合。
說得比較文绉绉的,我們來接點地氣
比如一個ViewController,它又要負責UITableView的delegate、dataSource,又要負責UITextField的delegate,又要負責網絡請求,又要負責網絡的響應及Model的更新….這種設計,就是內聚性較差的體現。高內聚這個概念,與”面向對象設計的五大原則”(SOLID)當中的單一職責原則(SRP)意義相近,即”一個類,只干一件事”
耦合又是什麼?即是模塊與模塊之間的依賴。舉之前的例子,在ViewController當中,又要依賴View,又要依賴AFNetworking,又要依賴Model…這種設計,就是耦合性較高的體現。低耦合這個概念,與”面向對象設計的五大原則”(SOLID)當中的依賴倒置原則(DIP)與接口隔離原則(SIP)意義相近。
兩者的共同點:都是觀察者模式
兩者的不同點:KVO是鍵值觀察,NSNotificationCenter是通知中心。在適用場景上,KVO必須有一個鍵,它才能觀察值的變化。而NSNotificationCenter不需要
如果要觀察的內容是跟模型相關的,使用KVO。反之,則使用NSNotificationCenter
陷阱:一些面試者會回答Notification是一對一,KVO是多對多
進程是一個“執行中的程序”,在iOS編程中,是不支持多進程設計的。比如在Mac系統下,登錄了一個QQ號,再按下快捷鍵Command+N,又可以再登錄一個QQ號,這時,QQ這個應用程序就開啟了兩個進程。但在iOS當中不能夠雙開。
在一個進程當中,可以有多個線程,但至少有一個主線程在一直運行,它們可以利用所在進程所擁有的資源。比如多個線程都可以去操作Model…
多進程設計中,一個進程crash,不會影響其他的進程。
多線程設計中,一個線程crash,會影響其他的線程。
如果進程crash了,它的所有線程也會crash。
延伸面試題:在iOS當中怎樣設計線程?
延伸面試題:線程同步問題
延伸面試題:SQLite與CoreData的區別?
延伸面試題:CoreData與MagicalRecord的區別?
延伸面試題:如果在簡歷當中或項目描述當中體現了FMDB或SQLite,可能會問到多表查詢的SQL語句。
延伸面試題:CoreData如何做表結構升級?
延伸面試題:CoreData使用的過程當中,有沒有遇到過死鎖,如何解決?
延伸面試題:使用數據庫有何心得?
陷阱:如果涉及到多表查詢的SQL,恰巧面試者不知道。會給面試官留下之前項目太簡單的印象,進而對面試者的技術能力產生懷疑
CoreData即是對SQLite的封裝,在CoreData當中,操作數據庫的代碼即是操作對象的代碼
MagicalRecord是對CoreData的封裝,使用起來比CoreData更簡單。並且其內部考慮到了多線程操作數據庫產生死鎖的問題
操作數據庫不要過於頻繁,一般情況下在程序啟動時,將數據庫當中的所有數據,讀入到Model當中;程序關閉時,將Model當中的所有數據,存儲到數據庫當中。並且引入MagicalRecord
多線程同步,即是排隊效果,類似於交通路口的信號燈,後邊的車要直行,但是前面的車在等待左轉信號燈,後面的燈要等到前面的車走了之後才能繼續走。不然的話就會繼續等待
多線程異步,即是”並發執行”。多用於會造成阻塞的任務,如:網絡請求如果使用同步,主線程會卡死,頁面不再刷新,不再響應用戶交互…如果使用異步,主線程繼續處理頁面刷新,響應用戶交互,而另外一個線程則等待網絡響應
多線程同步可采用以下方式:
dispatch_semaphore
原子操作OSAtomic
鎖NSLock
事件NSCondition
延伸面試題:屬性關鍵字當中的nonatomic與atomic有什麼區別?
堆和棧的概念請自行百度,一般情況下不會出現錯誤。在此僅說明它們之間的區別。
例如以下代碼:
- (void)method {
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
NSInteger inta = 0;
NSInteger intb = 0;
}
堆區內容:
按照內存管理的法則,有alloc,就必須要有release。但此段代碼沒有做release並不是真的不需要release,而是ARC在函數結尾自動去release了。其中obj1和obj2兩個對象被存放到了堆區,堆區的內容需要程序員自己手動去分配(alloc)和釋放(release)
棧區內容:
由編譯器自動分配釋放,inta和intb兩個整型數值肯定是要有一個內存區域去存儲的,這一塊內存不需要程序員去分配,也不需要程序員去釋放
沒有多重繼承,OC只能夠單繼承。如果想要做多重繼承的話,可以使用協議(protocol)代替
延伸面試題:因為涉及到多重繼承,所以如果簡歷裡如果體現出其他的編程語言,可能會問到(很多面試者會在簡歷裡提到C++、Java,這時面試極有可能問到多重繼承、虛函數、純虛函數、抽象類之類的概念)
如有下列代碼:
NSString *str = [[NSData alloc] init];
str = [str stringByAppendingString:@"abc"]
會不會順利通過編譯?如果編譯通過,運行過程當中會發生什麼?
答:會順利通過編譯,但編譯器會報警告。在編譯時,str是NSString類型,但在運行時,會創建一個NSData對象,並將str動態綁定成NSData,既然str實際上是一個NSData對象,而NSData對象又沒有實現”stringByAppeningString”方法,所以會發生閃退。
#import和#include都是導入頭文件,但#import在編譯時能夠保證”只導入一次”,但#include做不到。所以如果沒有#import關鍵字,只能使用#include關鍵字,可以在被導入的頭文件當中添加如下代碼:
#ifndef _HEADER_H // 此處的_HEADER_H應該根據實際的頭文件名取得
#define _HEADER_H
// 實際內容
#endif
@class 是為了防止交叉編譯,一般優秀的設計,會盡量避免兩個頭文件相互導入,但有時確實避免不了,兩個頭文件相互導入,編譯器就會報錯。比如:
Teacher.h:
#import "Student.h"
@interface Teacher : NSObject
@property (strong, nonatomic) NSMutableArray *students;
@end
Student.h:
#import "Teacher.h"
@interface Student : NSobject
@property (strong, nonatomic) NSMutableArray *teachers;
@end
此時@class關鍵字就可以派上用場了,將頭文件當中的#import剪切替換到.m文件當中,並在頭文件當中的相同位置,添加@class關鍵字就可解決
如果是C語言基礎類型,使用assign(CGFloat, long, int, short, NSInteger, NSUInteger)
如果是代理,使用weak(MRC使用assign)
如果強引用了,需要將一個strong改成weak;
如果對象需要保存一份副本,不想別人的修改了,自己也跟著修改,那麼使用copy
剩下的,都使用strong(MRC當中使用retain)
原子操作:線程安全,相當於在getter和settger兩個函數當中,添加了線程鎖
nonatomic
非原子操作-非線程安全
atomic
原子操作-線程安全
self.imageView.money = money;
strong、retain
strong是在ARC當中用的,retain是在MRC當中用的
引用計數自動+1
- (void)setImage:(UIImage *)image {
if (_image != image) {
[_image release];
[image retain];
_image = image;
}
}
- (void)dealloc {
[_image release];
}
assign
- (void)setUnknown:(Unknown )var {
_var = var;
}
weak
大體上跟assign一樣,不一樣的:如果在指向的對象被銷毀了,那麼指針會指向nil
copy
- (void)setString:(NSString *)string {
if (_string != string) {
[_string release]
_string = [string copy];
}
}
readonly
在別的類使用的過程當中,不能夠調用setter方法
在自己的類中,如果要使用,可以在.m文件當中,自己再寫一個匿名類別,寫上與.h文中相同的property內容(刪掉readonly關鍵字),就可以使用了
readwrite
unsafe
safe
問:對於其他的類,屬性是只讀的,但對於自己的類,是可讀可寫的,如何做?
答:直接上代碼
Template.h:
@inteface Template : NSObject
@property (strong, nonatomic, readonly) NSObject *prop;
@end`
Template.m:
@interface Template()
@property (strong, nonatomic) NSObject *prop;
@end
@implementation Template
// ...
@end
+[UIImage imageFromView:]
響應者鏈表示一系列的響應者對象。事件被交由第一響應者對象處理,如果第一響應者不處理,事件被沿著響應者鏈向上傳遞,交給下一個響應者(next responder)。一般來說,第一響應者是個視力對象或者其子類對象,當其被觸摸後事件被交由它處理,如果它不處理,事件就會被傳遞給它的視力控制器對象(如果存在),然後是它的父視圖(superview)對象(如果存在),以此類推,走到頂層視圖。接下來會沿著頂層視圖(top view)到窗口(UIWindow對象)再到程序(UIApplication對象)。如果整個過程都沒有響應這個事件,該事件就被丟棄。一般情況下,在響應者鏈中只要由對象處理事件,事件就停止傳遞。但有時候可以在視圖的響應方法中根據一些條件判斷來判斷是否需要繼續傳遞事件。
const int *cnp = 0;
int const *ncp = 0;
const * int cpn = 0;
int * const npc = 0;
int const * const ncpc = 0;
const int * const cnpc = 0;
以上6行分別是什麼意思?
類擴展(Extension)是匿名的類別(Category),即括號當中沒有任何字符
繼承是以子類化的方式,對原有類進行擴展
類別是在原有類之上,對其進行擴展
答:類別
理論上不可以,但實際上可以通過runtime方法添加。屬性即是一個settger方法和一個getter方法。所以在頭文件當中,直接添加屬性聲明就可以:
ExtensionObject.h:
#import
@interface NSObject(ExtensionObject)
@property (retain, nonatomic) NSObject *extensionProp;
@end
但是在setter和getter方法中,必須要與一個成員變量相關聯,因為類別不能夠添加成員變量。所以只能曲線救國了:
ExtensionObject.m:
#import "ExtensionObject.h"
#import
static const void *extensionPropKey = &extensionPropKey;
@implementation NSObject(CustomAnimate)
- (NSObject *)extensionProp {
return objc_getAssociatedObject(self, extensionPropKey);
}
- (void)setExtensionProp:(NSObject *)extensionProp{
objc_setAssociatedObject(self, extensionPropKey, extensionProp, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
// ARC版本
+ (instancetype)factory {
id factory = [[self alloc] init];
return factory;
}
// MRC版本
+ (instancetype)factory {
id factory = [[self alloc] init];
return [factory autorelease];
}
基本原則:誰使用(alloc, retain, copy),誰釋放(release、autorelease)。
心得:一律使用self.prop_name
方式,不使用成員變量方式。可以避免掉絕大多數內存洩露問題。
淺復制: 只復制對象本身,不對裡面的屬性復制。
深復制:不僅復制對象本身,對象持有的屬性對象與做復制。
被復制的類,需要實現NSCopying協議
Student.h:
#import
@class Score;
@interface Student : NSObject
@property (strong, nonatomic) NSMutableArray *score;
@end
Student.m:
#import "Student.h"
#import "Score.h"
@implementation Student
- (id)copyWithZone:(NSZone *)zone {
Student *result = [[Student allocWithZone:zone] init];
result.score = [self.score copy]; // 屬性也要復制
return result;
}
@end
Score.h:
#import
@interface Score : NSObject
@property (copy, nonatomic) NSString *subjectName;
@property (assign, nonatomic) NSInteger score;
@end
Score.m:
#import "Score.h"
@implementation Score
- (id)copyWithZone:(NSZone *)zone {
Score *result = [[Score allocWithZone:zone] init];
result.subjectName = [self.subjectName copy];
result.score = self.score;
return result;
}
@end
淺復制版本
Student.h:
#import
@class Score;
@interface Student : NSObject
@property (strong, nonatomic) NSMutableArray *score;
@end
Student.m:
#import "Student.h"
#import "Score.h"
@implementation Student
- (id)copyWithZone:(NSZone *)zone {
Student *result = [[Student allocWithZone:zone] init];
result.score = self.score; // 此處不同
return result;
}
@end
Score.h:
#import
@interface Score : NSObject
@property (copy, nonatomic) NSString *subjectName;
@property (assign, nonatomic) NSInteger score;
@end
Score.m:
#import "Score.h"
@implementation Score
@end
會有內存洩露的,
1 如果不使用property自動生成的settger和getter方法,而使用成員變量。一樣會出現內存洩露問題
2 如果涉及到對象復用(UITableViewCell或UICollectionViewCell),添加了觀察者,就可能同時觀察多個被觀察者。當Model刷新時會錯亂
延伸面試題:C++中想要做動態綁定,怎麼做?
@interface Template()
@property (strong, nonatomic) NSObject *prop;
@end
@implementation Template
- (void)method {
// 解釋一下兩行的區別
self.prop = [[NSObject alloc] init];
_prop = [[NSObject alloc] init];
}
@end
答:
self.prop = [[NSObject alloc] init];
會調用編譯器自動生成的setter方法,其內部會考慮到引用計數;
而_prop = [[NSObject alloc] init];
不會調用編譯器自動 生成的setter方法,不會考慮到引用計數,所以有可能會產生內存洩露