1.怎么对接口返回的空数据null做数据保护?
网上有个很牛逼的第三方框架NullSafe,可以处理iOS开发中服务器返回null引起的崩溃。NullSafe是NSNull上的一个简单类别,它为无法识别的消息返回nil而不是抛出异常。底层现实的原理是同runtime对方法的转发进行最后的拦截,在forwardInvocation方法里面使target变为nil,因为nil在oc中是有效的指针。要使用NullSafe,只需将NullSafe.m文件拖到项目中即可。NullSafe将在运行时自动加载,您不需要在代码中包含任何头文件。
2.无侵入的界面埋点和事件埋点实现思路?
我们都知道,在 iOS 开发中最常见的三种埋点,就是对页面进入次数、页面停留时间、点击事件的埋点。对于这三种常见情况,我们都可以通过运行时方法替换技术来插入埋点代码以实现无侵入的埋点方法。
页面进入次数、页面停留时间都需要对 UIViewController 生命周期进行埋点,你可以创建一个 UIViewController 的 Category
@implementation UIViewController (logger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通过 @selector 获得被替换和替换方法的 SEL,作为 SMHook:hookClass:fromeSelector:toSelector 的参数传入
SEL fromSelectorAppear = @selector(viewWillAppear:);
SEL toSelectorAppear = @selector(hook_viewWillAppear:);
[SMHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
SEL fromSelectorDisappear = @selector(viewWillDisappear:);
SEL toSelectorDisappear = @selector(hook_viewWillDisappear:);
[SMHook hookClass:self fromSelector:fromSelectorDisappear toSelector:toSelectorDisappear];
});
}
- (void)hook_viewWillAppear:(BOOL)animated {
// 先执行插入代码,再执行原 viewWillAppear 方法
[self insertToViewWillAppear];
[self hook_viewWillAppear:animated];
}
- (void)hook_viewWillDisappear:(BOOL)animated {
// 执行插入代码,再执行原 viewWillDisappear 方法
[self insertToViewWillDisappear];
[self hook_viewWillDisappear:animated];
}
- (void)insertToViewWillAppear {
// 在 ViewWillAppear 时进行日志的埋点
[[[[SMLogger create]
message:[NSString stringWithFormat:@"%@ Appear",NSStringFromClass([self class])]]
classify:ProjectClassifyOperation]
save];
}
- (void)insertToViewWillDisappear {
// 在 ViewWillDisappear 时进行日志的埋点
[[[[SMLogger create]
message:[NSString stringWithFormat:@"%@ Disappear",NSStringFromClass([self class])]]
classify:ProjectClassifyOperation]
save];
}
@end
可以看到,Category 在 +load() 方法里使用了SMHook 进行方法替换,在替换的方法里执行需要埋点的方法 [self insertToViewWillAppear]。
对于点击事件来说,我们也可以通过运行时方法替换的方式进行无侵入埋点,这里最主要的工作是,找到这个点击事件的方法,sendAction:to:forEvent:然后在 +load() 方法使用 SMHook 替换成为你定义的方法。完整代码实现如下:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通过 @selector 获得被替换和替换方法的 SEL,作为 SMHook:hookClass:fromeSelector:toSelector 的参数传入
SEL fromSelector = @selector(sendAction:to:forEvent:);
SEL toSelector = @selector(hook_sendAction:to:forEvent:);
[SMHook hookClass:self fromSelector:fromSelector toSelector:toSelector];
});
}
- (void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[self insertToSendAction:action to:target forEvent:event];
[self hook_sendAction:action to:target forEvent:event];
}
- (void)insertToSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 日志记录
if ([[[event allTouches] anyObject] phase] == UITouchPhaseEnded) {
NSString *actionString = NSStringFromSelector(action);
NSString *targetName = NSStringFromClass([target class]);
[[[SMLogger create] message:[NSString stringWithFormat:@"%@ %@",targetName,actionString]] save];
3.重写了属性的set方法和get方法,会怎么样?要怎么解决?
写了get和set方法之后@property默认生成的@synthesize就不会起作用了,这也就意味着你的类不会自动生成出来实例变量了,你就必须要自己声明实例变量,
4.怎么通过view去找到对应的控制器?
可以通过一个第三方框架PSRouter去获取,
找到当前view所在的控制器- (UIViewController *)findViewController:(UIView *)sourceView
{
id target=sourceView;
while (target) {
target = ((UIResponder *)target).nextResponder;
if ([target isKindOfClass:[UIViewController class]]) {
break;
}
}
return target;
}
5.怎么查找两个view的公共父视图
首先我们写一个函数去找到一个View的所有父view,并返回一个数组,这个数组存储着这条链上的所有view。
-(NSArray*)superViews:(UIView *)viewA{
if (viewA == nil) {
return @[];
}
NSMutableArray* superViewsArray = [NSMutableArray array];
if (viewA != nil) {
[superViewsArray addObject:viewA];
viewA = viewA.superview;
}
return [superViewsArray copy];
}
//我们把两个view的所有父view都存在了数组里,那么想要比较两个view最近的根节点其实就是从头比较两个数组,看第一个公共元素是什么,就是我们要找的第一个公共父view啦!
-(UIView*)commonView:(UIView*)viewA andView:(UIView*)viewB{
NSArray* superViewA = [self superViews:viewA];
NSArray* superViewB = [self superViews:viewB];
NSSet* view2Set = [NSSet setWithArray:superViewB];
UIView* resultView = [[UIView alloc] init];
for (NSUInteger i =0; i
if ([view2Set containsObject:superViewA[i]]) {
resultView = superViewA[i];
return resultView;// 找到了就马上返回,不要再继续找了
}
}
return nil;