在不考慮 APP 在後臺被 kill 的狀況: 進入後臺:html
方法 | 做用 |
---|---|
applicationWillResignActive |
點擊 Home 鍵,app開始準備進入後臺,這個時候會進入該回調,意味着 app 被掛起,進程即將失去活躍。通過不嚴謹的測試,大約有 10 分鐘左右的時間用來處理事務。 |
applicationDidEnterBackground |
當 applicationWillResignActive回調方法徹底執行完畢後,會進入 applicationDidEnterBackground 。 |
進入前臺:ios
方法 | 做用 |
---|---|
applicationWillEnterForeground |
在 app 未被殺死的狀況下,點擊 icon再次進入 app,從新回到前臺以前會先進入 applicationWillEnterForeground 回調 |
applicationDidBecomeActive |
當 applicationWillEnterForeground 執行完畢後,會進入 applicationDidBecomeActive 回調,正式迴歸活躍。 |
先後臺切換,主要的坑點在於:VC中並沒函數會調用,尤爲注意:VC 相關的 Appear 和 Disappear 函數並不會被調用。想在VC中監聽切換,只能監聽通知,每一個在appdelegate的生命代理方法都有對應的通知。git
若是考慮 APP 在後臺被 kill 的狀況:github
進入後臺後,若是沒有後臺運行權限及功能,可能在一段時間後被系統 kill 掉,再次進入app後,會從新進入啓動流程。objective-c
方法 | 做用 |
---|---|
main() 函數: | 這個階段通常是 可執行 .o 文件,動態庫加載,objc類註冊,category 類註冊,selector 惟一性檢查,+(void)load 方法,C++ 靜態全局變量的建立等。 |
didFinishLaunchingWithOptions |
用戶點擊 icon 啓動 app,或者被 kill 後以任何方式進入 app,在 main() 執行後,會進入didFinishLaunchingWithOptions回調,處理首屏渲染,以及其餘業務相關的事件,例如監聽事件,配置文件讀寫或者 SDK 初始化等等。 |
applicationDidBecomeActive |
在didFinishLaunchingWithOptions方法做用域結束後,會進入 applicationDidBecomeActive 回調,也正式意味着 app 已經處於活躍狀態。 |
rootViewController 的相關的 Appear 函數 |
注意:此時rootViewController 的相關的 Appear 函數會被調用。 |
參考連接:WWDC 2016 - Session 406-Optimizing App Startup Timeswift
蘋果爲咱們提供了不少 Array 的排序方法,但原理上能夠看到就是 Comparator (比較器) 和 Descriptor (描述器) 兩種,像是 Selector 和 Function ,最終也是使用 Comparator 在作排序,只是響應方法不一樣。 其中 Swift 也有方法: array.sort(),見參考連接:Apple Documentation-Swift-Array-sorted 先說說 Comparator ,若是數組中元素是 String 或 Number,首選 Comparator,能夠將 compare: 方法的返回值直接做爲 NSComparisonResult 返回值。實際的排序代碼三行就能夠搞定。固然用 Selector 和 Function,也是同樣的效果,但須要寫更多的代碼。 例子一:後端
NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
if ([obj1 compare:obj2] == NSOrderedAscending) {
return NSOrderedAscending;
} else if ([obj1 compare:obj2] == NSOrderedDescending){
return NSOrderedDescending;
}else {
return NSOrderedSame;
}
}];
複製代碼
例子二:api
- (void)arraySortUsingCompare {
// 比較器 排序
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 10; i ++) {
int n = arc4random() % (10 - 0) + 1;
[arr addObject:@(n)];
}
NSLog(@"排序前 ===== %@", arr);
[arr sortUsingComparator:^NSComparisonResult(NSNumber *num1, NSNumber *num2) {
// return [num1 compare:num2]; // 正序
return [num2 compare:num1]; // 倒序
}];
NSLog(@"排序後 %@", arr);
arr = [NSMutableArray array];
[arr addObject:@"Kobe Bryant"];
[arr addObject:@"LeBorn James"];
[arr addObject:@"Steve Nash"];
[arr addObject:@"Stephen Curry"];
[arr addObject:@"Monkey D Luffy"];
[arr addObject:@"Roronoa Zoro"];
NSLog(@"排序前 ==== %@", arr);
[arr sortUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
// return [str1 compare:str2]; // 正序
return [str2 compare:str1]; // 倒序
}];
NSLog(@"排序後 %@", arr);
}
複製代碼
參考連接:Objective-C中的排序及Compare陷阱數組
可是若是須要針對一個對象的幾個屬性做爲不一樣的維度去作排序,那選擇 Descriptor,由於不須要根據利用屬性對排序優先級寫一大堆的邏輯判斷。主要將全部參與比較的屬性都放入描述器中便可,若是想對球員的年齡和號碼(優先級分前後)進行排序,只須要依次加入描述器組,三行代碼就能夠完成。性能優化
- (void)arraySortUsingDescriptor {
NSMutableArray *arr = [NSMutableArray array];
Person *person = [[Person alloc] init];
person.name = @"Ingram";
person.age = 21;
person.number = 14;
[arr addObject:person];
person = [[Person alloc] init];
person.name = @"Ball";
person.age = 21;
person.number = 2;
[arr addObject:person];
person = [[Person alloc] init];
person.name = @"Zubac";
person.age = 21;
person.number = 15;
[arr addObject:person];
NSSortDescriptor *ageDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
NSSortDescriptor *numberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES];
[arr sortUsingDescriptors:@[numberDescriptor, ageDescriptor]];
for (Person *person in arr) {
NSLog(@"\n 球員姓名: %@ \n 球員號碼: %d \n 球員年齡: %d \n -------- \n", person.name, person.number, person.age);
}
}
複製代碼
post請求; Use HTTP/2 and TLS 1.2 or later to establish a connection between your provider server and one of the following servers:
也就是: 設備信息是經過一個POST請求將DeveiceToken和其餘信息發送給APNS,須要用 HTTP/2 和 TLS 1.2或以上的版本,在本身提供的服務和以上服務之間創建鏈接。
開發環境:api.sandbox.push.apple.com:443
生產環境:api.push.apple.com:443
固然,還能夠用一臺機器的 2197 端口讓 APNS 經過防火牆
請求示例:
HEADERS
- END_STREAM
+ END_HEADERS
:method = POST
:scheme = https
:path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
host = api.sandbox.push.apple.com
authorization = bearer eyAia2lkIjogIjhZTDNHM1JSWDciIH0.eyAiaXNzIjogIkM4Nk5WOUpYM0QiLCAiaWF0I
jogIjE0NTkxNDM1ODA2NTAiIH0.MEYCIQDzqyahmH1rz1s-LFNkylXEa2lZ_aOCX4daxxTZkVEGzwIhALvkClnx5m5eAT6
Lxw7LZtEQcH6JENhJTMArwLf3sXwi
apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b
apns-expiration = 0
apns-priority = 10
apns-topic = com.example.MyApp
DATA
+ END_STREAM
{ "aps" : { "alert" : "Hello" } }
複製代碼
didRegisterForRemoteNotificationsWithDeviceToken
方法,回調內處理設備信息上傳的業務。但有些狀況是,咱們但願根據用戶帳號來作推送,例如即時通信應用。那麼咱們就要在登陸或自動登陸後,上傳deviceToken,和用戶信息綁定並處理替換邏輯,避免推送錯亂。
This address takes the form of a device token unique to both the device and your app.
猜想:UDID+bundleId+生產/開發環境+時間戳。
其中注意帶時間戳hash是爲何頻繁上傳device token的主要緣由。長期不活躍app,好比用戶一個月或者兩個月沒打開過該app,該服務器後端就再也推不到了。
優勢:
區別於⼿動爲每⼀個類編寫埋點⽅法或者寫⼀個基類來作統⼀的埋點,前二者在某些場景下⼯ 做量都不算⼩。能夠作⼀個UIViewController的Category,置換原⽣⽅法,在置換⽅法中將寫⼊埋點代碼,這樣能夠直接⼀鍵埋點完成。以後新增的UIViewController類也不須要再關⼼這些的埋點代碼。
- (void)cyl_APOViewDidLoad {
Class class = [self class];
if (!([class isEqual:[UIViewController class]] || [class isEqual: [UINavigationController class]])) {
NSLog(@"統計該⻚⾯ %@", class);
}
}
複製代碼
置換 NSDictionary
的 -setObject:forKey:
方法,用於防止 crash
。NSArray
同理。
- (void)cyl_safeSetObject:(id)object forKey:(id<NSCopying>)key {
if (object && key) {
[self safe_setObject:object forKey:key];
}
}
複製代碼
缺點:
總結:一時hook一時爽,debug火葬場。
緣由:
如下爲What are the Dangers of Method Swizzling in Objective-C? 中列舉出的7個問題:
可見,其沒有相似註解的東⻄,⽅法置換沒有有效聲明。若是濫⽤,反⽽會增長維護成本。若擅⾃使⽤未同步其餘同窗,會成爲極⼤的項⽬隱患。尤爲是⼀些封裝的模塊。
這裏着重說明幾個場景:
場景:(iTeaTime(技術清談)@國家一級保護廢物 提供答案)
若是屢次hook了同一個類的同一個方法, 跟分類重名的表現是同樣:表現爲沒法控制執行的前後順序,與編譯器build的順序有關,但編譯器順序有不可控性。
好比下面的實現方法,可能出現方法覆蓋的問題:
+ (void)load {
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad); SEL swizzledSelector = @selector(XK_ViewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
複製代碼
場景:(iTeaTime(技術清談)@molon 提供) Hook了具備繼承關係的相同方法。
如下場景:
若是子類並無重寫父類的方法,拿父類的implement去swizzling原本就是錯誤的行爲。
A<—繼承---B<—繼承---C (B是A的子類,C又是B的子類)
A 裏有 test 方法,可是 B 和 C 都沒有重寫。 一般若是要對 B 或者 C 的 test 進行hook的話,不少開發者都喜歡去給 B 或者 C add A.test 的 implemention。 那若是先hook的C,又hook的B,彷佛就造成了C與A直接打交道的局面。可是以面向對象來講,C的原實現應該是B的當前實現才合理。 因此不該該hook當前類沒有重寫的方法,這種其實直接繼承(或者加category方法)就能夠作了,不須要hook,須要調用原實現直接[super test]便可。
視頻播放器默認靜音模式下是沒有聲音的,但能夠控制即便是靜音模式下依然有聲音,顯然前者設置了,後者沒有設置。推測前者是被提交了bug因此fix掉了,後者使用場景比較少,因此沒有被注意到。
//忽略靜音按鈕
AVAudioSession *session =[AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
複製代碼
完整代碼:
- (AVAudioPlayer *)player {
if (!_player) {
NSURL *URL = [[NSBundle mainBundle] URLForResource:@"xxxx.wav"
withExtension:nil];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:nil];
AVAudioSession *autioSession = [AVAudioSession sharedInstance];
[autioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[autioSession setActive:YES error:nil];
[_player prepareToPlay];
}
複製代碼
耳機場景下,統一作了處理,均可以播放視頻帶聲音。 好比如下代碼用於判斷耳機狀態,由於AVAudioSession是單例,對耳機優先處理便可。
- (BOOL)isHeadsetPluggedIn {
AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
for (AVAudioSessionPortDescription* desc in [route outputs]) {
if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])
return YES;
}
return NO;
}
複製代碼
就是label的寬度設置跟scrollView等寬,最底下的label底部要跟scrollView的底部約束上就能夠了。 考察的主要是scrollView的約束問題。scrollView的約束主要是從內部撐開寬度跟高度。
三個label 那個,就是放了個scrollview而後裏面放三個label,從上往下邊距所有約束爲0,而後label寬度與scrollview相同,最下面那個label距離底部scrollview爲0。(在內部無需多放view)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.contentView layoutIfNeeded];
self.scrollview.contentSize =
CGSizeMake(CGRectGetWidth(self.contentView.frame), CGRectGetHeight(self.contentView.frame));
}
複製代碼
能夠在定義的同時就取出元祖中的值
// 至關於同時定義了三個變量
let (name, age, score) = (「a」, 30, 99.9)
根據這一特性,咱們能夠這樣互換值: (a, b) = (b, a)
(a = a ^ b) && (b = a ^ b) && (a = a ^ b)
或者這樣
a = a ^ b;b = a ^ b;a = a ^ b;
(a = a + b) && (b = a - b) && (a = a - b)
(a = a x b) && (b = a / b) && (a = a / b)
【提示】兩種方法,答案提示:UIBezierPath,和iOS11 layer有個新的方法 【答案】iOS11的layer是maskedCorners,CACornerMask。
參考連接:ios 圓角 cornerRadius 對性能的影響究竟多大? 你測試過嗎?
cell複用機制的實現猜測,見GitHub-Chameleon:
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
for (UITableViewCell *cell in _reusableCells) {
if ([cell.reuseIdentifier isEqualToString:identifier]) {
UITableViewCell *strongCell = cell;
// the above strongCell reference seems totally unnecessary, but without it ARC apparently
// ends up releasing the cell when it's removed on this line even though we're referencing it
// later in this method by way of the cell variable. I do not like this.
[_reusableCells removeObject:cell];
[strongCell prepareForReuse];
return strongCell;
}
}
return nil;
}
複製代碼
時間複雜度爲: O(n)
注意:
NSArray
/ NSMutableArray
containsObject:
,containsObject:``,indexOfObject*
,removeObject:
會遍歷裏面元素查看是否與之匹對,因此複雜度等於或大於 O(n)。
這裏 _reusableCells
使用的是NSMutableSet
,而 NSSe
t / NSMutableSet
/ NSCountedSet
這些集合類型是無序沒有重複元素。這樣就能夠經過 hash table
進行快速的操做。好比 addObject:
, removeObject:
, containsObject:
都是按照 O(1) 來的。須要注意的是將數組轉成 Set 時會將重複元素合成一個,同時失去排序。
加之 for 循環,能夠獲得複雜度計算結果。
方案一:利用聯結(在異步線程上調用dispatch_source_merge_data
後,就會執行 dispatch source
事先定義好的handler
)、DISPATCH_SOURCE_TYPE_DATA_ADD
,將刷新UI的工做拼接起來,短期內作儘可能少次數的刷新。
方案二:本身實現隊列、肯定一個合適的時間閾值,在閾值時間到達時、主動取消息或者被動接受消息,最後刷新UI,達到消息限流的做用。舉例:假設咱們消息的獲取都是經過長鏈接推送過來的,而不是主動拉取的。能夠用消息隊列來作,消費者按期去隊列取數據進行數據展現。或者假設前一條消息和後一條消息間隔只在0.2s之內,就能夠認爲是頻繁收到消息。而後把這0.2s內的消息刷新相關操做,好比作個動畫效果。
效果圖見:
系統 Autolayout 參考 :Apple-Documentation-UIView-setContentHuggingPriority(_:for:)
Masonry 參考如下屬性:
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
複製代碼
【注】「簽到數據」指的是下圖: