在西安乐橙实习的3个月

3个月前经历了一波电话实习面试后,签了几个暑期之后再实习的offer。然后我想现在离暑假还有将近4个月呢,学校的课程那么少又无聊,待在学校肯定没事干,还不如先在西安找一个实习工作呢!恰好这时看到了飞饭(西安乐橙)发的 iOS 招聘帖,果断投了,当天就收到了面试邀请。

第二天来到公司先和 HR 小聊了一下,然后就直接让一个 iOS 技术负责面我了,他就问了我几个基础的问题(很基础的,所以具体是什么我忘了),让我介绍了一下项目经历,于是就跟我聊人生理想了。之后又一起和技术总监聊了一会,现场就确定了offer,明天上班,并且跟我说了公司即将要做的项目,意思就是让我独自一人开发,一个月后出第一个版本。我当时就觉得莫名其妙,这节奏也太快了吧,敢让一个实习生独立做一个新项目?。不过鉴于不错的薪水呢,我还是签下了这个offer。

第三天就直接上班了,超大的技术部办公室居然只有2个人!一个是 CEO 同学,一个是产品经理兼运营总监,也就是说我是第一个程序员!不过办公室里都是一堆开发机和一些遗留下来的技术书籍,我也能想到大概是今年外卖行业的崛起(美团,饿了么)直接抢走了飞饭在外卖市场的份额吧,导致了技术人员的流失,这真是一个悲伤的故事。

过了几天公司入职了一个 Android 实习生,居然是同校的研究生。之后呢,技术部陆陆续续招了很多人,到现在(7月份)已经有12个人了,就在今天还刚入职了一个西安交大的 java 实习生。所以呢,我也算是见证了一个新项目开发团队的成长。

很感谢技术总监对我的信任,很高兴能认识这群小伙伴,虽然不舍,但我7月底就要去北京开启新的生活了。下面就写一写我这三个月以来项目开发中遇到的问题以及我作为 iOS 面试官的经历,也算做一个阶段总结。


出于某些原因呢,技术总监决定让我自己来面 iOS 的应聘者。陆续在我这面了10几个人吧,只通过了2个。由于水平有限,我当然只能问我自己非常熟悉的问题,因此我认为以下几个问题适合基础不错的初级iOS 工程师,这些问题大概都是从我面试时被问的问题里选出来的,具体可以看我之前写的一篇博客

  • 语言方面
    1. Objective-C 的内存管理, strong/weak/assign 的区别,如果答得不错,再问 __unsafe_unretained
    2. 引用循环是怎么一回事(居然还有人问我什么是引用循环)
    3. Objective-C 有私有变量,私有方法吗?
    4. protocol 能添加属性吗?
  • iOS相关
    1. 视图控制器所管理的视图的生命周期
    2. 通知中心/KVO/代理它们的区别
    3. 多线程编程的那几种技术和它们之间的关系
    4. 如果简历上有写会Autolayout, 就问Autolayout的原理(很多人只会加约束,却不知道Autolayout和Frame之间的关系)

令我吃惊的是大多数人连视图控制器所管理的视图的生命周期都说不出来,而说得上来的都忘了和布局有关的方法viewWill/DidLayoutSubView,而这个方法真的是很重要,不是吗?最终和我聊得比较来的一个是北京理工大学软件工程的,一个是西安工业大学CS的,由此也可见科班出身的确实有不错的基础实力。


再说项目代码方面。 UI我们使用纯代码布局,这让 Git 协作变得非常方便。在ViewController代码的结构组织上,我采用了这篇博客的方案,也就是把代码分区成下图这样 ,并且属性全用 Getter 和 Setter。这样给项目带来的好出就是大大加强了代码的可读性,即使是写到了1000行的 ViewController 也能让别人快速上手。

在网络层的设计上,我基于 AFNetwork 和 MagicalRecord 对网络 API 进行了一个简单的封装:利用 AFNetwork 请求回数据后立刻把结果用 MagicalRecord 的MR_importFromObject方法映射成一个 CoreData 对象。而对每一个网络接口暴露一个static方法。大概就是下面这样:

@class Account;
@interface WebServiceClient : NSObject

+ (void)fetchAccountInfoWithSuccess:(void (^)(Account *account))success
                            failure:(void (^)(NSString *msg))failure;

@end

@implementation WebServiceClient

+ (AFHTTPSessionManager *)sessionManager {
    static AFHTTPSessionManager *sessionManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseURLString]];
        // 根据需要进行一系列的设置
    });
    return sessionManager;
}

+ (void)fetchAccountInfoWithSuccess:(void (^)(Account *account))success
                            failure:(void (^)(NSString *msg))failure {

    NSString *url = @"##";
    url = [###];//与baseURL合并

    [[self sessionManager] GET:url parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {

        if (请求成功) {
            success([Account MR_importFromObject:responseObject[kDataKey]]);
        } else {
            failure(responseObject[kErrorMsgKey]);
        }
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        if (failure) {
            failure(kNetworkErrorInfo);
        }
    }];

}
@end
先说一下这样设计的好处:
  1. 方便使用,请求接口的人只需要调 [WebServiceClient ...] 即可完成一个网络请求
  2. 返回的对象是 CoreData 对象,并且这个对象已经放入了一个 NSManagedContext 中。相当于作了一个 In-memory 的缓存,你可以先加载存在 context 中的数据,再请求新数据。并且如果需要对某些数据进行持久化,可把这个 CoreData 对象拷贝到另一个 Context 中,然后 Save 这个 Context。 注意这里用到了两个 Context,一个负责持久化,而另一个只负责对象图的管理和In-memory缓存
    说明一下MR_importFromObject的机制,要使用这个方法正确的导入数据,需要给Entitty添加一个relatedByAttribute的key, 相当于作为这个Entity的主键,具体可以去看官方文档。导入的时候首先会根据这个key检查context有没重复的数据,如果没有,才创建一个新的CoreData对象,因此,这个方法保证了不会导入重复的数据
然而随着项目变大,网络接口越来越多,我也发现了这样的设计存在很多缺陷:
  1. 暂时没有取消一个网络请求的机制(虽然没有这个需求),不过这不是什么大问题, 让每个方法返回NSURLSessionTask就好了
  2. 很多接口除了url,参数,返回对象不一样,其处理逻辑基本一样,这导致我写了很多重复的代码,很多接口甚至就是复制粘贴把参数一改就好了。然而,我是很痛恨写重复代码的,相同的代码写两遍就是罪过
  3. 接口太多全写在一个类里不易于管理维护
  4. 和 CoreData 的耦合度太高,这在一定程度上不易于测试

我认为改进方案可以使用YTKNetwork的基本思想,把每一个网络请求封装成对象,好处是

  • 将网络请求与具体的第三方库依赖隔离,方便以后更换底层的网络库。实际上 YTKNetwork 最初是基于 ASIHttpRequest 的,我们只花了两天,就很轻松地切换到了 AFNetworking。
  • 方便在基类中处理公共逻辑,例如猿题库的数据版本号信息就统一在基类中处理。
  • 方便在基类中处理缓存逻辑,以及其它一些公共逻辑。 方便做对象的持久化。

然后给业余层交付数据直接给NSDictionary,我们可以另外用Adaptor模式把这个NSDictionary转成模型对象

comments powered by Disqus