1.iOS Runtime面試題(什么是method swizzling(俗稱黑魔法))
什么是method swizzling(俗稱黑魔法)
簡單說就是進行方法交換
在Objective-C中調(diào)用一個方法,其實是向一個對象發(fā)送消息,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動態(tài)特性,可以實現(xiàn)在運行時偷換selector對應(yīng)的方法實現(xiàn),達到給方法掛鉤的目的。
每個類都有一個方法列表,存放著方法的名字和方法實現(xiàn)的映射關(guān)系,selector的本質(zhì)其實就是方法名,IMP有點類似函數(shù)指針,指向具體的Method實現(xiàn),通過selector就可以找到對應(yīng)的IMP。
換方法的幾種實現(xiàn)方式
* 利用 method_exchangeImplementations 交換兩個方法的實現(xiàn)
* 利用 class_replaceMethod替換方法的實現(xiàn)
* 利用 method_setImplementation 來直接設(shè)置某個方法的IMP
[圖片上傳失敗...(image-46db5a-1581791102546)]
2.iOS Runtime面試題(什么時候會報unrecognized selector的異常?)
objc在向一個對象發(fā)送消息時,runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,如果,在最頂層的父類中依然找不到相應(yīng)的方法時,會進入消息轉(zhuǎn)發(fā)階段,如果消息三次轉(zhuǎn)發(fā)流程仍未實現(xiàn),則程序在運行時會掛掉并拋出異常unrecognized selector sent to XXX
(1)runtime庫會根據(jù)對象的isa指針找到該對象實際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運行
(2)開始消息轉(zhuǎn)發(fā)
1.先征詢接收者能否動態(tài)添加方法(動態(tài)方法解析)
2.有沒有其他對象能夠處理
- (id)forwardingTargetForSelector:(SEL)aSelector{
// 當然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
ClassB *b = [[ClassB alloc] init];
if([b respondsToSelector:aSelector]){
return b;
}
return nil; }
3.運行期系統(tǒng)把與消息有關(guān)的全部細節(jié)封裝到NSInvocation(完整消息轉(zhuǎn)發(fā))
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; // 當然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"])
//ClassB *b = [[ClassB alloc] init];
if(signature == nil){
signature = [_b methodSignatureForSelector:aSelector];
}
NSUInteger argCount = [signature numberOfArguments];
for (NSInteger i=0; i<argCount; i++) {
NSLog(@"%s" , [signature getArgumentTypeAtIndex:i]);
}
NSLog(@"returnType:%s ,returnLen:%ld" , [signature methodReturnType] , [signature methodReturnLength]); NSLog(@"signature:%@" , signature); return signature; }
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// 當然這里可以通過判斷方法名來決定轉(zhuǎn)發(fā)的對象
//NSString *seletorName = NSStringFromSelector(aSelector);
//if([seletorName isEqualToString : @"xxx"]) SEL selector = [anInvocation selector];
//ClassB *b = [[ClassB alloc] init];
if([_b respondsToSelector:selector]){
//這里如果沒有響應(yīng),系統(tǒng)則會報錯崩潰 [anInvocation invokeWithTarget:_b];
}
}
4.如果完整消息轉(zhuǎn)發(fā)失敗則crash報unrecognized selector的異常
3.iOS Runtime面試題(如何給 Category 添加屬性?關(guān)聯(lián)對象以什么形式進行存儲?)
如何給 Category 添加屬性?關(guān)聯(lián)對象以什么形式進行存儲?
查看的是 關(guān)聯(lián)對象 的知識點。
詳細的說一下 關(guān)聯(lián)對象。
關(guān)聯(lián)對象 以哈希表的格式,存儲在一個全局的單例中。
找一下52個方法里的關(guān)聯(lián)key等知識!!!
@interface NSObject (Extension) @property (nonatomic,copy ) NSString *name; @end @implementation NSObject (Extension) - (void)setName:(NSString *)name { objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self,@selector(name)); } @end
4.iOS Runtime面試題(能否向編譯后得到的類中增加實例變量?能否向運行時創(chuàng)建的類中添加實例變量?為什么?)
不能向編譯后得到的類中增加實例變量;
能向運行時創(chuàng)建的類中添加實例變量;
1.因為編譯后的類已經(jīng)注冊在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內(nèi)存大小已經(jīng)確定,同時runtime會調(diào)用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.所以不能向存在的類中添加實例變量。
2.運行時創(chuàng)建的類是可以添加實例變量,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
5.iOS Runtime面試題(類對象的數(shù)據(jù)結(jié)構(gòu)?)
類對象就是 objc_class。
struct objc_class : objc_object { // Class ISA; Class superclass; //父類指針 cache_t cache; // formerly cache pointer and vtable 方法緩存 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 用于獲取地址 class_rw_t *data() { return bits.data(); // &FAST_DATA_MASK 獲取地址值 }
它的結(jié)構(gòu)相對豐富一些。繼承自objc_object結(jié)構(gòu)體,所以包含isa指針
* isa:指向元類
* superClass: 指向父類
* Cache: 方法的緩存列表
* data: 顧名思義,就是數(shù)據(jù)。是一個被封裝好的 class_rw_t。
補充一下實例對象?。?!
6.iOS Runtime面試題(runtime如何通過selector找到對應(yīng)的IMP地址?)
每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型,其實selector本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應(yīng)的方法實現(xiàn).
補充消息轉(zhuǎn)發(fā)時候的獲取方法列表等操作?。。?
7.iOS Runtime面試題(runtime如何實現(xiàn)weak變量的自動置nil?知道SideTable嗎?)
runtime 對注冊的類會進行布局,對于weak修飾的對象會放到一個hash表中。用weak指向的對象內(nèi)存地址作為key,當此對象的引用計數(shù)為0時會dealloc,加入weak指向的對象的內(nèi)存地址為a,那么就會以a為鍵,那么就以a為鍵,在這個weak表中搜索,找到所有以a為鍵的weak對象,從而設(shè)置為nil
(1)初始化時runtime會調(diào)用objc_initWeak函數(shù),初始化一個新的weak指針指向?qū)ο蟮牡刂贰?
(2)添加引用時:objc_initWeak函數(shù)會調(diào)用objc_storeWeak函數(shù)objc_storeWeak()的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表。
(3)釋放時,調(diào)用clearDeallocating函數(shù),clearDeallocating函數(shù)首先根據(jù)對象的地址獲取所有weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)置為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄。
全部原理太多了在:[http://www.itdecent.cn/p/5de63ac9dab7](http://www.itdecent.cn/p/5de63ac9dab7)
總結(jié):
其實Weak表是一個hash(哈希)表,Key是weak所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象指針的地址)數(shù)組。
8.iOS Runtime面試題(objc中向一個nil對象發(fā)送消息將會發(fā)生什么?)
如果向一個nil對象發(fā)送消息,首先在尋找對象的isa指針時就是0地址返回了,所以不會出現(xiàn)任何錯誤,也不會崩潰。
詳解:
(1)如果一個方法的返回值是一個對象,那么發(fā)送給nil的消息將會返回0(nil);
(2)如果方法的返回值為指針類型,其指針大小為小于或者等于sizeof(void*),float,double, long double或者long long的整型標量,發(fā)送給nil的消息將返回0;
(3)如果方法返回值為結(jié)構(gòu)體,發(fā)送給nil的消息將返回0.結(jié)構(gòu)體中各個字段的值都將是0;
(4)如果方法的返回值不是上述的幾種情況,那么發(fā)送給nil的消息的返回值將是未定義的。
9.iOS Runtime面試題(objc在向一個對象發(fā)送消息時,發(fā)生了什么?)
objc在向一個對象發(fā)送消息時,runtime會根據(jù)對象的isa指針找到該對象實際所屬的類
然后在該類中的方法列表及其父類方法列表中尋找發(fā)發(fā)運行,如果一直到根類還沒找到,轉(zhuǎn)向攔截調(diào)用,走消息轉(zhuǎn)發(fā)機制,一旦找到,就去執(zhí)行他的IMP
10.iOS Runtime面試題(isKindOfClass 與 isMemberOfClass)
待補充?。?
11.iOS Runtime面試題(Category 在編譯過后,是在什么時機與原有的類合并到一起的?)
這里應(yīng)該去查一下object init的原理?。?!
11.的原理也該查一下,下面寫的太繁瑣了
程序啟動后,通過編譯之后,Runtime 會進行初始化,調(diào)用 _objc_init。
然后會 map_images。
接下來調(diào)用 map_images_nolock。
再然后就是 read_images,這個方法會讀取所有的類的相關(guān)信息。
最后是調(diào)用 reMethodizeClass:,這個方法是重新方法化的意思。
在 reMethodizeClass:方法內(nèi)部會調(diào)用 attachCategories:,這個方法會傳入 Class 和 Category ,會將方法列表,協(xié)議列表等與原有的類合并。最后加入到 class_rw_t結(jié)構(gòu)體中。
12.iOS Runtime面試題(Category 有哪些用途?)
搜一下分類和擴展區(qū)別實現(xiàn)!??!
還有分類為什么不能添加屬性(就是分類的結(jié)構(gòu)體里面關(guān)于屬性的問題)?。?!
13.iOS Runtime面試題(Category 的實現(xiàn)原理?)
被添加在了 class_rw_t 的對應(yīng)結(jié)構(gòu)里。
Category實際上是Category_t的結(jié)構(gòu)體,在運行時,新添加的方法,都被以倒敘插入到原有的方法列表的最前面,所以不同的Category,添加了同一個方法,執(zhí)行的實際上是最后一個。
下面代碼要更新下?。?!
static struct _catrgory_t _OBJC_$_CATEGORY_NSObject_$_Tools __attribute__ ((used,section),("__DATA,__objc__const")) { // name // class // instance method list // class method list // protocol list // properties }
Category在剛剛編譯完的時候,和原來的類是分開的,只有程序在運行起來后,通過Runtime,Category和原來的類才會合并到一起
mememove,memcpy,這兩個方法是位移,復(fù)制,簡單的理解就是原有的方法移動到最后,根據(jù)新開辟的控件,把前面的位置留給分類,然后分類中的方法按照倒敘依次插入,可以得出的結(jié)論就是,越晚參與編譯的分類,里面的方法才是生效的那個
14.iOS Runtime面試題(_objc_msgForward函數(shù)是做什么的)
_objc_msgForward是 IMP 類型,用于消息轉(zhuǎn)發(fā)的:當向一個對象發(fā)送一條消息,但它并沒有實現(xiàn)的時候,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)。
詳解:_objc_msgForward在進行消息轉(zhuǎn)發(fā)的過程中會涉及以下這幾個方法:
List itemresolveInstanceMethod:方法 (或resolveClassMethod:)。
List itemforwardingTargetForSelector:方法
List itemmethodSignatureForSelector:方法
List itemforwardInvocation:方法
List itemdoesNotRecognizeSelector: 方法
15.iOS Runtime面試題([self class] 與 [super class])
下面的代碼輸出什么?
@implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son
詳解:這個題目主要是考察關(guān)于 Objective-C 中對 self 和 super 的理解。
self是類的隱藏參數(shù),指向當前調(diào)用方法的這個類的實例
super本質(zhì)是一個編譯器的標識符,和self是指向一個消息接收者。不同點在于:super會告訴編譯器,當調(diào)用方法是,去調(diào)用父類的方法,而不是本類中的方法。
當使用self調(diào)用方法時,則從父類的方法列表中開始找。然后調(diào)用父類的這個方法。
在調(diào)用[super class]的時候,runtime會去調(diào)用 objc_msgSenderSuper方法,而不是objc_msgSend;
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
在objc_msgSendSuper方法中,第一個參數(shù)是一個objc_super的結(jié)構(gòu)體,這個結(jié)構(gòu)體里面有兩個變量,一個是當前父類的super_class
objc_msgSenderSuper的工作原理應(yīng)該是這樣的
從objc_super結(jié)構(gòu)體指向的superClass父類的方法列表開始查找Selector,找到后以objc->receiver去調(diào)用父類的這個selector。注意,最后調(diào)用者是objc_receiver,而不是super_class
那么objc_msgSendSuper最后就轉(zhuǎn)變成:
// 注意這里是從父類開始msgSend,而不是從本類開始 objc_msgSend(objc_super->receiver, @selector(class)) /// Specifies an instance of a class. 這是類的一個實例 __unsafe_unretained id receiver; // 由于是實例調(diào)用,所以是減號方法 - (Class)class { return object_getClass(self); }
由于找到了父類NSObject里面的class方法的IMP,又因為傳入的參數(shù)objc_super->receiver = self. self就是son,調(diào)用class,所以父類的方法class執(zhí)行IMP之后,輸出的還是son最后輸出的兩個都一樣,都是輸出son
擴展題:[http://www.itdecent.cn/p/a8e32de4858f](http://www.itdecent.cn/p/a8e32de4858f)
16.iOS Runloop面試題(為什么 NSTimer 有時候不好使?)
找一下?。。?
17.iOS Runloop面試題(PerformSelector 的實現(xiàn)原理?)
在寫多線程時候因為直接在主函數(shù)里寫的多線程,但是多線程沒有開啟runloop(正確性待驗證)
當調(diào)用 NSObject 的 performSelecter:afterDelay: 后,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當前線程的 RunLoop 中。所以如果當前線程沒有 RunLoop,則這個方法會失效。
當調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應(yīng)的線程去,同樣的,如果對應(yīng)線程沒有 RunLoop 該方法也會失效。
18.iOS Runloop面試題(PerformSelector:afterDelay:這個方法在子線程中是否起作用?為什么?怎么解決?)
不起作用,子線程默認沒有 Runloop,也就沒有 Timer。
解決的辦法是可以使用 GCD 來實現(xiàn):Dispatch_after
19.iOS Runloop面試題(RunLoop的Mode)
關(guān)于Mode首先要知道一個RunLoop 對象中可能包含多個Mode,且每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode(CurrentMode)。切換 Mode,需要重新指定一個 Mode 。主要是為了分隔開不同的 Source、Timer、Observer,讓它們之間互不影響。
當RunLoop運行在Mode1上時,是無法接受處理Mode2或Mode3上的Source、Timer、Observer事件的
總共是有五種CFRunLoopMode:
* kCFRunLoopDefaultMode:默認模式,主線程是在這個運行模式下運行
* UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響)
* UIInitializationRunLoopMode:在剛啟動App時第進入的第一個 Mode,啟動完成后就不再使用
* GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件,通常用不到
* kCFRunLoopCommonModes:偽模式,不是一種真正的運行模式,是同步Source/Timer/Observer到多個Mode中的一種解決方案
20.iOS Runloop面試題(RunLoop的實現(xiàn)機制)
[http://www.itdecent.cn/p/cc1bb6cba76d](http://www.itdecent.cn/p/cc1bb6cba76d)
21.iOS Runloop面試題(RunLoop和線程)
* 線程和runloop是一一對應(yīng)的,其映射關(guān)系是保存在一個全局的Dictionary里
* 自己創(chuàng)建的線程默認是沒有開啟RunLoop的
(1)怎么創(chuàng)建一個常駐線程?
1.為當前線程開啟了一個RunLoop(第一次調(diào)用[NSRunLoop currentRunLoop])方法時實際是會先創(chuàng)建一個 RunLoop
2.向當前RunLoop中添加一個Port/Source等維持RunLoop的事件循環(huán)(如果RunLoop的mode中的一個item都沒有,RunLoop會退出)
3.啟動該RunLoop
@autoreleasepool { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }
(2)輸出下邊代碼的執(zhí)行順序
NSLog(@"1"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); [self performSelector:@selector(test) withObject:nil afterDelay:10]; NSLog(@"3"); }); NSLog(@"4"); - (void)test { NSLog(@"5"); }
答案是1423,test方法并不會執(zhí)行。
原因是如果是帶afterDelay的延時函數(shù),會在內(nèi)部創(chuàng)建一個 NSTimer,然后添加到當前線程的RunLoop中。也就是如果當前線程沒有開啟RunLoop,該方法會失效。
那么我們改成:
dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); [[NSRunLoop currentRunLoop] run]; [self performSelector:@selector(test) withObject:nil afterDelay:10]; NSLog(@"3"); });
然而test方法依然不執(zhí)行。
原因是如果RunLoop的mode中一個item都沒有,RunLoop會退出。即在調(diào)用RunLoop的run方法后,由于其mode中沒有添加任何item去維持RunLoop的時間循環(huán),RunLoop隨即還是會退出。
所以我們自己啟動RunLoop,一定要在添加item后
dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); [self performSelector:@selector(test) withObject:nil afterDelay:10]; [[NSRunLoop currentRunLoop] run];//一定是添加在item后,item維持RunLoop的時間循環(huán) NSLog(@"3"); });
(3)怎樣保證子線程數(shù)據(jù)回來更新UI的時候不打斷用戶的滑動操作?
當我們在子請求數(shù)據(jù)的同時滑動瀏覽當前頁面,如果數(shù)據(jù)請求成功要切回主線程更新UI,那么就會影響當前正在滑動的體驗。
我們就可以將更新UI事件放在主線程的NSDefaultRunLoopMode上執(zhí)行即可,這樣就會等用戶不再滑動頁面,主線程RunLoop由UITrackingRunLoopMode切換到NSDefaultRunLoopMode時再去更新UI
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
22.iOS Runloop面試題(RunLoop的數(shù)據(jù)結(jié)構(gòu))
[http://www.itdecent.cn/p/64c478fe2cf1](http://www.itdecent.cn/p/64c478fe2cf1)
23.iOS Runloop面試題(RunLoop概念)
RunLoop是通過內(nèi)部維護的事件循環(huán)(Event Loop)來對事件/消息進行管理的一個對象。
1、沒有消息處理時,休眠已避免資源占用,由用戶態(tài)切換到內(nèi)核態(tài)(CPU-內(nèi)核態(tài)和用戶態(tài))
2、有消息需要處理時,立刻被喚醒,由內(nèi)核態(tài)切換到用戶態(tài)
為什么main函數(shù)不會退出?
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
UIApplicationMain內(nèi)部默認開啟了主線程的RunLoop,并執(zhí)行了一段無限循環(huán)的代碼(不是簡單的for循環(huán)或while循環(huán))
//無限循環(huán)代碼模式(偽代碼) int main(int argc, char * argv[]) { BOOL running = YES; do { // 執(zhí)行各種任務(wù),處理各種事件 // ...... } while (running); return 0; }
UIApplicationMain函數(shù)一直沒有返回,而是不斷地接收處理消息以及等待休眠,所以運行程序之后會保持持續(xù)運行狀態(tài)。
24.iOS Runloop面試題(講一下 Observer ?)
[http://www.itdecent.cn/p/bac5c2462e69](http://www.itdecent.cn/p/bac5c2462e69)
25.iOS Runloop面試題(解釋一下 NSTimer。)
NSTimer其實就是 CFRunLoopTimerRef,他們之間是 toll-free bridged的。一個 NSTimer注冊到 RunLoop后,RunLoop會為其重復(fù)的時間點注冊好事件。例如 10:00, 10:10, 10:20這幾個時間點。RunLoop為了節(jié)省資源,并不會在非常準確的時間點回調(diào)這個Timer。Timer有個屬性叫做 Tolerance(寬容度),標示了當時間點到后,容許有多少最大誤差。
如果某個時間點被錯過了,例如執(zhí)行了一個很長的任務(wù),則那個時間點的回調(diào)也會跳過去,不會延后執(zhí)行。就比如等公交,如果 10:10 時我忙著玩手機錯過了那個點的公交,那我只能等 10:20 這一趟了。
CADisplayLink是一個和屏幕刷新率一致的定時器(但實際實現(xiàn)原理更復(fù)雜,和 NSTimer 并不一樣,其內(nèi)部實際是操作了一個 Source)。如果在兩次屏幕刷新之間執(zhí)行了一個長任務(wù),那其中就會有一幀被跳過去(和 NSTimer相似),造成界面卡頓的感覺。在快速滑動 TableView時,即使一幀的卡頓也會讓用戶有所察覺。Facebook開源的 AsyncDisplayLink就是為了解決界面卡頓的問題,其內(nèi)部也用到了 RunLoop。
26.iOS Runloop面試題(解釋一下 `事件響應(yīng)` 的過程?)
[http://www.itdecent.cn/p/9c3d136302be](http://www.itdecent.cn/p/9c3d136302be)
27.iOS Runloop面試題(解釋一下 手勢識別 的過程?)
[http://www.itdecent.cn/p/32d97298d669](http://www.itdecent.cn/p/32d97298d669)
順便補充下UIGestureRecognizer和touch事件?。。?
28.iOS Runloop面試題(什么是異步繪制?)
異步繪制,就是可以在子線程把需要繪制的圖形,提前在子線程處理好。將準備好的圖像數(shù)據(jù)直接返給主線程使用,這樣可以降低主線程的壓力。
異步繪制的過程
要通過系統(tǒng)的 [view.delegate displayLayer:]這個入口來實現(xiàn)異步繪制。
* 代理負責生成對應(yīng)的 Bitmap
* 設(shè)置該 Bitmap 為 layer.contents 屬性的值。
?。。http://www.itdecent.cn/p/6634dbdf2964](http://www.itdecent.cn/p/6634dbdf2964)
?。。http://www.itdecent.cn/p/ee67b17dbf19](http://www.itdecent.cn/p/ee67b17dbf19)
29.iOS Runloop面試題(利用 runloop 解釋一下頁面的渲染的過程?)
[http://www.itdecent.cn/p/8900516f1644](http://www.itdecent.cn/p/8900516f1644)
30.iOS OC底層面試題(KVC(Key-value coding))
-(id)valueForKey:(NSString *)key; -(void)setValue:(id)value forKey:(NSString *)key;
KVC就是指iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值。而不需要調(diào)用明確的存取方法。這樣就可以在運行時動態(tài)地訪問和修改對象的屬性。而不是在編譯時確定,這也是iOS開發(fā)中的黑魔法之一。很多高級的iOS開發(fā)技巧都是基于KVC實現(xiàn)的
當調(diào)用setValue:屬性值forKey:@”name“的代碼時,,底層的執(zhí)行機制如下:
* 程序優(yōu)先調(diào)用set<Key>:屬性值方法,代碼通過setter方法完成設(shè)置。注意,這里的<key>是指成員變量名,首字母大小寫要符合KVC的命名規(guī)則,下同
* 如果沒有找到setName:方法,KVC機制會檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES,默認該方法會返回YES,如果你重寫了該方法讓其返回NO的話,那么在這一步KVC會執(zhí)行setValue:forUndefinedKey:方法,不過一般開發(fā)者不會這么做。所以KVC機制會搜索該類里面有沒有名為_<key>的成員變量,無論該變量是在類接口處定義,還是在類實現(xiàn)處定義,也無論用了什么樣的訪問修飾符,只在存在以<key>命名的變量,KVC都可以對該成員變量賦值。
* 如果該類即沒有set<key>:方法,也沒有_<key>成員變量,KVC機制會搜索_is<Key>的成員變量。
* 和上面一樣,如果該類即沒有set<Key>:方法,也沒有_<key>和_is<Key>成員變量,KVC機制再會繼續(xù)搜索<key>和is<Key>的成員變量。再給它們賦值。
* 如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會執(zhí)行該對象的setValue:forUndefinedKey:方法,默認是拋出異常。
即如果沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜索成員并進行賦值操作。
如果開發(fā)者想讓這個類禁用KVC,那么重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可,這樣的話如果KVC沒有找到set<Key>:屬性名時,會直接用setValue:forUndefinedKey:方法。
當調(diào)用valueForKey:@”name“的代碼時,KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下:
* 首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話會直接調(diào)用。如果是BOOL或者Int等值類型, 會將其包裝成一個NSNumber對象。
* 如果上面的getter沒有找到,KVC則會查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外兩個方法中的一個被找到,那么就會返回一個可以響應(yīng)NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類),調(diào)用這個代理集合的方法,或者說給這個代理集合發(fā)送屬于NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes這幾個方法組合的形式調(diào)用。還有一個可選的get<Key>:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標準命名方法,包括方法簽名。
* 如果上面的方法沒有找到,那么會同時查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果這三個方法都找到,那么就返回一個可以響應(yīng)NSSet所的方法的代理集合,和上面一樣,給這個代理集合發(fā)NSSet的消息,就會以countOf<Key>,enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用。
* 如果還沒有找到,再檢查類方法+
(BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為),那么和先前的設(shè)值一樣,會按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名,這里不推薦這么做,因為這樣直接訪問實例變量破壞了封裝性,使代碼更脆弱。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那么會直接調(diào)用valueForUndefinedKey:方法,默認是拋出異常。
31.iOS OC底層面試題(KVO (Key-value observing))
待添加?。?!
* 那么通過直接賦值成員變量會觸發(fā)KVO嗎?
不會,因為不會調(diào)用setter方法,需要加上
willChangeValueForKey和didChangeValueForKey方法來手動觸發(fā)才行
32.iOS OC底層面試題(分類、擴展、代理(Delegate))
一、分類
* 1.分類的作用?
聲明私有方法,分解體積大的類文件,把framework的私有方法公開
* 2.分類的特點
運行時決議,可以為系統(tǒng)類添加分類 。
說得詳細些,在運行時時期,將 Category 中的實例方法列表、協(xié)議列表、屬性列表添加到主類中后(所以Category中的方法在方法列表中的位置是在主類的同名方法之前的),然后會遞歸調(diào)用所有類的 load 方法,這一切都是在main函數(shù)之前執(zhí)行的。
* 3.分類可以添加哪些內(nèi)容?
實例方法,類方法,協(xié)議,屬性(添加getter和setter方法,并沒有實例變量,添加實例變量需要用關(guān)聯(lián)對象)
* 4.如果工程里有兩個分類A和B,兩個分類中有一個同名的方法,哪個方法最終生效?
取決于分類的編譯順序,最后編譯的那個分類的同名方法最終生效,而之前的都會被覆蓋掉(這里并不是真正的覆蓋,因為其余方法仍然存在,只是訪問不到,因為在動態(tài)添加類的方法的時候是倒序遍歷方法列表的,而最后編譯的分類的方法會放在方法列表前面,訪問的時候就會先被訪問到,同理如果聲明了一個和原類方法同名的方法,也會覆蓋掉原類的方法)。
* 5.如果聲明了兩個同名的分類會怎樣?
會報錯,所以第三方的分類,一般都帶有命名前綴
* 6.分類能添加成員變量嗎??。?!
不能。只能通過關(guān)聯(lián)對象(objc_setAssociatedObject)來模擬實現(xiàn)成員變量,但其實質(zhì)是關(guān)聯(lián)內(nèi)容,所有對象的關(guān)聯(lián)內(nèi)容都放在同一個全局容器哈希表中:AssociationsHashMap,由AssociationsManager統(tǒng)一管理。
二、擴展
* 1.一般用擴展做什么?
聲明私有屬性,聲明方法(沒什么意義),聲明私有成員變量
* 2.擴展的特點
編譯時決議,只能以聲明的形式存在,多數(shù)情況下寄生在宿主類的.m中,不能為系統(tǒng)類添加擴展。
三、代理(Delegate)
代理是一種設(shè)計模式,以@protocol形式體現(xiàn),一般是一對一傳遞。
一般以weak關(guān)鍵詞以規(guī)避循環(huán)引用。
待補充!??!
33.iOS OC底層面試題(屬性關(guān)鍵字copy)
可變對象的copy和mutableCopy都是深拷貝
不可變對象的copy是淺拷貝,mutableCopy是深拷貝?。。?
copy方法返回的都是不可變對象
* @property (nonatomic, copy) NSMutableArray * array;這樣寫有什么影響?
因為copy方法返回的都是不可變對象,所以array對象實際上是不可變的,如果對其進行可變操作如添加移除對象,則會造成程序crash!??!
assign:修飾基本數(shù)據(jù)類型,修飾對象類型時,不改變其引用計數(shù),會產(chǎn)生懸垂指針,修飾的對象在被釋放后,assign指針仍然指向原對象內(nèi)存地址,如果使用assign指針繼續(xù)訪問原對象的話,就可能會導(dǎo)致內(nèi)存泄漏或程序異常(實際上會報錯???)
34.iOS Animation面試題(請說一下對 CALayer 的認識。)
layer層是涂層繪制、渲染、以及動畫的完成者,它無法直接的處理觸摸事件(也可以捕捉事件)
layer包含的方面非常多,常見的屬性有 Frame、Bounds、Position、AnchorPoint、Contents等等。
35.iOS Animation面試題(`CALayer` 的 `Contents` 有幾下幾個主要的屬性:)
待補充!?。?
36.iOS Block面試題(Block的幾種形式)
比較基礎(chǔ):[http://www.itdecent.cn/p/ab5cd4153bf8](http://www.itdecent.cn/p/ab5cd4153bf8)
37.iOS Block面試題(Block變量截獲)
待補充?。?
38.iOS Block面試題(什么是Block?)
待補充?。?
39.iOS UI相關(guān)面試題
一、UIView與CALayer
<單一職責原則>
UIView為CALayer提供內(nèi)容,以及負責處理觸摸等事件,參與響應(yīng)鏈
CALayer負責顯示內(nèi)容contents
二、事件傳遞與視圖響應(yīng)鏈 :
待補充?。?!
三、圖像顯示原理
1.CPU:輸出位圖
2.GPU :圖層渲染,紋理合成
3.把結(jié)果放到幀緩沖區(qū)(frame buffer)中
4.再由視頻控制器根據(jù)vsync信號在指定時間之前去提取幀緩沖區(qū)的屏幕顯示內(nèi)容
5.顯示到屏幕上
CPU工作
1.Layout: UI布局,文本計算
2.Display: 繪制
3.Prepare: 圖片解碼
4.Commit:提交位圖
GPU渲染管線(OpenGL)
頂點著色,圖元裝配,光柵化,片段著色,片段處理
四、UI卡頓掉幀原因
iOS設(shè)備的硬件時鐘會發(fā)出Vsync(垂直同步信號),然后App的CPU會去計算屏幕要顯示的內(nèi)容,之后將計算好的內(nèi)容提交到GPU去渲染。隨后,GPU將渲染結(jié)果提交到幀緩沖區(qū),等到下一個VSync到來時將緩沖區(qū)的幀顯示到屏幕上。也就是說,一幀的顯示是由CPU和GPU共同決定的。
一般來說,頁面滑動流暢是60fps,也就是1s有60幀更新,即每隔16.7ms就要產(chǎn)生一幀畫面,而如果CPU和GPU加起來的處理時間超過了16.7ms,就會造成掉幀甚至卡頓。
五、滑動優(yōu)化方案
CPU:
把以下操作放在子線程中
1.對象創(chuàng)建、調(diào)整、銷毀
2.預(yù)排版(布局計算、文本計算、緩存高度等等)
3.預(yù)渲染(文本等異步繪制,圖片解碼等)
GPU:
紋理渲染,視圖混合
一般遇到性能問題時,考慮以下問題:
是否受到CPU或者GPU的限制?
是否有不必要的CPU渲染?
是否有太多的離屏渲染操作?
是否有太多的圖層混合操作?
是否有奇怪的圖片格式或者尺寸?
是否涉及到昂貴的view或者效果?
view的層次結(jié)構(gòu)是否合理?
六、UI繪制原理
異步繪制:
[self.layer.delegate displayLayer: ]
代理負責生成對應(yīng)的bitmap
設(shè)置該bitmap作為該layer.contents屬性的值
七、離屏渲染
On-Screen Rendering:當前屏幕渲染,指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區(qū)中進行
Off-Screen Rendering:離屏渲染,分為CPU離屏渲染和GPU離屏渲染兩種形式。GPU離屏渲染指的是GPU在當前屏幕緩沖區(qū)外新開辟一個緩沖區(qū)進行渲染操作
應(yīng)當盡量避免的則是GPU離屏渲染
GPU離屏渲染何時會觸發(fā)呢?
圓角(當和maskToBounds一起使用時)、圖層蒙版、陰影,設(shè)置
layer.shouldRasterize = YES
為什么要避免GPU離屏渲染?
GPU需要做額外的渲染操作。通常GPU在做渲染的時候是很快的,但是涉及到offscreen-render的時候情況就可能有些不同,因為需要額外開辟一個新的緩沖區(qū)進行渲染,然后繪制到當前屏幕的過程需要做onscreen跟offscreen上下文之間的切換,這個過程的消耗會比較昂貴,涉及到OpenGL的pipeline跟barrier,而且offscreen-render在每一幀都會涉及到,因此處理不當肯定會對性能產(chǎn)生一定的影響。另外由于離屏渲染會增加GPU的工作量,可能會導(dǎo)致CPU+GPU的處理時間超出16.7ms,導(dǎo)致掉幀卡頓。所以可以的話應(yīng)盡量減少offscreen-render的圖層
40.iOS 多線程面試題(進程、線程)
一、 進程:
* 1.進程是一個具有一定獨立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運行活動,它是操作系統(tǒng)分配資源的最小單元.
* 2.進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機上的一個app.
* 3.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi),擁有獨立運行所需的全部資源
補充進程通信?。。????
二、 線程
* 1.程序執(zhí)行流的最小單元,線程是進程中的一個實體.
* 2.一個進程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動的時候,系統(tǒng)會默認開啟一條線程,也就是主線程
三、 進程和線程的關(guān)系
* 1.線程是進程的執(zhí)行單元,進程的所有任務(wù)都在線程中執(zhí)行
* 2.線程是 CPU 分配資源和調(diào)度的最小單位
* 3.一個程序可以對應(yīng)多個進程(多進程),一個進程中可有多個線程,但至少要有一條線程
* 4.同一個進程內(nèi)的線程共享進程資源
41.iOS 多線程面試題(死鎖)
1、一個比較常見的死鎖例子:主隊列同步
- (void)viewDidLoad { [super viewDidLoad]; dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"deallock"); }); // Do any additional setup after loading the view, typically from a nib. }
在主線程中運用主隊列同步,也就是把任務(wù)放到了主線程的隊列中。
同步對于任務(wù)是立刻執(zhí)行的,那么當把任務(wù)放進主隊列時,它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù),viewDidLoad才會繼續(xù)向下執(zhí)行。?。。?
而viewDidLoad和任務(wù)都是在主隊列上的,由于隊列的先進先出原則,任務(wù)又需等待viewDidLoad執(zhí)行完畢后才能繼續(xù)執(zhí)行,viewDidLoad和這個任務(wù)就形成了相互循環(huán)等待,就造成了死鎖。!?。?
想避免這種死鎖,可以將同步改成異步dispatch_async,或者將dispatch_get_main_queue換成其他串行或并行隊列,都可以解決。?。?!
2、同樣,下邊的代碼也會造成死鎖:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ dispatch_sync(serialQueue, ^{ NSLog(@"deadlock"); }); });
外面的函數(shù)無論是同步還是異步都會造成死鎖。!?。?
這是因為里面的任務(wù)和外面的任務(wù)都在同一個serialQueue隊列內(nèi),又是同步,這就和上邊主隊列同步的例子一樣造成了死鎖!??!
解決方法也和上邊一樣,將里面的同步改成異步dispatch_async,或者將serialQueue換成其他串行或并行隊列,都可以解決
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ dispatch_sync(serialQueue2, ^{ NSLog(@"deadlock"); }); });
這樣是不會死鎖的,并且serialQueue和serialQueue2是在同一個線程中的。
隊列的知識也需要再看看?。?!
42\. iOS 多線程面試題(dispatch_barrier_async)
1、問:怎么用GCD實現(xiàn)多讀單寫?
多讀單寫的意思就是:可以多個讀者同時讀取數(shù)據(jù),而在讀的時候,不能取寫入數(shù)據(jù)。并且,在寫的過程中,不能有其他寫者去寫。即讀者之間是并發(fā)的,寫者與讀者或其他寫者是互斥的。
這里的寫處理就是通過柵欄的形式去寫。
就可以用dispatch_barrier_sync(柵欄函數(shù))去實現(xiàn)
- (id)readDataForKey:(NSString *)key { __block id result; dispatch_sync(_concurrentQueue, ^{ result = [self valueForKey:key]; }); return result; } - (void)writeData:(id)data forKey:(NSString *)key { dispatch_barrier_async(_concurrentQueue, ^{ [self setValue:data forKey:key]; }); }
dispatch_barrier_async全部知識:[http://www.itdecent.cn/p/540c2b22ba38](http://www.itdecent.cn/p/540c2b22ba38)
_concurrentQueue目的是柵欄函數(shù)和同步公用一個并發(fā)隊列?。。?
43.iOS 多線程面試題(dispatch_group_async)
場景:在n個耗時并發(fā)任務(wù)都完成后,再去執(zhí)行接下來的任務(wù)。比如,在n個網(wǎng)絡(luò)請求完成后去刷新UI頁面。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); for (NSInteger i = 0; i < 10; i++) { dispatch_group_async(group, concurrentQueue, ^{ sleep(1); NSLog(@"%zd:網(wǎng)絡(luò)請求",i); }); } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"刷新頁面"); });
注意最后要寫:dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面");
});?。。。。。。。。?
44.iOS 多線程面試題(Dispatch Semaphore 信號量)
[http://www.itdecent.cn/p/8549a35b7bf2](http://www.itdecent.cn/p/8549a35b7bf2)
45.iOS 多線程面試題(延時函數(shù)(dispatch_after))
dispatch_after能讓我們添加進隊列的任務(wù)延時執(zhí)行,該函數(shù)并不是在指定時間后執(zhí)行處理,而只是在指定時間追加處理到dispatch_queue
//第一個參數(shù)是time,第二個參數(shù)是dispatch_queue,第三個參數(shù)是要執(zhí)行的block dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"dispatch_after"); });
由于其內(nèi)部使用的是dispatch_time_t管理時間,而不是NSTimer。
所以如果在子線程中調(diào)用,相比performSelector:afterDelay,不用關(guān)心runloop是否開啟
46.iOS 多線程面試題(使用dispatch_once實現(xiàn)單例)
多寫幾遍背下來?。。?
+ (instancetype)shareInstance { static dispatch_once_t onceToken; static id instance = nil; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; }
47.iOS 多線程面試題(NSThread+runloop實現(xiàn)常駐線程)
[http://www.itdecent.cn/p/f0bcc10abad0](http://www.itdecent.cn/p/f0bcc10abad0)
48.iOS 多線程面試題(自旋鎖與互斥鎖)
[http://www.itdecent.cn/p/80043c824d2d](http://www.itdecent.cn/p/80043c824d2d)
49.iOS 內(nèi)存管理面試題(在 MRC 下如何重寫屬性的 Setter 、 Getter、strong、retain、copy)
strong、retain、copy待補充!??!
-(void)setBrand:(NSString *)brand{ //如果實例變量指向的地址和參數(shù)指向的地址不同 if (_brand != brand) { //將實例變量的引用計數(shù)減一 [_brand release]; //將參數(shù)變量的引用計數(shù)加一,并賦值給實例變量 _brand = [brand retain]; } }
-(NSString *)brand{ //將實例變量的引用計數(shù)加1后,添加自動減1 //作用,保證調(diào)用getter方法取值時可以取到值的同時在完全不需要使用后釋放 return [[_brand retain] autorelease]; }
//MRC下 手動釋放內(nèi)存 可重寫dealloc但不要調(diào)用dealloc 會崩潰 -(void)dealloc{ [_string release]; //必須最后調(diào)用super dealloc [super dealloc]; }
50.iOS 內(nèi)存管理面試題(循環(huán)引用)
[http://www.itdecent.cn/p/33b7b326dcc4](http://www.itdecent.cn/p/33b7b326dcc4)
2、NSTimer循環(huán)引用屬于相互循環(huán)使用
在控制器內(nèi),創(chuàng)建NSTimer作為其屬性,由于定時器創(chuàng)建后也會強引用該控制器對象,那么該對象和定時器就相互循環(huán)引用了。
如何解決呢?
這里我們可以使用手動斷開循環(huán)引用:
如果是不重復(fù)定時器,在回調(diào)方法里將定時器invalidate并置為nil即可。
如果是重復(fù)定時器,在合適的位置將其invalidate并置為nil即可
51.iOS 內(nèi)存管理面試題(說一下什么是 `懸垂指針`?什么是 `野指針`?)
指針指向的內(nèi)存已經(jīng)被釋放了,但是指針還存在,這就是一個 懸垂指針 或者說 迷途指針
沒有進行初始化的指針,其實都是 野指針
52.iOS 內(nèi)存管理面試題(是否了解 深拷貝 和 淺拷貝 的概念,集合類深拷貝如何實現(xiàn))
簡而言之:
1、對不可變的非集合對象,copy是指針拷貝,mutablecopy是內(nèi)容拷貝
2、對于可變的非集合對象,copy,mutablecopy都是內(nèi)容拷貝
3、對不可變的數(shù)組、字典、集合等集合類對象,copy是指針拷貝,mutablecopy是內(nèi)容拷貝
4、對于可變的數(shù)組、字典、集合等集合類對象,copy,mutablecopy都是內(nèi)容拷貝
但是,對于集合對象的內(nèi)容復(fù)制僅僅是對對象本身,但是對象的里面的元素還是指針復(fù)制。要想復(fù)制整個集合對象,就要用集合深復(fù)制的方法,有兩種:!?。。。。。。?
(1)使用initWithArray:copyItems:方法,將第二個參數(shù)設(shè)置為YES即可
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
(2)將集合對象進行歸檔(archive)然后解歸檔(unarchive):
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
53.iOS 內(nèi)存相關(guān)面試題(能不能簡述一下 Dealloc 的實現(xiàn)機制.md)
[http://www.itdecent.cn/p/c00e2fdb5afb](http://www.itdecent.cn/p/c00e2fdb5afb)
54.iOS 內(nèi)存相關(guān)面試題(內(nèi)存中的5大區(qū)分別是什么?)
背下來?。?!
棧區(qū)(stack):由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其 操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。
全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的 全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。 - 程序結(jié)束后由系統(tǒng)釋放。
文字常量區(qū):常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放。
程序代碼區(qū):存放函數(shù)體的二進制代碼。
55.iOS 內(nèi)存管理面試題(內(nèi)存管理默認的關(guān)鍵字是什么?)
atomic,readWrite,strong如果改為基本數(shù)據(jù)類型,那就是atomic,readWrite, assign。
56.iOS 內(nèi)存管理面試題(內(nèi)存管理方案)
內(nèi)存管理方案
* taggedPointer :存儲小對象如NSNumber。深入理解Tagged Pointer
* NONPOINTER_ISA(非指針型的isa):在64位架構(gòu)下,isa指針是占64比特位的,實際上只有30多位就已經(jīng)夠用了,為了提高利用率,剩余的比特位存儲了內(nèi)存管理的相關(guān)數(shù)據(jù)內(nèi)容。
* 散列表:復(fù)雜的數(shù)據(jù)結(jié)構(gòu),包括了引用計數(shù)表和弱引用表
通過SideTables()結(jié)構(gòu)來實現(xiàn)的,SideTables()結(jié)構(gòu)下,有很多SideTable的數(shù)據(jù)結(jié)構(gòu)。
而sideTable當中包含了自旋鎖,引用計數(shù)表,弱引用表。
SideTables()實際上是一個哈希表,通過對象的地址來計算該對象的引用計數(shù)在哪個sideTable中。
自旋鎖:
* 自旋鎖是“忙等”的鎖。
* 適用于輕量訪問。
引用計數(shù)表和弱引用表實際是一個哈希表,來提高查找效率。
搜索一下hash表!?。?
57.iOS 內(nèi)存管理面試題(內(nèi)存布局)
棧(stack):方法調(diào)用,局部變量等,是連續(xù)的,高地址往低地址擴展
堆(heap):通過alloc等分配的對象,是離散的,低地址往高地址擴展,需要我們手動控制
未初始化數(shù)據(jù)(bss):未初始化的全局變量等
已初始化數(shù)據(jù)(data):已初始化的全局變量等
代碼段(text):程序代碼
3、static、const和sizeof關(guān)鍵字
static關(guān)鍵字
答:Static的用途主要有兩個,一是用于修飾存儲類型使之成為靜態(tài)存儲類型,二是用于修飾鏈接屬性使之成為內(nèi)部鏈接屬性。
* 1、靜態(tài)存儲類型:
在函數(shù)內(nèi)定義的靜態(tài)局部變量,該變量存在內(nèi)存的靜態(tài)區(qū),所以即使該函數(shù)運行結(jié)束,靜態(tài)變量的值不會被銷毀,函數(shù)下次運行時能仍用到這個值。
在函數(shù)外定義的靜態(tài)變量——靜態(tài)全局變量,該變量的作用域只能在定義該變量的文件中,不能被其他文件通過extern引用。
* 2、內(nèi)部鏈接屬性
靜態(tài)函數(shù)只能在聲明它的源文件中使用。
const關(guān)鍵字
* 1、聲明常變量,使得指定的變量不能被修改。
const int a = 5;/*a的值一直為5,不能被改變*/ const int b; b = 10;/*b的值被賦值為10后,不能被改變*/ const int *ptr; /*ptr為指向整型常量的指針,ptr的值可以修改,但不能修改其所指向的值*/ int *const ptr;/*ptr為指向整型的常量指針,ptr的值不能修改,但可以修改其所指向的值*/ const int *const ptr;/*ptr為指向整型常量的常量指針,ptr及其指向的值都不能修改*/const int a = 5;/*a的值一直為5,不能被改變*/ const int b; b = 10;/*b的值被賦值為10后,不能被改變*/ const int *ptr; /*ptr為指向整型常量的指針,ptr的值可以修改,但不能修改其所指向的值*/ int *const ptr;/*ptr為指向整型的常量指針,ptr的值不能修改,但可以修改其所指向的值*/ const int *const ptr;/*ptr為指向整型常量的常量指針,ptr及其指向的值都不能修改*/
* 2、修飾函數(shù)形參,使得形參在函數(shù)內(nèi)不能被修改,表示輸入?yún)?shù)。
int fun(const int a);或int fun(const char *str);
* 3、修飾函數(shù)返回值,使得函數(shù)的返回值不能被修改。
const char *getstr(void);使用:const *str= getstr(); const int getint(void); 使用:const int a =getint();
sizeof關(guān)鍵字
sizeof是在編譯階段處理,且不能被編譯為機器碼。sizeof的結(jié)果等于對象或類型所占的內(nèi)存字節(jié)數(shù)。sizeof的返回值類型為size_t。
* 變量:int a; sizeof(a)為4;
* 指針:int *p; sizeof(p)為4;
* 數(shù)組:int b[10]; sizeof(b)為數(shù)組的大小,4*10;int c[0]; sizeof(c)等于0
* 結(jié)構(gòu)體:struct (int a; char ch;)s1; sizeof(s1)為8 與結(jié)構(gòu)體字節(jié)對齊有關(guān)。
對結(jié)構(gòu)體求sizeof時,有兩個原則:
(1)展開后的結(jié)構(gòu)體的第一個成員的偏移量應(yīng)當是被展開的結(jié)構(gòu)體中最大的成員的整數(shù)倍。 (2)結(jié)構(gòu)體大小必須是所有成員大小的整數(shù)倍,這里所有成員計算的是展開后的成員,而不是將嵌套的結(jié)構(gòu)體當做一個整體。
* 注意:不能對結(jié)構(gòu)體中的位域成員使用sizeof
* sizeof(void)等于1
* sizeof(void *)等于4
58.iOS 內(nèi)存管理面試題(講一下 `iOS` 內(nèi)存管理的理解)
實際上是三種方案的結(jié)合
* 1.TaggedPointer(針對類似于 NSNumber的小對象類型)
* 2.NONPOINTER_ISA(64位系統(tǒng)下)
* 第一位的 0或 1代表是純地址型 isa指針,還是 NONPOINTER_ISA指針。
* 第二位,代表是否有關(guān)聯(lián)對象
* 第三位代表是否有 C++代碼。
* 接下來33位代表指向的內(nèi)存地址
* 接下來有 弱引用的標記
* 接下來有是否 delloc的標記....等等
* 3.散列表(引用計數(shù)表、weak表)
* SideTables表在 非嵌入式的64位系統(tǒng)中,有 64張 SideTable表
* 每一張 SideTable主要是由三部分組成。自旋鎖、引用計數(shù)表、弱引用表。
* 全局的 引用計數(shù)之所以不存在同一張表中,是為了避免資源競爭,解決效率的問題。
* 引用計數(shù)表中引入了 分離鎖的概念,將一張表分拆成多個部分,對他們分別加鎖,可以實現(xiàn)并發(fā)操作,提升執(zhí)行效率
搜一下散列表?。?!
59.iOS 內(nèi)存管理面試題(講一下 `@dynamic` 關(guān)鍵字?)
@dynamic 意味著編譯器不會幫助我們自動合成 setter 和 getter 方法。我們需要手動實現(xiàn)、這里就涉及到 Runtime 的動態(tài)添加方法的知識點。
60.iOS 內(nèi)存管理面試題(簡要說一下 `@autoreleasePool` 的數(shù)據(jù)結(jié)構(gòu)?)
簡單說是雙向鏈表,每張鏈表頭尾相接,有 parent、child指針
每創(chuàng)建一個池子,會在首部創(chuàng)建一個 哨兵對象,作為標記
最外層池子的頂端會有一個next指針。當鏈表容量滿了,就會在鏈表的頂端,并指向下一張表。
61.iOS 內(nèi)存管理面試題(訪問 `__weak` 修飾的變量,是否已經(jīng)被注冊在了 `@autoreleasePool` 中?為什么?)
答案是肯定的,__weak修飾的變量屬于弱引用,如果沒有被注冊到 @autoreleasePool中,創(chuàng)建之后也就會隨之銷毀,為了延長它的生命周期,必須注冊到 @autoreleasePool中,以延緩釋放。
62.iOS 內(nèi)存管理面試題(`retain`、`release` 的實現(xiàn)機制?)
1.Retain的實現(xiàn)機制。
SideTable& table = SideTables()[This];//第一層 hash算法,找到 指針變量所對應(yīng)的 sideTable size_t& refcntStorage = table.refcnts[This];//然后再通過一層 hash算法,找到存儲 引用計數(shù)的 size_t refcntStorage += SIZE_TABLE_RC_ONE;//IZE_TABLE_RC_ONE是一個宏定義,實際上是一個值為 4 的偏移量。
2.Release的實現(xiàn)機制。
SideTable& table = SideTables()[This];//第一層 hash算法,找到 指針變量所對應(yīng)的 sideTable size_t& refcntStorage = table.refcnts[This];//然后再通過一層 hash算法,找到存儲 引用計數(shù)的 size_t refcntStorage -= SIZE_TABLE_RC_ONE;//IZE_TABLE_RC_ONE是一個宏定義,實際上是一個值為 4 的偏移量。
二者的實現(xiàn)機制類似,概括講就是通過第一層 hash算法,找到 指針變量所對應(yīng)的 sideTable。然后再通過一層 hash算法,找到存儲 引用計數(shù)的 size_t,然后對其進行增減操作。retainCount不是固定的 1,SIZE_TABLE_RC_ONE是一個宏定義,實際上是一個值為 4 的偏移量。
63.iOS 內(nèi)存管理面試題(MRC(手動引用計數(shù))和ARC(自動引用計數(shù)))
MRC(手動引用計數(shù))和ARC(自動引用計數(shù))
1、MRC:alloc,retain,release,retainCount,autorelease,dealloc
2、ARC:
* ARC是LLVM和Runtime協(xié)作的結(jié)果
* ARC禁止手動調(diào)用retain,release,retainCount,autorelease關(guān)鍵字
* ARC新增weak,strong關(guān)鍵字
3、引用計數(shù)管理:
* alloc: 經(jīng)過一系列函數(shù)調(diào)用,最終調(diào)用了calloc函數(shù),這里并沒有設(shè)置引用計數(shù)為1
* retain: 經(jīng)過兩次哈希查找,找到其對應(yīng)引用計數(shù)值,然后將引用計數(shù)加1(實際是加偏移量)
* release:和retain相反,經(jīng)過兩次哈希查找,找到其對應(yīng)引用計數(shù)值,然后將引用計數(shù)減1
* dealloc:
4、弱引用管理:
* 添加weak變量:通過哈希算法位置查找添加。如果查找對應(yīng)位置中已經(jīng)有了當前對象所對應(yīng)的弱引用數(shù)組,就把新的弱引用變量添加到數(shù)組當中;如果沒有,就創(chuàng)建一個弱引用數(shù)組,并將該弱引用變量添加到該數(shù)組中。
* 當一個被weak修飾的對象被釋放后,weak對象怎么處理的?
清除weak變量,同時設(shè)置指向為nil。當對象被dealloc釋放后,在dealloc的內(nèi)部實現(xiàn)中,會調(diào)用弱引用清除的相關(guān)函數(shù),會根據(jù)當前對象指針查找弱引用表,找到當前對象所對應(yīng)的弱引用數(shù)組,將數(shù)組中的所有弱引用指針都置為nil。
5、自動釋放池:
在當次runloop將要結(jié)束的時候調(diào)用objc_autoreleasePoolPop,并push進來一個新的AutoreleasePool
AutoreleasePoolPage是以棧為結(jié)點通過雙向鏈表的形式組合而成,是和線程一一對應(yīng)的。
內(nèi)部屬性有parent,child對應(yīng)前后兩個結(jié)點,thread對應(yīng)線程 ,next指針指向棧中下一個可填充的位置。
* AutoreleasePool實現(xiàn)原理?
編譯器會將 @autoreleasepool {} 改寫為:
void * ctx = objc_autoreleasePoolPush; {} objc_autoreleasePoolPop(ctx);
objc_autoreleasePoolPush:
把當前next位置置為nil,即哨兵對象,然后next指針指向下一個可入棧位置,
AutoreleasePool的多層嵌套,即每次objc_autoreleasePoolPush,實際上是不斷地向棧中插入哨兵對象。
objc_autoreleasePoolPop:
根據(jù)傳入的哨兵對象找到對應(yīng)位置。
給上次push操作之后添加的對象依次發(fā)送release消息。
回退next指針到正確的位置。
64.iOS 內(nèi)存管理面試題(`BAD_ACCESS` 在什么情況下出現(xiàn)? )
訪問了已經(jīng)被銷毀的內(nèi)存空間,就會報出這個錯誤。
根本原因是有 懸垂指針 沒有被釋放。
65.iOS 內(nèi)存管理面試題(`autoReleasePool` 什么時候釋放?)
[http://www.itdecent.cn/p/73150489071e](http://www.itdecent.cn/p/73150489071e)
66.iOS 內(nèi)存管理面試題( `ARC` 在運行時做了哪些工作?)
* 主要是指 weak關(guān)鍵字。weak修飾的變量能夠在引用計數(shù)為0時被自動設(shè)置成 nil,顯然是有運行時邏輯在工作的。
* 為了保證向后兼容性,ARC在運行時檢測到類函數(shù)中的 autorelease后緊跟其后 retain,此時不直接調(diào)用對象的 autorelease方法,而是改為調(diào)用 objc_autoreleaseReturnValue。
objc_autoreleaseReturnValue會檢視當前方法返回之后即將要執(zhí)行的那段代碼,若那段代碼要在返回對象上執(zhí)行 retain操作,則設(shè)置全局數(shù)據(jù)結(jié)構(gòu)中的一個標志位,而不執(zhí)行 autorelease操作,與之相似,如果方法返回了一個自動釋放的對象,而調(diào)用方法的代碼要保留此對象,那么此時不直接執(zhí)行 retain,而是改為執(zhí)行 objc_retainAoutoreleasedReturnValue函數(shù)。此函數(shù)要檢測剛才提到的標志位,若已經(jīng)置位,則不執(zhí)行 retain操作,設(shè)置并檢測標志位,要比調(diào)用 autorelease和retain更快。
67.iOS 內(nèi)存管理面試題(`ARC` 在編譯時做了哪些工作)
根據(jù)代碼執(zhí)行的上下文語境,在適當?shù)奈恢貌迦?retain,release
68.iOS 內(nèi)存管理面試題(`ARC` 的 `retainCount` 怎么存儲的?)
ARC的 retainCount怎么存儲的?
存在64張哈希表中,根據(jù)哈希算法去查找所在的位置,無需遍歷,十分快捷
散列表(引用計數(shù)表、weak表)
- SideTables表在 非嵌入式的64位系統(tǒng)中,有 64張 SideTable表
- 每一張 SideTable主要是由三部分組成。自旋鎖、引用計數(shù)表、弱引用表。
- 全局的 引用計數(shù)之所以不存在同一張表中,是為了避免資源競爭,解決效率的問題。
- 引用計數(shù)表中引入了 分離鎖的概念,將一張表分拆成多個部分,對他們分別加鎖,可以實現(xiàn)并發(fā)操作,提升執(zhí)行效率
引用計數(shù)表(哈希表)
通過指針的地址,查找到引用計數(shù)的地址,大大提升查找效率
通過 DisguisedPtr(objc_object)函數(shù)存儲,同時也通過這個函數(shù)查找,這樣就避免了循環(huán)遍歷。
69.iOS 內(nèi)存管理面試題(`__weak` 屬性修飾的變量,如何實現(xiàn)在變量沒有強引用后自動置為 `nil` ?)
用的弱引用 - weak表。也是一張 哈希表。
被 weak修飾的指針變量所指向的地址是 key,所有指向這塊內(nèi)存地址的指針會被添加在一個數(shù)組里,這個數(shù)組是 Value。當內(nèi)存地址銷毀,數(shù)組里的所有對象被置為 nil。
70.iOS 內(nèi)存管理面試題( `__weak` 和 `_Unsafe_Unretain` 的區(qū)別?)
weak 修飾的指針變量,在指向的內(nèi)存地址銷毀后,會在 Runtime的機制下,自動置為 nil。
_Unsafe_Unretain不會置為 nil,容易出現(xiàn) 懸垂指針,發(fā)生崩潰。但是 _Unsafe_Unretain比 __weak效率高。
71.iOS 設(shè)計模式面試題(編程中的六大設(shè)計原則?)
背下來?。。?
1.單一職責原則
通俗地講就是一個類只做一件事
* CALayer:動畫和視圖的顯示。
* UIView:只負責事件傳遞、事件響應(yīng)。
2.開閉原則
對修改關(guān)閉,對擴展開放。
要考慮到后續(xù)的擴展性,而不是在原有的基礎(chǔ)上來回修改
3.接口隔離原則
使用多個專門的協(xié)議、而不是一個龐大臃腫的協(xié)議
* UITableviewDelegate
* UITableViewDataSource
4.依賴倒置原則
抽象不應(yīng)該依賴于具體實現(xiàn)、具體實現(xiàn)可以依賴于抽象。
調(diào)用接口感覺不到內(nèi)部是如何操作的
5.里氏替換原則
父類可以被子類無縫替換,且原有的功能不受任何影響
例如 KVO
6.迪米特法則
一個對象應(yīng)當對其他對象盡可能少的了解,實現(xiàn)高聚合、低耦合
72.RSA非對稱加密和MD5加密
補充MD5加密及其應(yīng)用!?。?
RSA非對稱加密
對稱加密[算法]在加密和解密時使用的是同一個秘鑰;而[非對稱加密算法]需要兩個[密鑰]來進行加密和解密,這兩個秘鑰是[公開密鑰](public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰)。
RSA加密
與對稱加密[算法]不同,[非對稱加密算法]需要兩個[密鑰]:[公開密鑰](publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,如果用公開密鑰對數(shù)據(jù)進行加密,只有用對應(yīng)的私有密鑰才能解密;如果用私有密鑰對數(shù)據(jù)進行加密,那么只有用對應(yīng)的公開密鑰才能解密。因為加密和解密使用的是兩個不同的[密鑰],所以這種算法叫作[非對稱加密算法]。
RSA加密原理
RSA是常用的加密模式,其加密原理可用以下的例子進行簡要的論述。
隨機取兩個質(zhì)數(shù)
P = 61; q = 53; N = P * Q = 3233; // E是1-n之間的一個隨機的質(zhì)數(shù) E = 17; // D是通過一系列數(shù)學(xué)運算得出的一個數(shù)字, // 運算方法后續(xù)會附上阮一峰老師的兩篇文章鏈接 // (N,D)(N,E)要滿足可以互相解值運算 // 假如(N,D)是公鑰,(N,E)是私鑰 // 滿足私鑰加密,公鑰解密或者反過來公鑰加密,私鑰解密。 // 也要滿足只知道(N,D)就想知道(N,E),那就要把N這個大的整數(shù)進行因數(shù)分解。 // 因數(shù)分解只能使用暴力窮舉,N越大,相應(yīng)的也就越安全 // 當 N 大到1024位或者2048位時,以目前的技術(shù)破解幾乎不可能,所以很安全
73.簡述 `SSL` 加密的過程用了哪些加密方法,為何這么作?
簡述 SSL加密的過程用了哪些加密方法,為何這么作?
SSL加密的過程之前有些過,此處不再贅述。
SSL加密,在過程中實際使用了 對稱加密和 非對稱加密的結(jié)合。
主要的考慮是先使用 非對稱加密進行連接,這樣做是為了避免中間人攻擊秘鑰被劫持,但是 非對稱加密的效率比較低。所以一旦建立了安全的連接之后,就可以使用輕量的 對稱加密。
74.iOS 性能優(yōu)化面試題
在性能優(yōu)化中一個最具參考價值的屬性是FPS:Frames Per Second,其實就是屏幕刷新率,蘋果的iphone推薦的刷新率是60Hz,也就是說GPU每秒鐘刷新屏幕60次,這每刷新一次就是一幀frame,F(xiàn)PS也就是每秒鐘刷新多少幀畫面。靜止不變的頁面FPS值是0,這個值是沒有參考意義的,只有當頁面在執(zhí)行動畫或者滑動的時候,F(xiàn)PS值才具有參考價值,F(xiàn)PS值的大小體現(xiàn)了頁面的流暢程度高低,當?shù)陀?5的時候卡頓會比較明顯。
圖層混合:
每一個layer是一個紋理,所有的紋理都以某種方式堆疊在彼此的頂部。對于屏幕上的每一個像素,GPU需要算出怎么混合這些紋理來得到像素RGB的值。
當Sa = 0.5時,RGB值為(0.5, 0, 0),可以看出,當兩個不是完全不透明的CALayer覆蓋在一起時,GPU大量做這種復(fù)合操作,隨著這中操作的越多,GPU越忙碌,性能肯定會受到影響。
公式:
R = S + D * ( 1 – Sa )
結(jié)果的顏色是源色彩(頂端紋理)+目標顏色(低一層的紋理)*(1-源顏色的透明度)。
當Sa = 1時,R = S,GPU將不會做任何合成,而是簡單從這個層拷貝,不需要考慮它下方的任何東西(因為都被它遮擋住了),這節(jié)省了GPU相當大的工作量。
一、入門級
1、用ARC管理內(nèi)存
2、在正確的地方使用 reuseIdentifier
3、盡量把views設(shè)置為透明
4、避免過于龐大的XIB
5、不要阻塞主線程
6、在ImageViews中調(diào)整圖片大小。如果要在UIImageView中顯示一個來自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運行中縮放圖片是很耗費資源的,特別是UIImageView嵌套在UIScrollView中的情況下。如果圖片是從遠端服務(wù)加載的你不能控制圖片大小,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后,最好是用background
thread,縮放一次,然后在UIImageView中使用縮放后的圖片。
7、選擇正確的Collection。
* Arrays: 有序的一組值。使用index來lookup很快,使用value lookup很慢, 插入/刪除很慢。
* Dictionaries: 存儲鍵值對。 用鍵來查找比較快。
* Sets: 無序的一組值。用值來查找很快,插入/刪除很快。
8、打開gzip壓縮。app可能大量依賴于服務(wù)器資源,問題是我們的目標是移動設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好。減小文檔的一個方式就是在服務(wù)端和你的app中打開gzip。這對于文字這種能有更高壓縮率的數(shù)據(jù)來說會有更顯著的效用。
iOS已經(jīng)在NSURLConnection中默認支持了gzip壓縮,當然AFNetworking這些基于它的框架亦然。
二、中級
1、重用和延遲加載(lazy load) Views
* 更多的view意味著更多的渲染,也就是更多的CPU和內(nèi)存消耗,對于那種嵌套了很多view在UIScrollView里邊的app更是如此。
* 這里我們用到的技巧就是模仿UITableView和UICollectionView的操作: 不要一次創(chuàng)建所有的subview,而是當需要時才創(chuàng)建,當它們完成了使命,把他們放進一個可重用的隊列中。這樣的話你就只需要在滾動發(fā)生時創(chuàng)建你的views,避免了不劃算的內(nèi)存分配。
2、Cache, Cache, 還是Cache!
* 一個極好的原則就是,緩存所需要的,也就是那些不大可能改變但是需要經(jīng)常讀取的東西。
* 我們能緩存些什么呢?一些選項是,遠端服務(wù)器的響應(yīng),圖片,甚至計算結(jié)果,比如UITableView的行高。
* NSCache和NSDictionary類似,不同的是系統(tǒng)回收內(nèi)存的時候它會自動刪掉它的內(nèi)容。
3、權(quán)衡渲染方法.性能能還是要bundle保持合適的大小。
4、處理內(nèi)存警告.移除對緩存,圖片object和其他一些可以重創(chuàng)建的objects的strong references.
5、重用大開銷對象
6、一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它們,比如從JSON或者XML中解析數(shù)據(jù)。想要避免使用這個對象的瓶頸你就需要重用他們,可以通過添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來實現(xiàn)。
7、避免反復(fù)處理數(shù)據(jù).在服務(wù)器端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要。
8、選擇正確的數(shù)據(jù)格式.解析JSON會比XML更快一些,JSON也通常更小更便于傳輸。從iOS5起有了官方內(nèi)建的JSON deserialization 就更加方便使用了。但是XML也有XML的好處,比如使用SAX 來解析XML就像解析本地文件一樣,你不需像解析json一樣等到整個文檔下載完成才開始解析。當你處理很大的數(shù)據(jù)的時候就會極大地減低內(nèi)存消耗和增加性能。
9、正確設(shè)定背景圖片
* 全屏背景圖,在view中添加一個UIImageView作為一個子View
* 只是某個小的view的背景圖,你就需要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費很多內(nèi)存:
10、減少使用Web特性。想要更高的性能你就要調(diào)整下你的HTML了。第一件要做的事就是盡可能移除不必要的javascript,避免使用過大的框架。能只用原生js就更好了。盡可能異步加載例如用戶行為統(tǒng)計script這種不影響頁面表達的javascript。注意你使用的圖片,保證圖片的符合你使用的大小。
11、Shadow Path 。CoreAnimation不得不先在后臺得出你的圖形并加好陰影然后才渲染,這開銷是很大的。使用shadowPath的話就避免了這個問題。使用shadow path的話iOS就不必每次都計算如何渲染,它使用一個預(yù)先計算好的路徑。但問題是自己計算path的話可能在某些View中比較困難,且每當view的frame變化的時候你都需要去update shadow path.
12、優(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、選擇正確的數(shù)據(jù)存儲選項
* NSUserDefaults的問題是什么?雖然它很nice也很便捷,但是它只適用于小數(shù)據(jù),比如一些簡單的布爾型的設(shè)置選項,再大點你就要考慮其它方式了
* XML這種結(jié)構(gòu)化檔案呢?總體來說,你需要讀取整個文件到內(nèi)存里去解析,這樣是很不經(jīng)濟的。使用SAX又是一個很麻煩的事情。
* NSCoding?不幸的是,它也需要讀寫文件,所以也有以上問題。
* 在這種應(yīng)用場景下,使用SQLite 或者 Core Data比較好。使用這些技術(shù)你用特定的查詢語句就能只加載你需要的對象。
* 在性能層面來講,SQLite和Core Data是很相似的。他們的不同在于具體使用方法。
* Core Data代表一個對象的graph model,但SQLite就是一個DBMS。
* Apple在一般情況下建議使用Core Data,但是如果你有理由不使用它,那么就去使用更加底層的SQLite吧。
* 如果你使用SQLite,你可以用FMDB這個庫來簡化SQLite的操作,這樣你就不用花很多經(jīng)歷了解SQLite的C API了。
三、高級
1、加速啟動時間??焖俅蜷_app是很重要的,特別是用戶第一次打開它時,對app來講,第一印象太太太重要了。你能做的就是使它盡可能做更多的異步任務(wù),比如加載遠端或者數(shù)據(jù)庫數(shù)據(jù),解析數(shù)據(jù)。避免過于龐大的XIB,因為他們是在主線程上加載的。所以盡量使用沒有這個問題的Storyboards吧!一定要把設(shè)備從Xcode斷開來測試啟動速度
2、使用Autorelease Pool。NSAutoreleasePool`負責釋放block中的autoreleased objects。一般情況下它會自動被UIKit調(diào)用。但是有些狀況下你也需要手動去創(chuàng)建它。假如你創(chuàng)建很多臨時對象,你會發(fā)現(xiàn)內(nèi)存一直在減少直到這些對象被release的時候。這是因為只有當UIKit用光了autorelease pool的時候memory才會被釋放。消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時的對象來避免這個行為。
3、選擇是否緩存圖片。常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile,第一種比較常見一點。
4、避免日期格式轉(zhuǎn)換。如果你要用NSDateFormatter來處理很多日期格式,應(yīng)該小心以待。就像先前提到的,任何時候重用NSDateFormatters都是一個好的實踐。如果你可以控制你所處理的日期格式,盡量選擇Unix時間戳。你可以方便地從時間戳轉(zhuǎn)換到NSDate:
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { return[NSDate dateWithTimeIntervalSince1970:timestamp]; }
這樣會比用C來解析日期字符串還快!需要注意的是,許多web API會以微秒的形式返回時間戳,因為這種格式在javascript中更方便使用。記住用dateFromUnixTimestamp之前除以1000就好了。
平時你是如何對代碼進行性能優(yōu)化的?
* 利用性能分析工具檢測,包括靜態(tài) Analyze 工具,以及運行時 Profile 工具,通過Xcode工具欄中Product->Profile可以啟動,
* 比如測試程序啟動運行時間,當點擊Time Profiler應(yīng)用程序開始運行后.就能獲取到整個應(yīng)用程序運行消耗時間分布和百分比.為了保證數(shù)據(jù)分析在統(tǒng)一使用場景真實需要注意一定要使用真機,因為此時模擬器是運行在Mac上,而Mac上的CPU往往比iOS設(shè)備要快。
* 為了防止一個應(yīng)用占用過多的系統(tǒng)資源,開發(fā)iOS的蘋果工程師門設(shè)計了一個“看門狗”的機制。在不同的場景下,“看門狗”會監(jiān)測應(yīng)用的性能。如果超出了該場景所規(guī)定的運行時間,“看門狗”就會強制終結(jié)這個應(yīng)用的進程。開發(fā)者們在crashlog里面,會看到諸如0x8badf00d這樣的錯誤代碼。
優(yōu)化Table View
* 正確使用reuseIdentifier來重用cells
* 盡量使所有的view opaque,包括cell自身
* 如果cell內(nèi)現(xiàn)實的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
減少subviews的數(shù)量
* 盡量不適用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結(jié)果
* 使用rowHeight, sectionFooterHeight和sectionHeaderHeight來設(shè)定固定的高,不要請求delegate
UIImage加載圖片性能問題
* imagedNamed初始化
* imageWithContentsOfFile初始化
* imageNamed默認加載圖片成功后會內(nèi)存中緩存圖片,這個方法用一個指定的名字在系統(tǒng)緩存中查找并返回一個圖片對象.如果緩存中沒有找到相應(yīng)的圖片對象,則從指定地方加載圖片然后緩存對象,并返回這個圖片對象.
* imageWithContentsOfFile則僅只加載圖片,不緩存.
* 加載一張大圖并且使用一次,用imageWithContentsOfFile是最好,這樣CPU不需要做緩存節(jié)約時間.
* 使用場景需要編程時,應(yīng)該根據(jù)實際應(yīng)用場景加以區(qū)分,UIimage雖小,但使用元素較多問題會有所凸顯.
* 不要在viewWillAppear 中做費時的操作:viewWillAppear: 在view顯示之前被調(diào)用,出于效率考慮,方法中不要處理復(fù)雜費時操作;在該方法設(shè)置 view 的顯示屬性之類的簡單事情,比如背景色,字體等。否則,會明顯感覺到 view 有卡頓或者延遲。
* 在正確的地方使用reuseIdentifier:table view用 tableView:cellForRowAtIndexPath:為rows分配cells的時候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell。
* 盡量把views設(shè)置為透明:如果你有透明的Views你應(yīng)該設(shè)置它們的opaque屬性為YES。系統(tǒng)用一個最優(yōu)的方式渲染這些views。這個簡單的屬性在IB或者代碼里都可以設(shè)定。
* 避免過于龐大的XIB:盡量簡單的為每個Controller配置一個單獨的XIB,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨的XIB中去, 當你加載一個引用了圖片或者聲音資源的nib時,nib加載代碼會把圖片和聲音文件寫進內(nèi)存。
* 不要阻塞主線程:永遠不要使主線程承擔過多。因為UIKit在主線程上做所有工作,渲染,管理觸摸反應(yīng),回應(yīng)輸入等都需要在它上面完成,大部分阻礙主進程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作,比如存儲或者網(wǎng)絡(luò)。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 選擇一個子線程來執(zhí)行耗時操作
dispatch_async(dispatch_get_main_queue(), ^{
// 返回主線程更新UI
});
});
* 在Image Views中調(diào)整圖片大小
如果要在UIImageView中顯示一個來自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運行中縮放圖片是很耗費資源的.
講講你用Instrument優(yōu)化動畫性能的經(jīng)歷吧(別問我什么是Instrument)
Apple的instrument為開發(fā)者提供了各種template去優(yōu)化app性能和定位問題。很多公司都在趕feature,并沒有充足的時間來做優(yōu)化,導(dǎo)致不少開發(fā)者對instrument不怎么熟悉。但這里面其實涵蓋了非常完整的計算機基礎(chǔ)理論知識體系,memory,disk,network,thread,cpu,gpu等等,順藤摸瓜去學(xué)習(xí),是一筆巨大的知識財富。動畫性能只是其中一個template,重點還是理解上面問題當中CPU GPU如何配合工作的知識。
facebook啟動時間優(yōu)化
1.瘦身請求依賴
2.UDP啟動請求先行緩存
3.隊列串行化處理啟動響應(yīng)
75.iOS 性能優(yōu)化面試題(光柵化)
光柵化是將幾何數(shù)據(jù)經(jīng)過一系列變換后最終轉(zhuǎn)換為像素,從而呈現(xiàn)在顯示設(shè)備上的過程,光柵化的本質(zhì)是坐標變換、幾何離散化
我們使用 UITableView 和 UICollectionView 時經(jīng)常會遇到各個 Cell 的樣式是一樣的,這時候我們可以使用這個屬性提高性能:
cell.layer.shouldRasterize=YES;
cell.layer.rasterizationScale=[[UIScreenmainScreen]scale];
76.iOS 性能優(yōu)化面試題(如何高性能的畫一個圓角?)
搜索一下直接切圖畫圓角,貝塞爾曲線圓角?。?!
視圖和圓角的大小對幀率并沒有什么卵影響,數(shù)量才是傷害的核心輸出
label.layer.cornerRadius = 5 label.layer.masksToBounds = true
如果能夠只用 cornerRadius解決問題,就不用優(yōu)化。
如果必須設(shè)置 masksToBounds,可以參考圓角視圖的數(shù)量,如果數(shù)量較少(一頁只有幾個)也可以考慮不用優(yōu)化。
UIImageView的圓角通過直接截取圖片實現(xiàn),其它視圖的圓角可以通過 Core Graphics畫出圓角矩形實現(xiàn)。
[圖片上傳失敗...(image-56643b-1581791102540)]
77.iOS 性能優(yōu)化面試題(如何提升 `tableview` 的流暢度?)
本質(zhì)上是降低 CPU、GPU 的工作,從這兩個大的方面去提升性能。
* CPU:對象的創(chuàng)建和銷毀、對象屬性的調(diào)整、布局計算、文本的計算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制
* GPU:紋理的渲染
卡頓優(yōu)化在 CPU 層面
* 盡量用輕量級的對象,比如用不到事件處理的地方,可以考慮使用 CALayer 取代 UIView
* 不要頻繁地調(diào)用 UIView 的相關(guān)屬性,比如 frame、bounds、transform 等屬性,盡量減少不必要的修改
* 盡量提前計算好布局,在有需要時一次性調(diào)整對應(yīng)的屬性,不要多次修改屬性
* Autolayout 會比直接設(shè)置 frame 消耗更多的 CPU 資源
* 圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
* 控制一下線程的最大并發(fā)數(shù)量
* 盡量把耗時的操作放到子線程
* 文本處理(尺寸計算、繪制)
* 圖片處理(解碼、繪制)
卡頓優(yōu)化在 GPU層面
* 盡量避免短時間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進行顯示
* GPU能處理的最大紋理尺寸是 4096x4096,一旦超過這個尺寸,就會占用 CPU 資源進行處理,所以紋理盡量不要超過這個尺寸
* 盡量減少視圖數(shù)量和層次
* 減少透明的視圖(alpha<1),不透明的就設(shè)置 opaque 為 YES
* 盡量避免出現(xiàn)離屏渲染
[iOS 保持界面流暢的技巧](https://links.jianshu.com/go?to=https%3A%2F%2Fblog.ibireme.com%2F2015%2F11%2F12%2Fsmooth_user_interfaces_for_ios%2F)
1.預(yù)排版,提前計算
在接收到服務(wù)端返回的數(shù)據(jù)后,盡量將 CoreText排版的結(jié)果、單個控件的高度、cell整體的高度提前計算好,將其存儲在模型的屬性中。需要使用時,直接從模型中往外取,避免了計算的過程。
盡量少用 UILabel,可以使用 CALayer。避免使用 AutoLayout的自動布局技術(shù),采取純代碼的方式
2.預(yù)渲染,提前繪制
例如圓形的圖標可以提前在,在接收到網(wǎng)絡(luò)返回數(shù)據(jù)時,在后臺線程進行處理,直接存儲在模型數(shù)據(jù)里,回到主線程后直接調(diào)用就可以了
避免使用 CALayer 的 Border、corner、shadow、mask 等技術(shù),這些都會觸發(fā)離屏渲染。
3.異步繪制
4.全局并發(fā)線程
5.高效的圖片異步加載
iOS 性能優(yōu)化面試題(如何優(yōu)化 `APP` 的電量?)
程序的耗電主要在以下四個方面:
* CPU 處理
* 定位
* 網(wǎng)絡(luò)
* 圖像
優(yōu)化的途徑主要體現(xiàn)在以下幾個方面:
* 盡可能降低 CPU、GPU 的功耗。
* 盡量少用 定時器。
* 優(yōu)化 I/O 操作。
* 不要頻繁寫入小數(shù)據(jù),而是積攢到一定數(shù)量再寫入
* 讀寫大量的數(shù)據(jù)可以使用 Dispatch_io ,GCD 內(nèi)部已經(jīng)做了優(yōu)化。
* 數(shù)據(jù)量比較大時,建議使用數(shù)據(jù)庫
* 網(wǎng)絡(luò)方面的優(yōu)化
* 減少壓縮網(wǎng)絡(luò)數(shù)據(jù) (XML -> JSON -> ProtoBuf),如果可能建議使用 ProtoBuf。
* 如果請求的返回數(shù)據(jù)相同,可以使用 NSCache 進行緩存
* 使用斷點續(xù)傳,避免因網(wǎng)絡(luò)失敗后要重新下載。
* 網(wǎng)絡(luò)不可用的時候,不嘗試進行網(wǎng)絡(luò)請求
* 長時間的網(wǎng)絡(luò)請求,要提供可以取消的操作
* 采取批量傳輸。下載視頻流的時候,盡量一大塊一大塊的進行下載,廣告可以一次下載多個
* 定位層面的優(yōu)化
* 如果只是需要快速確定用戶位置,最好用 CLLocationManager 的 requestLocation 方法。定位完成后,會自動讓定位硬件斷電
* 如果不是導(dǎo)航應(yīng)用,盡量不要實時更新位置,定位完畢就關(guān)掉定位服務(wù)
* 盡量降低定位精度,比如盡量不要使用精度最高的 kCLLocationAccuracyBest
* 需要后臺定位時,盡量設(shè)置 pausesLocationUpdatesAutomatically 為 YES,如果用戶不太可能移動的時候系統(tǒng)會自動暫停位置更新
* 盡量不要使用 startMonitoringSignificantLocationChanges,優(yōu)先考慮 startMonitoringForRegion:
* 硬件檢測優(yōu)化
* 用戶移動、搖晃、傾斜設(shè)備時,會產(chǎn)生動作(motion)事件,這些事件由加速度計、陀螺儀、磁力計等硬件檢測。在不需要檢測的場合,應(yīng)該及時關(guān)閉這些硬件
iOS 性能優(yōu)化面試題(如何有效降低 APP 包的大?。浚?
降低包大小需要從兩方面著手
可執(zhí)行文件
* 編譯器優(yōu)化
* Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 設(shè)置為 YES
* 去掉異常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 設(shè)置為 NO, Other C Flags 添加 -fno-exceptions
* 利用 AppCode 檢測未使用的代碼:菜單欄 -> Code -> Inspect Code
* 編寫LLVM插件檢測出重復(fù)代碼、未被調(diào)用的代碼
資源包括 圖片、音頻、視頻 等
* 優(yōu)化的方式可以對資源進行無損的壓縮
* 去除沒有用到的資源: [https://github.com/tinymind/LSUnusedResources](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Ftinymind%2FLSUnusedResources)
iOS 性能優(yōu)化面試題(什么是 離屏渲染?什么情況下會觸發(fā)?該如何應(yīng)對?)
離屏渲染就是在當前屏幕緩沖區(qū)以外,新開辟一個緩沖區(qū)進行操作
離屏渲染出發(fā)的場景有以下:
* 圓角 (maskToBounds并用才會觸發(fā))
* 圖層蒙版
* 陰影
* 光柵化
為什么要避免離屏渲染?
CPUGPU在繪制渲染視圖時做了大量的工作。離屏渲染發(fā)生在 GPU層面上,會創(chuàng)建新的渲染緩沖區(qū),會觸發(fā) OpenGL的多通道渲染管線,圖形上下文的切換會造成額外的開銷,增加 GPU工作量。如果 CPUGPU累計耗時 16.67毫秒還沒有完成,就會造成卡頓掉幀。
圓角屬性、蒙層遮罩都會觸發(fā)離屏渲染。指定了以上屬性,標記了它在新的圖形上下文中,在未愈合之前,不可以用于顯示的時候就出發(fā)了離屏渲染。
* 在OpenGL中,GPU有2種渲染方式
* On-Screen Rendering:當前屏幕渲染,在當前用于顯示的屏幕緩沖區(qū)進行渲染操作
* Off-Screen Rendering:離屏渲染,在當前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進行渲染操作
* 離屏渲染消耗性能的原因
* 需要創(chuàng)建新的緩沖區(qū)
* 離屏渲染的整個過程,需要多次切換上下文環(huán)境,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,又需要將上下文環(huán)境從離屏切換到當前屏幕
* 哪些操作會觸發(fā)離屏渲染?
* 光柵化,layer.shouldRasterize = YES
* 遮罩,layer.mask
* 圓角,同時設(shè)置 layer.masksToBounds = YES、layer.cornerRadius大于0
* 考慮通過 CoreGraphics 繪制裁剪圓角,或者叫美工提供圓角圖片
* 陰影,layer.shadowXXX,如果設(shè)置了 layer.shadowPath 就不會產(chǎn)生離屏渲染
iOS 性能優(yōu)化面試題(如何檢測離屏渲染?)
1、模擬器debug-選中color Offscreen - Renderd離屏渲染的圖層高亮成黃 可能存在性能問題
2、真機Instrument-選中Core Animation-勾選Color Offscreen-Rendered Yellow
離屏渲染的觸發(fā)方式
設(shè)置了以下屬性時,都會觸發(fā)離屏繪制:
1、layer.shouldRasterize(光柵化)
光柵化概念:將圖轉(zhuǎn)化為一個個柵格組成的圖象。
光柵化特點:每個元素對應(yīng)幀緩沖區(qū)中的一像素。
2、masks(遮罩)
3、shadows(陰影)
4、edge antialiasing(抗鋸齒)
5、group opacity(不透明)
6、復(fù)雜形狀設(shè)置圓角等
7、漸變
8、drawRect
例如我們?nèi)粘探?jīng)常打交道的TableViewCell,因為TableViewCell的重繪是很頻繁的(因為Cell的復(fù)用),如果Cell的內(nèi)容不斷變化,則Cell需要不斷重繪,如果此時設(shè)置了cell.layer可光柵化。則會造成大量的離屏渲染,降低圖形性能。
如果將不在GPU的當前屏幕緩沖區(qū)中進行的渲染都稱為離屏渲染,那么就還有另一種特殊的“離屏渲染”方式:CPU渲染。如果我們重寫了drawRect方法,并且使用任何Core Graphics的技術(shù)進行了繪制操作,就涉及到了CPU渲染。整個渲染過程由CPU在App內(nèi)同步地完成,渲染得到的bitmap最后再交由GPU用于顯示。
現(xiàn)在擺在我們面前得有三個選擇:當前屏幕渲染、離屏渲染、CPU渲染,該用哪個呢?這需要根據(jù)具體的使用場景來決定。
盡量使用當前屏幕渲染
鑒于離屏渲染、CPU渲染可能帶來的性能問題,一般情況下,我們要盡量使用當前屏幕渲染。
離屏渲染 VS CPU渲染
由于GPU的浮點運算能力比CPU強,CPU渲染的效率可能不如離屏渲染;但如果僅僅是實現(xiàn)一個簡單的效果,直接使用CPU渲染的效率又可能比離屏渲染好,畢竟離屏渲染要涉及到緩沖區(qū)創(chuàng)建和上下文切換等耗時操作
UIButton 的 masksToBounds = YES又設(shè)置setImage、setBackgroundImage、[button setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"btn_selected"]]];
下發(fā)生離屏渲染,但是[button setBackgroundColor:[UIColor redColor]];是不會出現(xiàn)離屏渲染的
關(guān)于 UIImageView,現(xiàn)在測試發(fā)現(xiàn)(現(xiàn)版本: iOS10),在性能的范圍之內(nèi),給UIImageView設(shè)置圓角是不會觸發(fā)離屏渲染的,但是同時給UIImageView設(shè)置背景色則肯定會觸發(fā).觸發(fā)離屏渲染跟 png.jpg格式并無關(guān)聯(lián)
日常我們使用layer的兩個屬性,實現(xiàn)圓角
imageView.layer.cornerRaidus = CGFloat(10);
imageView.layer.masksToBounds = YES;
這樣處理的渲染機制是GPU在當前屏幕緩沖區(qū)外新開辟一個渲染緩沖區(qū)進行工作,也就是離屏渲染,這會給我們帶來額外的性能損耗。如果這樣的圓角操作達到一定數(shù)量,會觸發(fā)緩沖區(qū)的頻繁合并和上下文的的頻繁切換,性能的代價會宏觀地表現(xiàn)在用戶體驗上——掉幀
iOS 性能優(yōu)化面試題(怎么檢測圖層混合?)
1、模擬器debug- 選中 color blended layers紅色區(qū)域表示圖層發(fā)生了混合
2、Instrument-選中Core Animation-勾選Color Blended Layers
避免圖層混合:
1、確??丶膐paque屬性設(shè)置為true,確保backgroundColor和父視圖顏色一致且不透明
2、如無特殊需要,不要設(shè)置低于1的alpha值
3、確保UIImage沒有alpha通道
UILabel圖層混合解決方法:
iOS8以后設(shè)置背景色為非透明色并且設(shè)置label.layer.masksToBounds=YES讓label只會渲染她的實際size區(qū)域,就能解決UILabel的圖層混合問題
iOS8 之前只要設(shè)置背景色為非透明的就行
為什么設(shè)置了背景色但是在iOS8上仍然出現(xiàn)了圖層混合呢?
UILabel在iOS8前后的變化,在iOS8以前,UILabel使用的是CALayer作為底圖層,而在iOS8開始,UILabel的底圖層變成了_UILabelLayer,繪制文本也有所改變。在背景色的四周多了一圈透明的邊,而這一圈透明的邊明顯超出了圖層的矩形區(qū)域,設(shè)置圖層的masksToBounds為YES時,圖層將會沿著Bounds進行裁剪 圖層混合問題解決了
2020-02-16
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。