iOS 技术

1. 请问先后台切换,会发生些什么,系统哪些方法会被调用,viewcontroller哪些方法会被调用

在不考虑 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

2. 请问对无序的Array排序,有什么好的方法,代码越少,API越高级越好。有无原生方法能够办到。

苹果为咱们提供了不少 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);
       
   }
}
复制代码

3. 请问APNs推送如何区分设备,如何将设备的信息传给Apple,你上传的时机时怎样的,猜测这个设备信息是如何生成的

设备信息传递给apple

post请求; Use HTTP/2 and TLS 1.2 or later to establish a connection between your provider server and one of the following servers:

  • Development server: api.sandbox.push.apple.com:443
  • Production server: api.push.apple.com:443

也就是: 设备信息是经过一个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,该服务器后端就再也推不到了。

3. 谨慎iOS黑魔法 - Method Swizzling

优势:

区别于⼿动为每⼀个类编写埋点⽅法或者写⼀个基类来作统⼀的埋点,前二者在某些场景下⼯ 做量都不算⼩。能够作⼀个UIViewController的Category,置换原⽣⽅法,在置换⽅法中将写⼊埋点代码,这样能够直接⼀键埋点完成。以后新增的UIViewController类也不须要再关⼼这些的埋点代码。

- (void)cyl_APOViewDidLoad {
Class class = [self class];
if (!([class isEqual:[UIViewController class]] || [class isEqual: [UINavigationController class]])) {
NSLog(@"统计该⻚⾯ %@", class);
}
}
复制代码

置换 NSDictionary-setObject:forKey: 方法,用于防止 crashNSArray 同理。

- (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个问题:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

可见,其没有相似注解的东⻄,⽅法置换没有有效声明。若是滥⽤,反⽽会增长维护成本。若擅⾃使⽤未同步其余同窗,会成为极⼤的项⽬隐患。尤为是⼀些封装的模块。

这里着重说明几个场景:

场景:(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]便可。

4. iPhone在无耳机状态下,经过实体按键设置静音后,如下路径好比: 微信主tab-朋友圈-点开feed流中的小视频,能够播放声音。经过点击头像-我的朋友圈主页,点开视频没法播放声音。即便按声音增长键也没法播放。请问这个表现不一致的现象,是feature仍是bug,若是是bug你以为是代码哪里写的有问题。写出修复代码

视频播放器默认静音模式下是没有声音的,但能够控制即便是静音模式下依然有声音,显然前者设置了,后者没有设置。推测前者是被提交了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;  
} 
复制代码

5. 【iOS-autolayout】一个ScrollView上有3个UILabel,每一个label字数不固定,相似字数不少的那种,要求上下依次排列,当文字超出ScrollView的时候能够滑动,左右不能滚动,上下可滚动。【难度🌟🌟🌟】【出题人群内大佬:@起点】

出题人提示

就是label的宽度设置跟scrollView等宽,最底下的label底部要跟scrollView的底部约束上就能够了。 考察的主要是scrollView的约束问题。scrollView的约束主要是从内部撑开宽度跟高度。

答案

三个label 那个,就是放了个scrollview而后里面放三个label,从上往下边距所有约束为0,而后label宽度与scrollview相同,最下面那个label距离底部scrollview为0。(在内部无需多放view)

  1. 在 Scrollview 添加⼀个 ContainView
  2. ContentView 彻底覆盖 Scrollview
  3. ContainView 上添加了三个 Label。View 的 bottom 和 第三个 Label 的 bottom 作约束
  4. 三个 Label 互相作间距和宽的约束,不约束⾼

5. 经过将 Scrollview 的 ContentSize 和 ContainView 的 size 保持⼀致。

- (void)viewDidAppear:(BOOL)animated {
       [super viewDidAppear:animated];
       [self.contentView layoutIfNeeded];
        self.scrollview.contentSize =
       CGSizeMake(CGRectGetWidth(self.contentView.frame), CGRectGetHeight(self.contentView.frame));
}
复制代码

DEMO

6. 如何用一行代码,互换两个变量的值,且不产生第三个变量。

  • 利用Swift元组特性:

能够在定义的同时就取出元祖中的值

// 至关于同时定义了三个变量

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)

7. 如何给view同时加上圆角和阴影?至少给出两种实现方法,使用到的API越高级越好。

【提示】两种方法,答案提示:UIBezierPath,和iOS11 layer有个新的方法 【答案】iOS11的layer是maskedCorners,CACornerMask。

参考连接:ios 圆角 cornerRadius 对性能的影响究竟多大? 你测试过吗?

8. 猜测dequeueReusableCellWithIdentifier的实现是怎样的,给出示例代码。注意边界条件:相邻cell的identifier相等时。你的实现中该函数的时间复杂度是多少。为何?【难度🌟🌟🌟🌟】【出题人 微博@iOS程序犭袁】

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,而 NSSet / NSMutableSet / NSCountedSet

这些集合类型是无序没有重复元素。这样就能够经过 hash table 进行快速的操做。好比 addObject:, removeObject:, containsObject: 都是按照 O(1) 来的。须要注意的是将数组转成 Set 时会将重复元素合成一个,同时失去排序。

加之 for 循环,能够获得复杂度计算结果。

参考:深刻剖析 iOS 性能优化

7. 【在IM开发中】app 接收到一个message,上层UI刷新一次,要求考虑到CPU和电量消耗,解决短期内接收到不少条消息的问题。怎么解决?有几种方案?【出题人:远之²³³³-free zone-北】【 难度🌟🌟】

方案一:利用联结(在异步线程上调用dispatch_source_merge_data后,就会执行 dispatch source事先定义好的handler)、DISPATCH_SOURCE_TYPE_DATA_ADD,将刷新UI的工做拼接起来,短期内作尽可能少次数的刷新。

方案二:本身实现队列、肯定一个合适的时间阈值,在阈值时间到达时、主动取消息或者被动接受消息,最后刷新UI,达到消息限流的做用。举例:假设咱们消息的获取都是经过长链接推送过来的,而不是主动拉取的。能够用消息队列来作,消费者按期去队列取数据进行数据展现。或者假设前一条消息和后一条消息间隔只在0.2s之内,就能够认为是频繁收到消息。而后把这0.2s内的消息刷新相关操做,好比作个动画效果。

8. 如图label1在containerView上,containerView、label2在cell.contentView上问题:label1与label2的字数不固定,需求是,不管label2字数多少,label1都不能被拉伸或者压缩:【 难度🌟🌟🌟】【出题人:记忆、搁浅】

效果图见:

【答案】须要给label1设置一下优先级,设置平行的的Content compression resistance priority。

系统 Autolayout 参考 :Apple-Documentation-UIView-setContentHuggingPriority(_:for:)

Masonry 参考如下属性:

static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
   static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
复制代码

9.【计算机常识】若是你一直在用GitLab开发,如今公司要切换到GitHub开发,能够两个邮箱不同,你本身的提交记录,GitHub没法识别,签到数据也没了,请问如何让GitHub可以识别你整个仓库中全部的提交记录。【难度🌟🌟】【出题人 微博@iOS程序犭袁】

【注】“签到数据”指的是下图:

【答案】参考:

Git 实战手册(一): 批量修改log中的提交信息

使用git迁移git项目并保留提交记录

文章来源

iTeaTime(技术清谈)

相关文章
相关标签/搜索