在不考虑 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;
复制代码
【注】“签到数据”指的是下图: