面試題總結(附答案)
1、UI佈局 約束相關
1.1 layoutSubviews在以下情況下會被呼叫:
1、init初始化不會觸發layoutSubviews
但是是用initWithFrame 進行初始化時,當rect的值不為CGRectZero時,也會觸發
2、addSubview會觸發layoutSubviews
3、設定view的Frame會觸發layoutSubviews,當然前提是frame的值設定前後發生了變化
4、滾動一個UIScrollView會觸發layoutSubviews
5、旋轉Screen會觸發父UIView上的layoutSubviews事件
6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
layoutSubviews
繼承於UIView的子類重寫,進行佈局更新,重新整理檢視。如果某個檢視自身的bounds或者子檢視的bounds發生改變,那麼這個方法會在當前runloop結束的時候被呼叫。為什麼不是立即呼叫呢?因為渲染畢竟比較消耗效能,特別是檢視層級複雜的時候。這種機制下任何UI控制元件佈局上的變動不會立即生效,而是每次間隔一個週期,所有UI控制元件在佈局上的變動統一生效並且在檢視上更新,蘋果通過這種高效能的機制保障了檢視渲染的流暢性。
-
setNeedsLayout
標記為需要重新佈局,非同步呼叫layoutIfNeeded重新整理佈局,不立即重新整理,在下一輪runloop結束前重新整理,對於這一輪runloop之內的所有佈局和UI上的更新只會重新整理一次,layoutSubviews一定會被呼叫。 -
layoutIfNeeded
如果有需要重新整理的標記,立即呼叫layoutSubviews進行佈局(如果沒有標記,不會呼叫layoutSubviews)。
關鍵點
- layoutIfNeeded不一定會呼叫layoutSubviews方法。
- setNeedsLayout一定會呼叫layoutSubviews方法(有延遲,在下一輪runloop結束前)。
- 如果想在當前runloop中立即重新整理,呼叫順序應該是
[self setNeedsLayout]; [self layoutIfNeeded];
1、setNeedsUpdateConstraints
當一個自定義view的某個屬性發生改變,並且可能影響到constraint時,需要呼叫此方法去標記constraints需要在未來的某個點更新,系統然後呼叫updateConstraints.
2、needsUpdateConstraints
constraint-based layout system使用此返回值去決定是否需要呼叫updateConstraints作為正常佈局過程的一部分。
3、updateConstraintsIfNeeded
立即觸發約束更新,自動更新佈局。
4、updateConstraints
自定義view應該重寫此方法在其中建立constraints. 注意:要在實現在最後呼叫[super updateConstraints]
1.2 masonry 基本使用
ofollow,noindex">https://www.jianshu.com/p/a24dd8638d28
1.3 cell 自適應大小
http://www.cocoachina.com/ios/20171212/21504.html
核心: 在cell 中 給lable新增約束,在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 中 返回cell高度,高度cell 中計算:獲取子控制元件的fram ,根據子控制元件fram 計算cell高度
注‘Android技術交流群752016839,歡迎大家加入交流,暢談!本群有免費學習資料視訊’
CGRect frame = cell.titleLabel.frame;
return frame.origin.y + frame.size.height + 10;
棄用:
self.tableView.estimatedRowHeight = 666;
self.tableView.rowHeight = UITableViewAutomaticDimension;
但是這個方法實際上在有多個子檢視的cell上滑動是很卡頓的,特別是在iOS8尤其是iOS10上卡頓尤為明顯,這跟系統的算高機制有一定關係,具體可以看上面的文章(http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/ ),這裡不再解釋了。
1.4 sizeToFit 和 sizeThatFits的使用區別
https://www.jianshu.com/p/c9ce5e195a07
2 記憶體管理 (待解決)
1.1自己生成的物件,自己所持有
非自己生成的物件,自己也能持有
不再需要自己持有的物件時釋放
非自己持有的物件無法釋放
3 runtime
1 動態建立類、並新增屬性和方法
2 已經存在的類,能否新增屬性、方法;如何修改屬性和方法
1.1 iOS_Runtime修改變數值,交換方法實現,動態新增類,成員變數和方法
https://blog.csdn.net/qq_27325349/article/details/52043120
class_addMethod的詳解
https://blog.csdn.net/lvmaker/article/details/323961671.2 能否想編譯後的類中新增例項變數,能否像執行時建立的類新增例項變數?
https://blog.csdn.net/hou_manager/article/details/79376656
1.3 resolveInstanceMethod和resolveClassMethod
https://www.jianshu.com/p/b2c6313fda98
使用methodSignatureForSelector與forwardInvocation實現訊息轉發(一起使用)
forwardingTargetForSelector
- (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"崩潰 0");
//if (sel == @selector(eat)) {
//
//// 動態新增eat方法
//
//
//
//// 第一個引數:給哪個類新增方法
//
//// 第二個引數:新增方法的方法編號
//
//// 第三個引數:新增方法的函式實現(函式地址)
//
//// 第四個引數:函式的型別,(返回值+引數型別) v:void @:物件->self :表示SEL->_cmd
//
//class_addMethod(self, @selector(eat), (IMP)eat, "v@:");
//return yes;
//}
//return [super resolveInstanceMethod:sel];
}
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
-
(id)forwardingTargetForSelector:(SEL)aSelector
{
NSLog(@"崩潰 1");
//if(aSelector == @selector(eat)){
//return vc;
//}
return [super forwardingTargetForSelector:aSelector];
}
-
(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSLog(@"崩潰 2");
return nil; //不能返回nil
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if ( !signature ) {
signature = [vc methodSignatureForSelector:aSelector];
}
return signature;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if(aSelector == @selector(testMethod))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-
(void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"崩潰 3");
//if ( [vc respondsToSelector:[anInvocation selector]] ) {
//[anInvocation invokeWithTarget:vc];
//} else {
//[super forwardInvocation:anInvocation];
//}
}
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
pragma mark - 捕捉崩潰日誌
void UncaughtExceptionHandler(NSException *exception) {
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *report = [NSString stringWithFormat:@"crash name: %@ \ncrash reason: %@", name, reason];
NSLog(@"%@~~%@",report,exception);
}
1.4 runtime 獲取類的例項方法和類方法
https://www.jianshu.com/p/b30c2580d977
1.5 isa
isa:是一個Class 型別的指標. 每個例項物件有個isa的指標,他指向物件的類,而Class裡也有個isa的指標, 指向meteClass(元類)。元類儲存了類方法的列表。當類方法被呼叫時,先會從本身查詢類方法的實現,如果沒有,元類會向他父類查詢該方法。同時注意的是:元類(meteClass)也是類,它也是物件。元類也有isa指標,它的isa指標最終指向的是一個根元類(root meteClass).根元類的isa指標指向本身,這樣形成了一個封閉的內迴圈。
super_class:父類,如果該類已經是最頂層的根類,那麼它為NULL。
isa 指標指向
例項物件的isa指標指向該例項物件的類,類指向該類元類,元類有點特殊,統一指向根元類,根元類指向自己。
1.6runtime之類方法和例項方法
https://www.jianshu.com/p/187ff251f344
~~4 記憶體分配
http://www.cocoachina.com/ios/20161009/17700.html
~~5 演算法
指標指向該例項物件的類
5.1 氣泡排序
oc:
NSMutableArray *arr_M = [NSMutableArray arrayWithObjects:@1,@4,@2,@3,@5,nil];
for (int i=0; i<arr_M.count; i++) {
for (int j=i+1; j<arr_M.count; j++) {
if (arr_M[i]<arr_M[j]) {
[arr_M exchangeObjectAtIndex:i withObjectAtIndex:j];
} } }
swift:
func bubbleSort(inout array: [Int]) {
if array.count > 1 {
for var i = 0; i < array.count; i++ { for var j = array.count - 1; j > i; j-- { if array[j] < array[j-1] { swap(&array, index1: j, index2: j - 1) } } } } }
5.2NSPredicate
~~ 6 響應者鏈條
https://www.jianshu.com/p/77a1b6e5194d
~~7 字串 copy 與strong 區別
比如以下程式碼:
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];
[string appendString:@"origion!"]
檢視記憶體,會發現 string、stringCopy 記憶體地址都不一樣,說明此時都是做內容拷貝、深拷貝。即使你進行如下操作:
[string appendString:@"origion!"]
stringCopy的值也不會因此改變,但是如果不使用copy,stringCopy的值就會被改變。 集合類物件以此類推。
~~8 執行緒與佇列
https://www.jianshu.com/p/a28c5bbd5b4a( 詳細)
序列佇列:任務按順序執行
並行佇列:任務不按順序
同步:在當前執行緒執行,不會開闢新的執行緒(按照程式碼順序,當前執行緒執行到此,當前執行緒便要執行~~~先執行block)
非同步:可能會開闢新的執行緒(看實際需要,後執行block)
當前為主執行緒:(比如viewdiload:)
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"2~~~%@",[NSThread currentThread]);
}//可行(自己猜想,相當於在主執行緒中直接列印)
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3~~~%@",[NSThread currentThread]);
}//不可行
dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(serialQueue, ^{
NSLog(@"1~~%@ ~~%@",[NSThread currentThread],[NSDate date]);
dispatch_sync(dispatch_get_main_queue(), ^{//阻塞的是子執行緒,當前執行緒是子執行緒,block內為主執行緒執行
NSLog(@"2~~%@ ~~%@",[NSThread currentThread],[NSDate date]);
});//等 主佇列中的其他任務處理完,再用主執行緒處理這個任務
NSLog(@"3~~%@ ~~%@",[NSThread currentThread],[NSDate date]);
});//可行 同步阻塞的是當前執行緒
列印結果:
1~~~<NSThread: 0x604000271a40>{number = 3, name = (null)}~~~Wed Nov 14 19:00:21 2018
2018-11-14 19:00:21.129314+0800 kkkkk[46715:1938117] viewWillAppear
2018-11-14 19:00:21.137304+0800 kkkkk[46715:1938117] viewDidAppear
2018-11-14 19:00:21.138455+0800 kkkkk[46715:1938117] 2~~~<NSThread: 0x604000075bc0>{number = 1, name = main}~~~Wed Nov 14 19:00:21 2018
2018-11-14 19:00:21.139365+0800 kkkkk[46715:1938188] 3~~~<NSThread: 0x604000271a40>{number = 3, name = (null)}~~~Wed Nov 14 19:00:21 2018
在子執行緒,呼叫同步主佇列,可行
在主執行緒,呼叫同步主佇列,不可行()
~~當前佇列只負責自己佇列中的事物,不管其他佇列中的事物
[NSThread sleepForTimeInterval:2 ];//當前執行緒休眠
- (void)sleepUntilDate:(NSDate *)date;//休眠到指定時間
dispatch_set_target_queue 改變當前佇列為目標佇列
//優先順序變更的序列佇列,初始是預設優先順序
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.setTargetQueue.serialQueue", NULL);
//優先順序不變的序列佇列(參照),初始是預設優先順序
dispatch_queue_t serialDefaultQueue = dispatch_queue_create("com.gcd.setTargetQueue.serialDefaultQueue", NULL);
//變更前
dispatch_async(serialQueue, ^{ NSLog(@"1"); });
dispatch_async(serialDefaultQueue, ^{ NSLog(@"2"); });
//獲取優先順序為後臺優先順序的全域性佇列 dispatch_queue_t globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//變更優先順序 dispatch_set_target_queue(serialQueue, globalDefaultQueue);
//變更後 dispatch_async(serialQueue, ^{ NSLog(@"1"); });
dispatch_async(serialDefaultQueue, ^{ NSLog(@"2"); });
dispatch_after
https://www.jianshu.com/p/cfcc0c302621我們看到他就是在主執行緒,就是剛好延遲了2秒,當然,我說這個2秒並不是絕對的,為什麼這麼說?還記得我之前在介紹dispatch_async這個特性的時候提到的嗎?他的block中方法的執行會放在主執行緒runloop之後,所以,如果此時runloop週期較長的時候,可能會有一些時差產生
dispatch_group
dispatch_barrier_async
dispatch_apply
dispatch_suspend & dispatch_resume
dispatch_semaphore(訊號量)的理解及使用
https://www.cnblogs.com/yajunLi/p/6274282.html
https://www.cnblogs.com/chims-liu-touch/p/5798708.html
dispatch_once和dispatch_apply
https://www.cnblogs.com/XYQ-208910/p/4859761.htmlNSOperation
https://www.jianshu.com/p/4b1d77054b35
~~9網路相關
iOS之HTTP和HTTPS的基本知識和應用
https://www.cnblogs.com/xiaopin/p/6428941.htmlget 與post
建議:提交使用者的隱私資料一定要使用POST請求
相對POST請求而言,GET請求的所有引數都直接暴露在URL中,請求的URL一般會記錄在伺服器的訪問日誌中,而伺服器的訪問日誌是黑客攻擊的重點物件之一
使用者的隱私資料如登入密碼,銀行賬號等。
~~10 kvc 與kvo
-
(void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
[self setValue:@"" forKey:@”age”];
} else {
[super setNilValueForKey:key];
}
}
-
(void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"id"]) {
self.userId = [value integerValue];
}
}
kvo
http://www.cocoachina.com/ios/20180319/22651.html
https://www.jianshu.com/p/bf053a28accb( 背)
手動呼叫kvo
-
(void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
-
(BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
~~11 資料庫
注‘Android技術交流群752016839,歡迎大家加入交流,暢談!本群有免費學習資料視訊’
-(instancetype)init{
if (self = [super init]) {
NSString * docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString * dbPathLocal = [docPath stringByAppendingPathComponent:@"xingyunSanBox.db"];
NSError *error;
NSString *filenameAgo = [[NSBundle mainBundle] pathForResource:@"xingyun" ofType:@"db" ];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager copyItemAtPath:filenameAgo toPath:dbPathLocal error:&error];
FMDatabaseQueue * localqueue = [FMDatabaseQueue databaseQueueWithPath:dbPathLocal];
self.localqueue = localqueue;
//if (!localqueue){
//NSLog(@"ERR: 建立資料庫失敗");
//
//}else{
//NSLog(@"ERR: 建立資料庫成功");
//
//}
//QBLog(@"資料庫地址:%@",dbPathLocal);
//__unsafe_unretained typeof(self) weakSelf = self;
//[localqueue inDatabase:^(FMDatabase *db) {
//
//[weakSelf createNewTableWithDB:db];
//
//}];
}
return self;
}
-(void)createNewTableWithDB:(FMDatabase *)db {
BOOL success1 = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_City(xycode TEXT,nation_code TEXT,name TEXT,short_name TEXT,short_spell TEXT,english_name TEXT,first_letter TEXT,air_code TEXT,train_code TEXT,post_code TEXT,taxi BOOL,long_bus BOOL,tags TEXT,enabled BOOL)"];
if (!success1) QBLog(@"ERR: 建立表 t_City 失敗");
BOOL success2 = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_Location(id TEXT,catalog TEXT,code TEXT,name TEXT,xycode TEXT,lng TEXT,lat TEXT,enabled BOOL)"];
if (!success2) QBLog(@"ERR: 建立表 t_Location 失敗");
}
查詢
[self.localqueue inDatabase:^(FMDatabase *db) {
NSString * sqlStr = [NSString stringWithFormat:@"SELECT * FROM hotel_geo WHERE city_name LIKE '%%%@%%'",cityName];
FMResultSet * resultSet =[db executeQuery:sqlStr];
NSMutableArray * resultMutArr = [[NSMutableArray alloc]init];
while ([resultSet next]) {
NSMutableDictionary * dataDicM = [[NSMutableDictionary alloc]init];
NSString *channel_id = [resultSet stringForColumn:@"channel_id"];
NSString *Country = [resultSet stringForColumn:@"Country"];
NSString *province_id = [resultSet stringForColumn:@"province_id"];
NSString *province_name = [resultSet stringForColumn:@"province_name"];
NSString *city_name = [resultSet stringForColumn:@"city_name"];
NSString *city_code = [resultSet stringForColumn:@"city_code"];
[dataDicM setObject:[QBTools noNullString:channel_id] forKey:@"channel_id"];
[dataDicM setObject:[QBTools noNullString:Country] forKey:@"Country"];
[dataDicM setObject:[QBTools noNullString:province_id] forKey:@"province_id"];
[dataDicM setObject:[QBTools noNullString:province_name] forKey:@"province_name"];
[dataDicM setObject:[QBTools noNullString:city_name] forKey:@"city_name"];
[dataDicM setObject:[QBTools noNullString:city_code] forKey:@"city_code"];
[resultMutArr addObject:dataDicM];
}
if (resultMutArr.count !=0) {
result(YES,resultMutArr);
}else{
result(NO,nil);
}
}];
}
增
-(void)saveCarTicketCityList:(NSDictionary*)dataDic{
[self.localqueue inDatabase:^(FMDatabase *db) {
BOOL success= [db executeUpdate:@"INSERT INTO t_CarTicketCityList(cityName,cityCode) VALUES (?,?)",dataDic[@"cityName"],dataDic[@"cityCode"]];
if (success) {
}
}];
}
刪
-(void)deleteChatUserInfo:(NSString*)chatUserName{
[self.queue inDatabase:^(FMDatabase *db) {
BOOL flag = [db executeUpdate:@"DELETE FROM t_ChatUserInfoTwo WHERE userName =?",chatUserName];
if (flag) {
NSLog(@"刪除舊的聊天使用者資訊成功");
}else{
NSLog(@"刪除舊的聊天使用者資訊失敗");
}
}];
}
改
-(void)updateEmployeSignData:(SignDataList*)model{
[self.queue inDatabase:^(FMDatabase *db) {
BOOL flag =[db executeUpdate:@"UPDATE t_SignDataList SET clockBeginTime =?,clockEndTime =? WHERE userPK =? and classNum = ?;",model.clockBeginTime,model.clockEndTime,model.userPK,model.classNum];
if (flag) {
NSLog(@"更新打卡記錄成功");
}else{
NSLog(@"更新打卡記錄失敗");
}
}];
}
連結串列查
查詢0316iOS班的所有學生
select s.name,s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;
NSString *tableName =@"my_db";
[db executeUpdate:[NSString stringWithFormat:@"create index idx1 on %@(userAccount)",tableName]];//索引userAccount
[db executeUpdate:[NSString stringWithFormat:@"create index idx2 on %@(userAccount,userId)",tableName]];//索引userId加userAccount