如何重寫UIButton來實現(xiàn)自定義image和title的布局?
- 重寫
UIButton
的titleRectForContentRect:
的方法返回 title 的frame
; - 重寫
UIButton
的imageRectForContentRect:
的方法返回 image 的frame
畜隶。
Method浸遗,SEL 和 IMP
-
IMP
跛锌,函數(shù)指針,指向方法的具體實現(xiàn)郑藏; -
SEL
译秦,方法選擇器,是一個C語言字符串阁吝。運行時系統(tǒng)可以根據(jù)SEL
在類對象的方法列表中查找對應(yīng)的IMP
突勇; -
Method
,方法定躏,由SEL
痊远、IMP
和參數(shù)類型(方法參數(shù)類型和返回值類型)組成。
方法交換的原理
通過使用void method_exchangeImplementations(Method m1, Method m2)
函數(shù)去交換兩個Method
實例m1
和m2
的IMP
函數(shù)指針逞姿。之后欲间,通過m1
方法的SEL
去進行消息傳遞時,會調(diào)用m2
方法原本的IMP
函數(shù)指針所指向的函數(shù)达址;通過m2
方法的SEL
去進行消息傳遞時疆虚,會調(diào)用m1
方法原本的IMP
函數(shù)指針所指向的函數(shù)译蒂。
當我們 hook 某個類的由其父類實現(xiàn)的方法時柔昼,例如,創(chuàng)建一個BaseViewController
類激率,hook 由其父類UIViewController
實現(xiàn)的viewWillAppear
方法,代碼如下:
@interface BaseViewController : UIViewController
@end
@implementation BaseViewController
+ (void)load
{
Method original = class_getInstanceMethod([self class], @selector(viewWillAppear:));
Method replacement = class_getInstanceMethod([self class], @selector(xxx_viewWillAppear:));
method_exchangeImplementations(original, replacement);
}
- (void)xxx_viewWillAppear:(BOOL)animated
{
[self xxx_viewWillAppear:animated];
NSLog(@"test");
}
在進行方法交換之后嘉冒,在調(diào)用父類UIViewController
的viewWillAppear:
方法時顶籽,會根據(jù)SEL
到UIViewController
及其父類的方法列表中去查找viewWillAppear:
方法,并調(diào)用viewWillAppear:
方法的IMP
函數(shù)指針所指向的函數(shù)镊绪。這時,實際上調(diào)用的是BaseViewController
的xxx_viewWillAppear:
方法的原始對應(yīng)函數(shù)榄鉴。而在BaseViewController
的xxx_viewWillAppear:
方法的原始對應(yīng)函數(shù)實現(xiàn)中,又會執(zhí)行[self xxx_viewWillAppear:animated]
方法减余。
這時,會再一次進入到消息傳遞流程抒抬。運行時系統(tǒng)會根據(jù)SEL
到UIViewController
及其父類(self
屬于UIViewController
類擦剑,而不是BaseViewController
類)的方法列表中查找xxx_viewWillAppear :
方法爬坑,但是UIViewController
類及其父類并沒有實現(xiàn)xxx_viewWillAppear :
方法售担,所以方法查找會失敗岩四,導(dǎo)致 app 運行崩潰。
為了不影響父類UIViewController
調(diào)用viewWillAppear:
方法末捣,我們可以在執(zhí)行method_exchangeImplementations
函數(shù)之前莽红,先調(diào)用class_addMethod
函數(shù)給BaseViewController
類動態(tài)添加一個viewWillAppear :
方法(這樣做相當于子類重寫了父類的方法)并且設(shè)置該方法的IMP
函數(shù)指針為xxx_viewWillAppear :
方法的原始函數(shù)指針。接著网棍,調(diào)用class_replaceMethod
函數(shù)去將xxx_viewWillAppear:
方法的函數(shù)指針替換為viewWillAppear :
方法的原始函數(shù)指針。這樣惑畴,父類UIViewController
調(diào)用viewWillAppear:
方法時,就不會受到影響了到踏。只有BaseViewController
調(diào)用viewWillAppear:
時,才會去執(zhí)行xxx_viewWillAppear:
方法菩彬。
+ (void)load
{
Method original = class_getInstanceMethod([self class], @selector(viewWillAppear:));
Method replacement = class_getInstanceMethod([self class], @selector(xxx_viewWillAppear:));
BOOL result = class_addMethod([self class], @selector(testMethod), method_getImplementation(replacement), method_getTypeEncoding(replacement));
if (result)
{
class_replaceMethod([self class], @selector(test_testMethod), method_getImplementation(original), method_getTypeEncoding(original));
}
else
{
method_exchangeImplementations(original, replacement);
}
}
由于類的+initialize
方法可能被多次執(zhí)行,并且 category 實現(xiàn)的+initialize
方法會覆蓋掉宿主類實現(xiàn)的+initialize
方法脱羡,我們選擇在類的+load
方法中去執(zhí)行方法交換。
雖然系統(tǒng)只會調(diào)用一次類的+load
方法,但是為了防止開發(fā)者自行再次調(diào)用類的+load
方法侨舆,需要使用dispatch_once
函數(shù)去保證方法交換操作只執(zhí)行一次。
如何hook一個類的某個實例對象的方法而不會影響該類的其他實例對象?
使用 runtime 提供的 API 在運行時動態(tài)創(chuàng)建一個繼承自實例對象所屬類的子類愁铺,并向運行時系統(tǒng)注冊這個子類,然后重寫這個子類的對應(yīng)方法闻鉴,最后將這個實例對象的 cls 指針指向這個子類帜讲。
NSString 和 NSDictionary 類型的屬性為什么要用 copy 修飾?
NSMutableString
是NSString
的子類椒拗,在開發(fā)過程中似将,有可能誤將NSMutableString
類型的字符串對象賦給了NSString
類型的屬性腋舌。如果后續(xù)修改了可變字符串對象的內(nèi)容授艰,屬性值也會被修改洲押,這可能會導(dǎo)致程序出錯。為了避免出現(xiàn)這種錯誤回怜,需要使用copy
去修飾NSString
類型的屬性。
如何銷毀單例?
將onceToken
置為0
嚼酝,并將單例對象置為nil
驾荣。
dispatch_once實現(xiàn)原理
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *))
{
volatile long *vval = val;
if (dispatch_atomic_cmpxchg(val, 0l, 1l))
{
func(ctxt); // block真正執(zhí)行
dispatch_atomic_barrier();
*val = ~0l;
}
else
{
do
{
_dispatch_hardware_pause();
} while (*vval != ~0l);
dispatch_atomic_barrier();
}
}
dispatch_once
函數(shù)的內(nèi)部實現(xiàn)使用了原子操作來同步地判斷并修改dispatch_once_t
的值。如果dispatch_once_t
的值為0l
,就將dispatch_once_t
的值改為1l
十办,并執(zhí)行block。block執(zhí)行完畢后,會將dispatch_once_t
的值改為~0l
惧笛;如果dispatch_once_t
的值不為0l
昌渤,則說明其他線程正在執(zhí)行block或者block已經(jīng)執(zhí)行過一次了。此時已烤,dispatch_once
函數(shù)就會進入忙等狀態(tài)剥哑,直到dispatch_once_t
的值變?yōu)?code>~0l大审。
NSNotificationCenter通知中心的實現(xiàn)原理
NSNotificationCenter
內(nèi)部維護著一個哈希表,其 key 是通知名稱士败,value 是一個數(shù)組,數(shù)組中保存著一組包含被通知對象和回調(diào)方法的數(shù)據(jù)結(jié)構(gòu)囚戚。發(fā)送通知時皮璧,會根據(jù)通知名稱查找對應(yīng)的數(shù)組,然后遍歷數(shù)組并調(diào)用被通知對象的回調(diào)方法。
遠程推送(APNs)的原理
- 應(yīng)用程序使用 UDID 和 BundleID 向 iOS 注冊消息推送;
- iOS 向 APNs 服務(wù)器請求 device token,應(yīng)用程序接收 device token奶浦;
- 應(yīng)用程序?qū)?device token 發(fā)送給應(yīng)用程序的 push 服務(wù)器所踊;
- 應(yīng)用程序的 push 服務(wù)器將要發(fā)送的消息和 device token 打包發(fā)送給 APNs 服務(wù)器;
- APNs 服務(wù)器收到消息之后珠十,根據(jù) device token 找到對應(yīng)的注冊設(shè)備说庭,然后將內(nèi)容推送給目標設(shè)備滔蝉。
如何調(diào)試 EXC_BAD_ACCESS 崩潰?
訪問一個已經(jīng)釋放內(nèi)存的對象時肮韧,可能會引發(fā) EXC_BAD_ACCESS 崩潰融蹂。
對象的dealloc
方法只是告訴系統(tǒng)其不再使用這塊內(nèi)存了,系統(tǒng)并沒有使這塊內(nèi)存不可訪問弄企。因而超燃,在訪問一個已經(jīng)釋放內(nèi)存的對象時,如果這塊內(nèi)存還沒有被覆蓋拘领,原來的數(shù)據(jù)保存完好意乓,那么可能就不會立即引發(fā)崩潰。只有當這塊內(nèi)存已經(jīng)被覆蓋约素,并寫上了不可訪問的數(shù)據(jù)之后届良,才會立即引發(fā)崩潰。
勾選 Xcode 的Product
->Scheme
->Edit Scheme
->Run
->Diagnostics
->Memory Management
->Zombie Objects
選項后圣猎,當對象的引用計數(shù)為0時士葫,系統(tǒng)會將對象轉(zhuǎn)換為一個僵尸對象。向僵尸對象發(fā)送消息時送悔,就會立即引發(fā) EXC_BAD_ACCESS 崩潰慢显。同時,Xcode 控制臺會輸出訪問已釋放對象的具體代碼欠啤;
勾選 Xcode 的Memory Management
->Malloc Scribble
選項荚藻,對象釋放內(nèi)存后,這塊內(nèi)存會被立即填上不可訪問的數(shù)據(jù)洁段,當再次訪問這塊內(nèi)存時应狱,會立即引發(fā) EXC_BAD_ACCESS 崩潰。
如果僵尸對象不能幫助解決問題眉撵,則可以使用 Xcode 的Product
->Analyze
選項去靜態(tài)分析代碼侦香,找出所有可能產(chǎn)生野指針的代碼,然后再進一步分析纽疟。
更多信息罐韩,可以參看:如何定位Obj-C野指針隨機Crash 和 如何處理iOS崩潰crash大解析。
如何實現(xiàn)多個任務(wù)執(zhí)行完畢后再執(zhí)行其他任務(wù)污朽?
- 使用
dispatch_async_group
函數(shù)向并行隊列中添加多個任務(wù)散吵,并使用dispatch_group_notify
函數(shù)向并行隊列中添加前面任務(wù)執(zhí)行完畢后才開始執(zhí)行的任務(wù)。如果任務(wù)內(nèi)部有自己的子線程蟆肆,例如矾睦,使用AFNetworking發(fā)送網(wǎng)絡(luò)請求,則需要使用dispatch_group_enter
和dispatch_group_leave
函數(shù)標識任務(wù)是否執(zhí)行完畢炎功; - 使用
NSOperation
枚冗,配置好NSOperation
之間的依賴后,將它們添加到NSOperationQueue
中蛇损。如果任務(wù)內(nèi)部有自己的子線程赁温,則需要使用自定義NSOperation
來自行控制任務(wù)是否執(zhí)行完畢; - 使用
dispatch_async
函數(shù)向并行隊列中添加多個任務(wù)淤齐,使用dispatch_barrier_async
函數(shù)向并行隊列中添加前面任務(wù)執(zhí)行完畢后才開始執(zhí)行的任務(wù)股囊。如果任務(wù)內(nèi)部有自己的子線程,這種方式就會失效更啄。
數(shù)組去重方式
- 使用數(shù)組創(chuàng)建一個
NSSet
稚疹,NSSet
會自動去重,NSSet
的allObjects
就是去重之后的數(shù)組祭务; - 創(chuàng)建一個可變數(shù)組内狗,然后遍歷不可變數(shù)組的元素,使用可變數(shù)組的
containsObject:
方法判斷是否包含該元素义锥,不包含就將其添加到可變數(shù)組中其屏; - 創(chuàng)建一個
NSDictionary
,遍歷數(shù)組缨该,將數(shù)組中的元素作為key添加到字典中偎行,最后NSDictionary
的allKeys
就是去重之后的數(shù)組; - 使用KVC的數(shù)組運算符去重贰拿,
resultArray = [array valueForKeyPath:@"@distinctUnionOfObjects.self"]
蛤袒。
如何尋找出兩個視圖的最近的公共父視圖?
查找視圖的所有父視圖:
- (NSArray<UIView *> *)superviewsOfView:(UIView *)view
{
if (view == nil) return @[];
NSMutableArray *array = [[NSMutableArray alloc] init];
while(view != nil)
{
if (view.superview)
{
[array addObject:view.superview];
}
view = view.superview;
}
return array;
}
查找兩個數(shù)組中第一個重復(fù)的元素:
- 遍歷數(shù)組A膨更,看數(shù)組B是否包含數(shù)組A的元素(
[array containsObject:object]
)直焙,第一次查找到的數(shù)組B也包含的元素就是兩個視圖最近的公共父視圖产上。這種方法使用了雙重遍歷,其時間復(fù)雜度為O(N^2)介褥; - 根據(jù)數(shù)組B創(chuàng)建一個
NSSet
集合,遍歷數(shù)組A面粮,看NSSet
集合是否包含數(shù)組A的元素([set containsObject:object]
),第一次查找到的NSSet
集合也包含的元素就是兩個視圖最近的公共父視圖。NSSet
集合內(nèi)部是用哈希表實現(xiàn)的薄料,其時間復(fù)雜度為O(N);
alloc 方法泵琳、 init 方法和 new 方法分別做了什么事情摄职?
// 類
type struct objc_class *Class
// 類的實例
struct objc_object {
Class isa;
......
}
// 一個指向類的實例的指針
typedef struct objc_object *id;
alloc
alloc
方法內(nèi)部會調(diào)用_objc_rootAlloc
函數(shù),_objc_rootAlloc
函數(shù)會調(diào)用callAlloc
函數(shù)获列,callAlloc
函數(shù)會調(diào)用_objc_rootAllocWithZone
函數(shù)谷市,_objc_rootAllocWithZone
函數(shù)會調(diào)用_class_creatInstanceFormZone
函數(shù)去創(chuàng)建實例。_class_creatInstanceFormZone
函數(shù)主要做了以下事情:
- 調(diào)用類對象的
alignedInstanceSize
函數(shù)計算需要為實例對象分配的內(nèi)存大小击孩。由于64位架構(gòu)下是以 16 字節(jié)為單位來進行內(nèi)存分配的迫悠,而實例對象實際占用的內(nèi)存可能不足 16 字節(jié)或者大于 16 字節(jié),所以需要進行內(nèi)存對齊巩梢。(不足 16 字節(jié)的及皂,則對齊為 16 字節(jié);大于 16 字節(jié)的且改,則對齊為 16 字節(jié)的整數(shù)倍验烧。) - 調(diào)用
calloc
函數(shù)申請內(nèi)存。 - 調(diào)用
objc_constructInstance
函數(shù)構(gòu)造實例對象又跛,這一步主要是初始化isa
碍拆,并調(diào)用實例對象的 C++ 構(gòu)造函數(shù)(如果存在的話),最后慨蓝,返回一個指向所申請內(nèi)存的首地址的id
類型的指針感混。
init
NSObject
的init
方法默認會調(diào)用_objc_rootInit
函數(shù),_objc_rootInit
函數(shù)會直接返回指向?qū)ο髢?nèi)存首地址的id
類型指針礼烈,而不會做其他任何事情弧满。
子類可以重寫init
方法來對實例對象進行初始化設(shè)置。
new
new
方法會調(diào)用callAlloc
函數(shù)此熬,然后再調(diào)用init
方法庭呜。
Objective-C 對象占用的內(nèi)存大小
在64位架構(gòu)下,int
類型占用 4 個字節(jié)犀忱,long
類型占用 8 個字節(jié)募谎,float
類型占用 4 個字節(jié),double
類型占用 8 個字節(jié)阴汇,BOOL
類型占用 1 個字節(jié)数冬,char
類型占用 1 個字節(jié),void *
類型占用 8 個字節(jié)搀庶。
Objective-C 對象本質(zhì)上是一個結(jié)構(gòu)體拐纱。未對齊的結(jié)構(gòu)體占用的內(nèi)存大小是其每個數(shù)據(jù)成員所占用的內(nèi)存大小之和铜异,編譯器在編譯時需要對未對齊結(jié)構(gòu)體進行內(nèi)存對齊。
這是因為 CPU 在存取數(shù)據(jù)時并不是每次只存取 1 個字節(jié)的數(shù)據(jù)秸架,而是一塊一塊的進行存取揍庄。塊的大小被稱為內(nèi)存存取粒度,內(nèi)存存取粒度可以為 2咕宿,4,8蜡秽,16 字節(jié)府阀。當存取未對齊數(shù)據(jù)時,需要進行 2 次存取操作芽突。而當存取已對齊數(shù)據(jù)時试浙,只需要進行 1 次存取操作。由于每次內(nèi)存存取都會產(chǎn)生一個固定的開銷寞蚌,所以減少內(nèi)存存取次數(shù)就可以提升程序的性能田巴。所以編譯器在編譯時,會對結(jié)構(gòu)體進行內(nèi)存對齊挟秤。
結(jié)構(gòu)體的內(nèi)存對齊規(guī)則如下:
- 結(jié)構(gòu)體中第一個數(shù)據(jù)成員的起始位置為0壹哺,之后每個數(shù)據(jù)成員的起始位置是該數(shù)據(jù)成員本身尺寸的整數(shù)倍。
- 結(jié)構(gòu)體的尺寸是最大的基本數(shù)據(jù)類型數(shù)據(jù)成員尺寸的整數(shù)倍艘刚。
- 結(jié)構(gòu)體作為數(shù)據(jù)成員時管宵,結(jié)構(gòu)體數(shù)據(jù)成員的起始位置就是該結(jié)構(gòu)體數(shù)據(jù)成員中尺寸最大的基本數(shù)據(jù)類型數(shù)據(jù)成員尺寸的整數(shù)倍。而結(jié)構(gòu)體的尺寸則是其所有結(jié)構(gòu)體數(shù)據(jù)成員的與自身中的最大的基本數(shù)據(jù)類型數(shù)據(jù)成員尺寸的整數(shù)倍攀甚。
由于每個 Objective-C 對象都包含一個isa
指針箩朴,isa
指針是void *
類型(對象類型的成員變量是一個指針,指針屬于void *
類型)秋度,在64位架構(gòu)下占用 8 個字節(jié)炸庞,所以64位架構(gòu)下的 Objective-C 對象占用的內(nèi)存大小都是 8 字節(jié)的整數(shù)倍。
Objective-C 類中定義的屬性順序會在編譯時進行優(yōu)化調(diào)整荚斯,其調(diào)整規(guī)則就是先按數(shù)據(jù)類型的尺寸從小到大進行排列埠居,相同尺寸的數(shù)據(jù)成員則按字母順序進行排列。另外事期,Objective-C 類轉(zhuǎn)換成結(jié)構(gòu)體之后拐格,繼承自父類的屬性會排在最前面,之后才是自己定義的屬性刑赶,所以 Objective-C 類的第一個數(shù)據(jù)成員始終是isa
指針捏浊。
有關(guān)內(nèi)存對齊的更多信息,可以參看 iOS 內(nèi)存字節(jié)對齊撞叨,iOS開發(fā)之內(nèi)存對齊金踪,一個OC對象在內(nèi)存中的布局&&占用多少內(nèi)存浊洞,iOS結(jié)構(gòu)體尺寸。
內(nèi)存平移問題:執(zhí)行以下 viewDidLoad 方法后胡岔,程序是否會崩潰法希?兩種調(diào)用方式的輸出結(jié)果又是什么?
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation NSObject
- (void)doSomething
{
NSLog(@"%@", self.name);
}
@end
struct TestStruct {
int a;
};
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// struct TestStruct a = {100};
// 調(diào)用方式 1
Class cls = [Person class];
void *kc = &cls;
[(__bridge id)kc doSomething];
// 調(diào)用方式 2
Person *person = [Person alloc];
[person doSomething]
}
@end
調(diào)用[Person alloc]
方法后靶瘸,會創(chuàng)建一個實例對象苫亦,該實例對象存儲在堆區(qū),該實例對象第一個屬性是指向其類對象的isa
指針怨咪,isa
指針占用 8 個字節(jié)(64位架構(gòu)下)屋剑。person
是一個指向該實例對象的指針變量,占用 8 個字節(jié)诗眨。執(zhí)行[person doSomething]
方法時唉匾,會先獲取person
指針指向的實例對象,并從實例對象所在內(nèi)存空間的起始地址開始匠楚,讀取 8 個字節(jié)的數(shù)據(jù)巍膘,這 8 個字節(jié)的數(shù)據(jù)就是實例對象的isa
指針的值,然后使用這個isa
指針去查找doSomething
方法的函數(shù)指針芋簿。
cls
是一個指向Person
類對象的指針峡懈,占用 8 個字節(jié)。kc
是一個指向cls
指針的指針与斤,占用 8 個字節(jié)逮诲。當執(zhí)行[(__bridge id)kc doSomething]
方法時,會首先獲取到cls
指針幽告,然后從cls
指針所在內(nèi)存空間的起始地址開始梅鹦,讀取 8 個字節(jié)的數(shù)據(jù),這 8 個字節(jié)的數(shù)據(jù)就是cls
指針的值冗锁,然后使用這個cls
指針去查找doSomething
方法的函數(shù)指針齐唆。
因此,以上兩種調(diào)用方式都是可以成功調(diào)用doSomething
方法的冻河。
使用[person doSomething]
方式調(diào)用時箍邮,執(zhí)行self.name
時,會從實例對象所在內(nèi)存空間的起始地址偏移 8 個字節(jié)開始叨叙,讀取 8 個字節(jié)的數(shù)據(jù)锭弊,這 8 個字節(jié)的數(shù)據(jù)就是name
的值。由于并沒有設(shè)置name
屬性的值擂错,所以打印結(jié)果為null
味滞。
使用[(__bridge id)kc doSomething]
方式調(diào)用時,執(zhí)行self.name
時,會從cls
指針所在內(nèi)存空間的起始地址偏移 8 個字節(jié)開始剑鞍,讀取 8 個字節(jié)的數(shù)據(jù)昨凡。由于cls
指針只占用了 8 個字節(jié)的內(nèi)存空間,所以讀取的 8 個字節(jié)數(shù)據(jù)會是其他變量的數(shù)據(jù)內(nèi)容蚁署。
在執(zhí)行函數(shù)時便脊,會生成一個棧幀,棧幀就是一個函數(shù)執(zhí)行的環(huán)境光戈,其包含有函數(shù)參數(shù)哪痰、函數(shù)的局部變量、函數(shù)的返回地址久妆,棧是從高地址向低地址擴展的晌杰。在執(zhí)行過程中,函數(shù)的參數(shù)和局部變量會被壓入到棧中镇饺,所以viewDidLoad
方法對應(yīng)的棧從高地址到低地址依次存放的是self(指向 viewController 對象的指針)
->_cmd(指向 sel 的指針)
->objc_super 結(jié)構(gòu)體
->cls 指針
->kc 指針
->person 指針
乎莉。(self
和_cmd
是viewDidLoad
方法的參數(shù)送讲,super
本質(zhì)上是一個objc_super
結(jié)構(gòu)體)
使用
calloc
函數(shù)創(chuàng)建的實例對象和類對象存放在堆區(qū)奸笤,局部變量(函數(shù)內(nèi)部的指針和結(jié)構(gòu)體)存放在棧區(qū)。
從cls
指針所在內(nèi)存空間的結(jié)束地址開始哼鬓,讀取 8 個字節(jié)的數(shù)據(jù)监右,這 8 個字節(jié)的數(shù)據(jù)是objc_super
結(jié)構(gòu)體的數(shù)據(jù)內(nèi)容。objc_super
結(jié)構(gòu)體第一個數(shù)據(jù)成員是id
類型的receiver
指針异希,其指向 viewController 對象健盒,第二個數(shù)據(jù)成員是Class
類型的super_class
指針,其指向 viewController 對象的父類類對象称簿。因此扣癣,objc_super
結(jié)構(gòu)體所在內(nèi)存空間從低地址到高地址依次存放的是receiver
指針和super_class
指針,讀取的 8 個字節(jié)數(shù)據(jù)就是receiver
指針憨降,最后輸出結(jié)果就是viewController
對象的地址父虑。
如果Person
類的name
屬性是一個int
類型,就會讀取 4 個字節(jié)的數(shù)據(jù)授药,這 4 個字節(jié)的數(shù)據(jù)只是receiver
指針的一部分士嚎,所以輸出結(jié)果就是這 4 個字節(jié)的數(shù)據(jù)轉(zhuǎn)換為int
類型后的值。如果我們在[super viewDidLoad]
后面添加代碼去創(chuàng)建一個結(jié)構(gòu)體悔叽,這個結(jié)構(gòu)體只包含一個int
類型的數(shù)據(jù)成員莱衩,該數(shù)據(jù)成員的值為100,那么讀取到的 4 個字節(jié)數(shù)據(jù)娇澎,就是這個結(jié)構(gòu)體的int
類型數(shù)據(jù)成員的值笨蚁,最后輸出結(jié)果就是100。
不使用其他變量,如何實現(xiàn)交換兩個變量的值赚窃?
int a = 10;
int b = 15;
b = a + b;
a = b - a; // a + b - a
b = b - a; // a + b - b
使用遞歸求1到n的和
int sum(int n)
{
if(n == 1)
{
return 1;
}else
{
return n + sum(n-1);
}
}
NSCache 和 NSMutableDictionary
-
NSCache
和NSMutableDictionary
提供的功能是相同的册招; -
NSCache
是線程安全的,NSMutableDictionary
是線程不安全的勒极; - 在應(yīng)用程序內(nèi)存不足時是掰,
NSCache
會自動釋放一部分緩存。還可以給NSCache
設(shè)置一個最大緩存容量辱匿,當緩存超過了最大緩存容量键痛,NSCache
會自動釋放一部分緩存。 -
NSMutableDictionary
的key對象必須實現(xiàn)NSCopying
協(xié)議匾七,而NSCache
的key對象不需要絮短。
super 和 self 的區(qū)別
-
self
是self
所在方法的調(diào)用對象,使用self
去調(diào)用某個方法時昨忆,會使用objc_msgSend
函數(shù)從self
的類對象的方法列表中開始查找該方法的方法實現(xiàn)丁频。 -
super
是一個objc_super
結(jié)構(gòu)體,其包含一個消息接收者對象邑贴,是super
所在方法的調(diào)用對象席里,也就是self
。使用super
去調(diào)用某個方法時拢驾,會使用objc_msgSendSuper
函數(shù)從objc_super
結(jié)構(gòu)體中的消息接收者對象的父類類對象的方法列表中開始查找該方法的方法實現(xiàn)奖磁。找到方法實現(xiàn)后,還是會使用這個消息接收者對象去調(diào)用該方法繁疤。
[self class]
和[super class]
的執(zhí)行結(jié)果是相同的咖为,[self superClass]
和[super superClass
的執(zhí)行結(jié)果也是相同的。因為父類和子類的class
方法的方法實現(xiàn)是一樣的稠腊,都是根據(jù)消息接收者對象的isa
指針去獲取類對象躁染,而它們的消息接收者對象都是self
。
nil架忌、Nil吞彤、NULL 和 [NSNull null] 之間的區(qū)別
-
nil
是一個空實例對象; -
Nil
是一個空類對象鳖昌; -
NULL
是一個非對象類型的空值备畦; -
[NSNull null]
是一個代表空值的單例對象,添加到字典和數(shù)組中的對象不能為nil
许昨,但可以為[NSNull null]
懂盐。
iOS為什么只能在主線程操作UI?
UIKit框架不是線程安全的糕档,如果同時在多個線程操作UI莉恼,可能會出現(xiàn)一些問題拌喉。例如,在一個線程中遍歷當前的視圖層時俐银,在另一個線程對視圖層執(zhí)行了更改操作尿背,這可能會引發(fā)崩潰。
內(nèi)存調(diào)優(yōu)
使用 Instruments 中的 Allocations 查看應(yīng)用程序的內(nèi)存占用捶惜,使用 Leaks 檢測內(nèi)存泄漏田藐。
主要有以下幾方面的原因?qū)е聝?nèi)存占用高:
- 使用了不合理的 API,例如:使用
[UIImage imageNamed:]
從給定的文件加載圖片數(shù)據(jù)時吱七,系統(tǒng)會緩存創(chuàng)建的UIImage
對象汽久,且沒有提供 API 進行清理。而使用[UIImage imageWithContentsOfFile:]
從給定的文件加載圖片數(shù)據(jù)時踊餐,系統(tǒng)不會緩存創(chuàng)建的UIImage
對象景醇,當UIImage
對象沒有再使用后,會釋放掉這個UIImage
對象吝岭; - 沒必要常駐內(nèi)存的對象三痰,被實現(xiàn)為常駐內(nèi)存;
- 數(shù)據(jù)模型中存在多余的字段窜管;
- 循環(huán)引用導(dǎo)致的內(nèi)存泄漏散劫。
id 、 NSObject * 和 instancetype
-
id
和NSObject *
都可以指向任何類型的對象微峰; -
NSObject *
在指向?qū)ο髸r舷丹,需要強制進行類型轉(zhuǎn)換抒钱; -
id
在指向?qū)ο髸r不用強制進行類型轉(zhuǎn)換蜓肆,可以直接使用; -
instancetype
只能用作方法的返回值類型谋币,表示以方法所在類的類型作為返回值類型仗扬。
數(shù)組和鏈表的區(qū)別
- 數(shù)組:數(shù)組是一塊連續(xù)的內(nèi)存空間,數(shù)組元素在內(nèi)存上連續(xù)存放蕾额,可以通過索引查找元素早芭;插入和刪除操作會移動大量元素,比較適用于元素很少變化的情況诅蝶。
- 鏈表:鏈表中的元素在內(nèi)存中不是連續(xù)存儲的退个,查找較慢,插入和刪除操作只需要對元素指針重新賦值调炬,效率更高语盈。
組件化
什么是組件化
將項目中的業(yè)務(wù)和基礎(chǔ)功能剝離出來,并劃分為一個個相互獨立的模塊缰泡,然后通過 cocoapods 來管理這些模塊刀荒。
組件化的優(yōu)點
沒有組件化的項目,業(yè)務(wù)模塊之間互相引入,耦合嚴重缠借。每次發(fā)版時干毅,都要對所有業(yè)務(wù)進行回歸測試。而且業(yè)務(wù)之間相互依賴泼返,可能會導(dǎo)致一個業(yè)務(wù)只能在另一個業(yè)務(wù)開發(fā)完成后才能開始開發(fā)硝逢。另外,每次功能開發(fā)完成后準備提交代碼時绅喉,往往有其他工程師提交了代碼趴捅,需要重新拉去代碼合并后再提交,即使開發(fā)一個很小的功能霹疫,也需要在整個工程里做編譯和調(diào)試拱绑,效率較低。
項目完成組件化之后丽蝎,會帶來如下效果:
- 加快編譯速度猎拨,可以把不會經(jīng)常變動的組件做成靜態(tài)庫,同時每個組件可以獨立編譯屠阻,不依賴于主工程或者其他組件红省;
- 每個組件都可以選擇自己擅長的開發(fā)模式(MVC / MVVM / MVP);
- 可以單獨測試每個組件国觉;
- 多條業(yè)務(wù)線可以并行開發(fā)吧恃,提高開發(fā)效率。
組件化的方案
- url scheme:在與模塊有關(guān)的類的
+(void)load
方法中創(chuàng)建一個執(zhí)行模塊的初始化和調(diào)用的block麻诀,并以指定的url作為key痕寓,將該block存儲到組件管理器對象維護的字典中。模塊在調(diào)用其他模塊時蝇闭,傳遞特定的url給組件管理器對象呻率,然后組件管理器對象通過url查找相應(yīng)的block,并執(zhí)行這個block呻引。這種方式需要提前將block存儲到內(nèi)存中礼仗,并且傳遞參數(shù)時不夠透明; - target - action:每個模塊都定義有一個 target 類逻悠,target 類中引用了與該模塊相關(guān)的類元践,并封裝了該模塊的初始化和調(diào)用的方法。另外童谒,為每個模塊都定義一個組件管理器的分類单旁,該分類中封裝了執(zhí)行該模塊的 target 中封裝的指定 action 的方法,組件管理器基于反射機制通過模塊的 target 名稱和 action 名稱來調(diào)用 target 類中的方法惠啄。模塊通過調(diào)用組件管理器分類中定義的方法來調(diào)用其他模塊慎恒。這種方式在參數(shù)傳遞上比較透明任内,而且不需要注冊block,但是需要實現(xiàn) target 類和組件管理器的分類融柬。
- 依賴注入:使用 objection 第三方庫死嗦。
什么是DNS解析?什么是DNS劫持以及如何解決該問題粒氧?
DNS解析
在客戶端發(fā)起一個HTTP請求時越除,請求會先到達運營商的DNS服務(wù)器,DNS服務(wù)器將域名解析成對應(yīng)的IP地址外盯,然后根據(jù)IP地址在互聯(lián)網(wǎng)上找到對應(yīng)的服務(wù)器摘盆,向這個服務(wù)器發(fā)起一個HTTP請求,該服務(wù)器找到對應(yīng)的資源后沿原路將資源返回給客戶端饱苟。
DNS劫持
DNS劫持又稱域名劫持孩擂,是指在劫持的網(wǎng)絡(luò)范圍內(nèi)攔截域名解析的請求,分析請求的域名箱熬,把審查范圍以外的請求放行类垦。否則,返回假的IP地址城须,或者什么都不做蚤认,使請求失去響應(yīng)。其最后效果是對特定的網(wǎng)絡(luò)不能訪問糕伐,或者將請求轉(zhuǎn)發(fā)到一個虛假的服務(wù)器砰琢。
解決辦法
- HttpDNS:國內(nèi)提供域名解析 API 接口的,有 DNSPod良瞧,現(xiàn)在國內(nèi)有很多廠商為 DNSPod 開發(fā)了 SDK陪汽,例如阿里、七牛等莺褒;
- 內(nèi)置IP列表:可以在啟動階段由服務(wù)端下發(fā)域名和 IP 的對應(yīng)列表掩缓,客戶端來進行緩存雪情,發(fā)起網(wǎng)絡(luò)請求的時候遵岩,直接根據(jù)域名映射到 IP 來進行業(yè)務(wù)訪問。實現(xiàn) HTTP 協(xié)議下 IP 連接其實是很簡單的巡通,只需要通過 NSURLProtocol 來攔截網(wǎng)絡(luò)請求尘执,然后將符合條件的網(wǎng)絡(luò)請求 URL 中的域名修改為 IP 就可以了。
對稱加密和非對稱加密
對稱加密又叫公開密鑰加密宴凉,加密和解密都會用到同一個密鑰誊锭。如果密鑰被攻擊者獲得,此時加密就失去了意義弥锄。常見的對稱加密算法有 DES丧靡、3DES蟆沫。
非對稱加密又稱共享密鑰加密,使用一對非對稱的密鑰温治,一把叫做私有密鑰饭庞,一把叫做公有密鑰,公鑰加密的數(shù)據(jù)只能使用私鑰解密熬荆,私鑰加密的數(shù)據(jù)只能使用公鑰解密舟山。常見的非對稱加密算法有 RSA。
TCP連接過程中的甭笨遥活機制
- 心跳檢測:服務(wù)端定時向客戶端發(fā)送一個數(shù)據(jù)包累盗,如果在一定時間內(nèi)沒有收到客戶端的回應(yīng),即認為客戶端已經(jīng)掉線突琳;同樣若债,如果客戶端在一定時間內(nèi)沒有收到服務(wù)器的心跳包,則認為連接不可用拆融;
- TCP的Keep-Alive辈鹱活機制:服務(wù)端或者客戶端開啟Keep-Alive功能后,會自動在規(guī)定時間內(nèi)向?qū)Ψ桨l(fā)送心跳包冠息,而另一方在收到心跳包后就會自動回復(fù)挪凑,以告訴對方其仍然在線。 由于開啟Keep-Alive功能需要消耗額外的寬帶和流量逛艰,所以TCP協(xié)議層默認關(guān)閉Keep-Alive功能躏碳。
如何解決TCP連接的粘包和拆包問題?
TCP連接在發(fā)送數(shù)據(jù)包的時候會建立一個緩存區(qū)散怖,發(fā)送的數(shù)據(jù)都會先進入這個緩存區(qū)菇绵,只有當上一條數(shù)據(jù)被確認接收或者到達最大等待時間之后,才會將緩存區(qū)的數(shù)據(jù)一塊發(fā)送過去镇眷,如此反復(fù)咬最。將小包進行整合,可以避免小包多次發(fā)送造成的傳輸速度慢等問題欠动,但是會產(chǎn)生粘包和拆包問題永乌。
粘包、拆包問題說明:
- 服務(wù)端分2次讀取到了兩個獨立的包具伍,分別是D1翅雏,D2,沒有粘包和拆包人芽;
- 服務(wù)端一次性接收了兩個包望几,D1和D2粘在一起了,被成為TCP粘包萤厅;
- 服務(wù)端分2次讀取到了兩個數(shù)據(jù)包橄抹,第一次讀取到了完整的D1包和D2包的部分內(nèi)容靴迫,第二次讀取到了D2包的剩余內(nèi)容,這被稱為拆包楼誓。
粘包和拆包的解決方案:
- 在每個數(shù)據(jù)包的包尾增加回車換行符來分割數(shù)據(jù)包矢劲,例如FTP協(xié)議;
- 發(fā)送的每個數(shù)據(jù)包是由包頭慌随、包長和包體組成的芬沉。
發(fā)布應(yīng)用程序的過程
- 申請發(fā)布證書
- 創(chuàng)建 App ID 并與應(yīng)用程序的 Bundle Identifier 綁定
- 生成描述文件將發(fā)布證書與 Bundle Identifier 綁定起來
- 在 iTunes Connect 中新建一個應(yīng)用程序,設(shè)置好應(yīng)用程序名稱和App ID
- 下載證書和描述文件阁猜,并在項目工程文件中配置好丸逸,然后打包應(yīng)用程序并上傳
- 填寫應(yīng)用程序的詳細資料,并構(gòu)建一個新版本剃袍,然后提交審核黄刚。
如何基于Audio Queue來實現(xiàn)音頻播放?
- 從遠程下載音頻數(shù)據(jù)民效,并使用
NSFileHandle
將音頻數(shù)據(jù)寫入到本地文件中憔维。 - 在下載和緩存音頻數(shù)據(jù)的同時,使用
NSFileHandle
從本地文件中讀取音頻數(shù)據(jù)畏邢。 - 使用
AudioFileStream
解析音頻數(shù)據(jù)來獲取采樣率业扒、碼率和時長等信息,并分離音頻幀得到PCM數(shù)據(jù)舒萎。 -
AudioQueue
把PCM數(shù)據(jù)解碼成音頻信號程储,并將音頻信號交給硬件播放屋休。 - 重復(fù)2-4步直到播放完成蹂安。
AVAudioSession有幾種模式?
-
AVAudioSessionCategoryAmbient
:混音播放怕轿,可以與其他音頻應(yīng)用同時播放咆贬。當用戶鎖屏或者靜音時败徊,也會隨著靜音; -
AVAudioSessionCategorySoloAmbient
:獨占播放掏缎,會打斷其他音頻應(yīng)用的播放皱蹦。當用戶鎖屏或者靜音時,也會隨著靜音御毅; -
AVAudioSessionCategoryPlayback
:后臺播放并且獨占根欧,當用戶鎖屏或者靜音時,會繼續(xù)播放端蛆; -
AVAudioSessionCategoryRecord
:錄音模式,會打斷其他音頻應(yīng)用的播放酥泛; -
AVAudioSessionCategoryPlayAndRecord
:播放和錄音今豆,可以一邊播放音頻一邊錄制音頻嫌拣,用于音頻通話; -
AVAudioSessionCategoryMultiRoute
:多種輸入輸出呆躲,例如可以耳機和USB設(shè)備同時播放异逐; -
AVAudioSessionCategoryAudioProcessing
:硬件解碼音頻,此時不能播放和錄制插掂。
如何處理音頻打斷事件灰瞻?
系統(tǒng)在接收到音頻打斷事件時,會發(fā)出通知辅甥。在收到音頻打斷開始通知時酝润,系統(tǒng)已經(jīng)暫停播放音頻,
此時我們需要更新音頻播放狀態(tài)和UI璃弄,同時標記一下暫停播放音頻的原因是接收到了打斷事件要销。在收到音頻打斷結(jié)束通知后,判斷一下當前暫停播放音頻的原因是否是因為接收到了打斷事件夏块。如果是疏咐,則恢復(fù)播放并更新UI。
六大設(shè)計原則
- 單一職責原則:一個類只負責一件事脐供;
- 開閉原則:不允許直接修改類的原有內(nèi)容浑塞,但可以對類進行擴展添加其他內(nèi)容;
- 接口隔離原則:使用多個專門的協(xié)議政己,而不是一個龐大臃腫的協(xié)議缩举;
- 依賴倒置原則:外界可以直接調(diào)用接口而不用關(guān)心接口的具體實現(xiàn),在具體實現(xiàn)中可以調(diào)用接口匹颤;
- 理氏替換原則:父類可以被子類無縫替換仅孩,且原有功能不受任何影響;
- 迪米特法則:一個對象應(yīng)當對其他對象有盡可能少的了解印蓖,這樣就能做到高內(nèi)聚辽慕、低耦合。
MVC
MVC(Model - View - Controller)即模型 - 視圖 - 控制器赦肃。
- 模型對象負責封裝應(yīng)用程序的數(shù)據(jù)溅蛉;
- 視圖對象負責展示數(shù)據(jù);
- 控制器對象負責協(xié)調(diào)模型對象和視圖對象之間的通信他宛,響應(yīng)用戶交互船侧。
模型對象和視圖對象不能直接進行通信,必須通過控制器來協(xié)調(diào)視圖和模型之間的通信厅各【盗茫控制器對模型和視圖的訪問是不受限的,但不允許模型和視圖直接訪問控制器队塘,模型通過KVO或Notification來通知控制器袁梗,視圖通過delegate和target-action來通知控制器宜鸯。
MVC的缺點:控制器不僅要協(xié)調(diào)模型和視圖之間的通信,還要管理視圖層次結(jié)構(gòu)和響應(yīng)用戶交互遮怜,這樣就會導(dǎo)致控制器中的代碼非常臃腫淋袖,不易于管理和維護。
MVVM
MVVM(Model - View - ViewModel)即模型 - 視圖 - 視圖模型锯梁,其衍生于MVC即碗。
- 視圖負責數(shù)據(jù)的展示;
- 模型負責封裝數(shù)據(jù)陌凳;
- 視圖模型封裝了響應(yīng)用戶交互事件的邏輯剥懒、視圖顯示的邏輯、發(fā)起網(wǎng)絡(luò)請求的邏輯以及其他邏輯冯遂。
模型對象和視圖對象不能直接進行通信蕊肥,必須通過視圖模型對象來協(xié)調(diào)視圖和模型之間的通信。視圖可以直接訪問視圖模型蛤肌,但不允許視圖模型直接訪問視圖壁却,視圖模型可以使用回調(diào)來通知視圖。視圖模型可以直接訪問模型裸准,但不允許模型直接訪問視圖模型展东,模型可以使用回調(diào)來通知視圖模型。
MVVM的缺點:數(shù)據(jù)綁定和數(shù)據(jù)轉(zhuǎn)化需要花費更多的成本炒俱。
MVP
MVP 全稱 Model - View / ViewController - Presenter盐肃,即模型-視圖-協(xié)調(diào)器,是面向協(xié)議的設(shè)計模式权悟。
- Model:模型負責封裝數(shù)據(jù)砸王;
- View:視圖負責數(shù)據(jù)的展示;
- Presenter: Model 和 View 之間的中間人峦阁。當用戶對 View 有操作時谦铃,由它負責去修改相應(yīng)的 Model。當 Model 發(fā)生變化時榔昔,由它負責去更新對應(yīng)的 View驹闰。
MVP的缺點:需要寫更多的代碼。
設(shè)計模式
責任鏈模式
責任鏈模式就是為一個消息創(chuàng)建一個由接收者對象組成的鏈撒会,這條鏈上的每一個對象都可以去響應(yīng)這個消息嘹朗。這種模式把消息的發(fā)送和接收進行解耦,每個接收者都包含對另一個接收者的引用诵肛。如果一個接收者不能處理該消息屹培,那么它會把相同的消息傳給下一個接收者,依此類推。例如惫谤,UIKit中的事件響應(yīng)鏈就運用了責任鏈設(shè)計模式壁顶。
橋接模式
橋接模式將抽象部分與它的具體實現(xiàn)部分分離珠洗,使它們都可以獨立地變化溜歪。例如,F(xiàn)oundation框架中的·NSOperationQueue
和NSOperation
就使用了橋接模式许蓖,NSOperation
中定義了start
蝴猪、main
和cancel
方法,而NSBlockOperation
膊爪、NSInvocationOperation
都繼承自NSOperation
并各自實現(xiàn)了這些方法自阱。向NSOperationQueue
中添加NSBlockOperation
和NSInvocationOperation
后,NSOperationQueue
內(nèi)部在使用它們時米酬,不用關(guān)心它們到底是NSBlockOperation
還是NSInvocationOperation
沛豌,而是直接調(diào)用NSOperation
的接口。
適配器模式
當希望復(fù)用一個已經(jīng)存在的類赃额,而它的接口不符合復(fù)用環(huán)境的規(guī)范時加派,如果直接對這個類進行修改,可能會存在風險跳芳。這時芍锦,可以使用適配器模式來解決這個問題。適配器模式使用一個適配對象來引用被適配對象飞盆,由適配對象執(zhí)行一些額外工作來適應(yīng)新環(huán)境的規(guī)范娄琉,然后在復(fù)用被適配對象。
單例模式
一個類同時只能存在唯一的一個對象吓歇,當使用類的alloc
方法創(chuàng)建對象時孽水,會始終返回這個單例對象。
命令模式
命令模式把一系列動作或者行為封裝成一個命令對象城看,客戶端不需要知道其實現(xiàn)細節(jié)就可以執(zhí)行這些動作或者行為女气。例如,F(xiàn)oundation 框架中的NSInvocation
就使用了命令模式析命。
工廠模式
同一類型的不同實例對象有它們各自的創(chuàng)建方法主卫。
享元模式
享元模式通過重復(fù)使用對象來減少同一類對象的大量創(chuàng)建,從而提高程序執(zhí)行效率鹃愤。UITableview
的Cell重用就是一種享元模式簇搅。
裝飾器模式
裝飾器模式可以在不改變原類文件和不使用繼承的情況下,動態(tài)地擴展一個對象的功能软吐。裝飾器模式是使用一個包裝對象來包裹真實的對象瘩将。
項目的整體架構(gòu)
- 業(yè)務(wù)層,例如,“首頁”模塊姿现,“消息”模塊肠仪,“我的”模塊;
- 中間層备典,基于反射機制實現(xiàn)業(yè)務(wù)層之間的解耦异旧;
- 通用業(yè)務(wù)層,例如提佣,通用的UI控件吮蛹;
- 獨立于App的通用層,例如拌屏,AFNetworking潮针、SDWebImage 等框架。
如何減少ipa包體積倚喂?
刪除無用類每篷、無用代碼、無用的第三方庫端圈,刪除不再使用的圖片焦读。
如何快速定位Bug?
使用第三方庫 bugly 收集并上傳應(yīng)用程序的崩潰日志枫笛,根據(jù)崩潰日志獲取設(shè)備機型吨灭、iOS 系統(tǒng)版本、app 版本刑巧、崩潰時的函數(shù)調(diào)用棧和引發(fā)崩潰的原因喧兄。接著,給程序設(shè)置異常斷點和關(guān)鍵斷點啊楚,并在代碼中添加打印關(guān)鍵數(shù)據(jù)的 NSLog 語句吠冤。然后,使用 xcode 在設(shè)備上運行 app恭理。當遇到斷點時拯辙,使用 lldb 獲取有關(guān)對象的屬性信息。一步一步地分析這些數(shù)據(jù)來找到產(chǎn)生 bug 的原因颜价,并修復(fù) bug涯保。
app版本升級,數(shù)據(jù)庫中的表結(jié)構(gòu)變了周伦,如何進行數(shù)據(jù)庫遷移夕春?
- 在本地保存數(shù)據(jù)表的當前版本號,在每次啟動 app 時专挪,讀取數(shù)據(jù)表的當前版本號及志;
- 根據(jù)數(shù)據(jù)表的當前版本號判斷是否需要遷移片排,如果需要,則使用數(shù)據(jù)庫事務(wù)執(zhí)行以下操作:
- 將數(shù)據(jù)表的名稱改為 temp速侈;
- 創(chuàng)建一個新的數(shù)據(jù)表率寡,其名稱與舊數(shù)據(jù)表的原始名稱一致;
- 把舊數(shù)據(jù)表中的數(shù)據(jù)插入到新數(shù)據(jù)表中倚搬;
- 刪除舊數(shù)據(jù)表冶共;
- 如果以上其中一步執(zhí)行失敗,則回滾事務(wù)潭枣;
- 如果數(shù)據(jù)庫遷移成功了比默,則更新本地保存的數(shù)據(jù)表的當前版本號幻捏;
什么是事務(wù)
事務(wù)是用戶定義的一個數(shù)據(jù)庫操作序列盆犁,這些操作要么全部執(zhí)行,要么全部不執(zhí)行篡九,是一個不可分割的工作單位谐岁。
事務(wù)的特性
- 原子性:一個事務(wù)是一個不可分割的工作單位,事務(wù)中包括的操作要么都做榛臼,要么都不做伊佃。
- 一致性:事務(wù)必須是使數(shù)據(jù)庫從一個一致性狀態(tài)變到另一個一致性狀態(tài),一致性與原子性是密切相關(guān)的沛善。
- 隔離性:一個事務(wù)是一個不可分割的工作單位航揉,事務(wù)中包括的操作要么都做,要么都不做金刁。
- 持久性:一個事務(wù)一旦提交帅涂,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應(yīng)該是永久性的。接下來的其他操作或故障不應(yīng)該對其有任何影響尤蛮。
講講項目中遇到的難題以及是如何解決這些難題的媳友?
導(dǎo)航欄背景透明以及導(dǎo)航欄背景動態(tài)切換
設(shè)置某個頁面的導(dǎo)航欄背景的透明度,并且在跳轉(zhuǎn)新頁面時將導(dǎo)航欄背景設(shè)置為不透明产捞。以及頁面之間跳轉(zhuǎn)時醇锚,改變導(dǎo)航欄的背景顏色或者背景圖片。由于 UIKit 框架并沒有提供 API 來設(shè)置導(dǎo)航欄背景的透明度坯临,并且使用官方 API 切換導(dǎo)航欄背景時焊唬,視覺體驗不太好,所以需要自行實現(xiàn)導(dǎo)航欄背景透明和動態(tài)切換背景效果看靠。
使用 xcode 運行應(yīng)用程序并查看UINavigationBar
視圖層次結(jié)構(gòu)赶促,發(fā)現(xiàn)UINavigationBar
的背景是由其最底層的子視圖_UIBarBackground
控制的,設(shè)置_UIBarBackground
的透明度就可以改變導(dǎo)航欄的透明度衷笋。
查閱UIViewController
的官方文檔得知芳杏,在視圖控制器執(zhí)行 push 或 pop 轉(zhuǎn)場動畫時矩屁,UIKit 會為視圖控制器創(chuàng)建一個轉(zhuǎn)場動畫協(xié)調(diào)器transitionCoordinator
,可以使用轉(zhuǎn)場動畫協(xié)調(diào)器提交其他動畫與 push 或 pop動畫一起同時執(zhí)行爵赵。我們可以將_UIBarBackground
設(shè)置為完全透明吝秕,并在_UIBarBackground
添加一個背景子視圖,通過設(shè)置這個背景子視圖的透明度和背景來控制導(dǎo)航欄的透明度和背景空幻。在每次執(zhí)行 push 或者 pop 轉(zhuǎn)場動畫時烁峭,向_UIBarBackground
添加一個臨時視圖,并將臨時視圖的透明度和背景設(shè)置為跳轉(zhuǎn)界面的導(dǎo)航欄的透明度和背景秕铛,然后讓臨時視圖跟隨視圖控制器一起 push 或 pop 到界面中约郁。最后,更新背景子視圖的透明度和背景但两,并刪除臨時子視圖鬓梅,這樣就能實現(xiàn)導(dǎo)航欄背景的動態(tài)切換效果。
根據(jù)以上思路谨湘,最終實現(xiàn)了一個UINavigationController
的 category绽快,在每次 push 和 pop 視圖控制器時,自動添加導(dǎo)航欄背景切換的轉(zhuǎn)場動畫紧阔,使用者只需要在視圖控制器的viewWillAppear:
方法中調(diào)用相關(guān) API 設(shè)置導(dǎo)航欄的透明度和背景即可坊罢。
高德2D地圖展示車輛行駛軌跡的動畫
網(wǎng)約車訂單在執(zhí)行過程中,乘客端需要在高德2D地圖上動畫顯示汽車標注的行駛軌跡擅耽。而高德2D地圖是沒有提供這個功能的活孩,所以需要自行實現(xiàn)。
CADisplayLink
是一個與屏幕刷新率一致的定時器乖仇,每次刷新屏幕時憾儒,都會觸發(fā)CADisplayLink
。計算出汽車標注從一個坐標點動畫移動到另一個坐標點的關(guān)鍵幀坐標點这敬,然后在每次刷新屏幕時航夺,將汽車標注的坐標設(shè)置為關(guān)鍵幀坐標點,這樣人眼看到的效果就是汽車標注從一個坐標點動態(tài)移動到了另一個坐標點崔涂。
另外阳掐,汽車標注從一個坐標點動態(tài)移動到另一個坐標點之前,還需要先動畫旋轉(zhuǎn)汽車標注來調(diào)整汽車行駛的方向冷蚂。汽車標注旋轉(zhuǎn)角度的計算可以根據(jù)汽車標注的上一個坐標點缭保、當前坐標點和下一個坐標點這三個坐標點構(gòu)造兩個向量,然后使用向量的夾角公式計算出角度(cosθ = 向量a與向量b的點積 / 向量a的模長 x 向量b的模長)蝙茶。計算出角度后艺骂,還要知道是順時針旋轉(zhuǎn)還是逆時針旋轉(zhuǎn),這個可以通過向量a與向量b的叉積來判斷隆夯。
由于乘客端每隔一段時間就會向服務(wù)端發(fā)送一個HTTP請求來獲取司機端的軌跡數(shù)組钳恕,這就有可能出現(xiàn)之前請求的軌跡坐標點還沒有動態(tài)更新完畢别伏,又有新的軌跡坐標點傳送過來了,因此需要隊列執(zhí)行汽車標注從一個坐標點移動到另一個坐標點的動畫忧额±灏梗可以自定義NSOperation
來封裝汽車標注從一個坐標點移動到另一個坐標點的動畫,這需要將CADisplayLink
添加到主線程的runloop中睦番,所以這個操作是一個異步操作类茂,因而需要我們自行去控制操作是否已經(jīng)執(zhí)行完畢。根據(jù)坐標點創(chuàng)建一個自定義的NSOperation
對象后托嚣,將其添加到最大并發(fā)數(shù)為1
的NSOperationQueue
中巩检。
應(yīng)用程序上架被拒絕的原因有哪些?
2.1 App 完成度
主要有應(yīng)用出現(xiàn)崩潰示启、加載失敗等非常明顯的Bug兢哭、應(yīng)用不支持 IPv6網(wǎng)絡(luò)下使用、測試賬號丑搔、隱藏開關(guān)等厦瓢。
解決方法:提前測試產(chǎn)品是否有bug、在IPV6網(wǎng)絡(luò)下是否能使用等啤月,根據(jù)反饋郵件,一個個審查自身產(chǎn)品信息是否符合劳跃,適當情況下可以發(fā)送截圖視頻給蘋果官方以證明自己的清白谎仲。
2.3 準確的元數(shù)據(jù)
主要是應(yīng)用標題、描述刨仑、截圖等與應(yīng)用功能嚴重不符郑诺。如用安卓手機截圖,瀏覽器截圖杉武。
解決方法:重新更換截圖辙诞,保證整個APP功能、流程看起來是一致的轻抱。去除隱藏功能模塊代碼或?qū)⑿枰[藏功能的代碼及定向跳轉(zhuǎn)鏈接網(wǎng)址做混淆處理飞涂,適當增加邏輯復(fù)雜度。
2.5 軟件要求
主要是產(chǎn)品加入違規(guī)代碼祈搜。
解決方法:很可能是三方庫中含有SDK较店,可以更新所有三方庫,或者反編譯提交的ipa容燕,檢查文檔中是否有違規(guī)字符串梁呈,有的話刪掉。
3.1.1 購買項目
主要是接入第三方支付蘸秘,支付寶官卡、微信等蝗茁。
解決方法:老老實實地走蘋果支付的支付方式,用內(nèi)購寻咒。如果隱藏虛擬產(chǎn)品或者通過后更改支付方式评甜,都是有一定風險的。
3.2.1 可接受的商業(yè)模式
主要是沒有資質(zhì)仔涩。
解決方法:最佳方案是拿到資質(zhì)忍坷,如果實在沒有資質(zhì),建議大家盡可能多的把自己公司合規(guī)的證據(jù)資料發(fā)給蘋果熔脂,而套殼佩研、換新賬號碰運氣上架等操作,不得已的話可以嘗試霞揉。
4.2 最低功能要求
主要問題在于蘋果認為部分開發(fā)者上傳的App功能不夠旬薯,或者沒有自己的核心功能,比如直接打包一個網(wǎng)頁上架的很容易觸發(fā)這個問題适秩。
解決辦法:可以添加一些功能豐富產(chǎn)品(導(dǎo)航欄绊序,下拉刷新,推送通知等功能)秽荞,如果覺得功能已經(jīng)全了骤公,還沒有通過審核,可以向蘋果解釋產(chǎn)品解決的用戶需求扬跋,以及具體功能的展現(xiàn)阶捆。
4.3 重復(fù) App
主要針對的是重復(fù)App,就是馬甲包钦听。
解決辦法:可通過修改名字洒试、icon、主色調(diào)朴上、代碼等解決垒棋,并且注意相同的馬甲包提交至少間隔一天以上。
5.1.1 數(shù)據(jù)收集和存儲
主要是App強制用戶注冊痪宰,且基于不需要用戶信息的功能之上叼架、暗中采集/共享用戶的個人信息。
解決方法:先與用戶協(xié)商酵镜,讓用戶同意后注冊碉碉,有“強登陸”功能的一定要修改為提示登陸的版本。
5.1.5 定位服務(wù)
主要是 App 未得到允許淮韭,與第三方共享收集的用戶數(shù)據(jù)垢粮,且并未說明使用目的等,例:位置靠粪、賬號……
解決方法:如果要采取用戶數(shù)據(jù)信息蜡吧,需要給予用戶提示毫蚓,并得到用戶的允許,或設(shè)置為可選昔善,并且明確告知蘋果采集用戶數(shù)據(jù)信息的使用目的元潘。總的來說就是要彈出提示說明使用這個權(quán)限做什么用君仆,寫清楚翩概。
5.2 知識產(chǎn)權(quán)
主要是未經(jīng)授權(quán),使用受版權(quán)保護的第三方材料返咱、App不得與蘋果現(xiàn)有產(chǎn)品類似等钥庇。
解決方法:確保 app 只包含由您創(chuàng)建或擁有使用許可的內(nèi)容,提交產(chǎn)品時使用受版權(quán)保護的第三方的書面證據(jù)或者將產(chǎn)品中包含的未經(jīng)第三方授權(quán)的部分隱藏咖摹。
自我介紹
我叫xxx评姨,今年xx歲,xx省xx市人萤晴,20xx年畢業(yè)吐句,從事 iOS 開發(fā)至今已有 x 年。在上家公司的時候店读,獨立負責公司 iOS 端應(yīng)用程序的開發(fā)和維護嗦枢,有比較豐富的項目開發(fā)經(jīng)驗,我相信自己能夠勝任貴公司的這一職位两入。非常感謝貴公司給與我的這次面試機會净宵,希望以后能為貴公司貢獻自己的價值。
職業(yè)規(guī)劃
未來1-3年裹纳,打算繼續(xù)在iOS開發(fā)技術(shù)上沉淀,同時提升自己的項目管理能力和團隊協(xié)作能力紧武,爭取為公司貢獻自己的最大價值剃氧。
離職原因
從上家公司的工作中,我學到了很多東西阻星,但是因為上家公司的業(yè)務(wù)發(fā)展比較局限朋鞍,與我的職業(yè)規(guī)劃有些偏差,所以打算換個平臺繼續(xù)努力妥箕。