新浪微博

新浪微博

1.打印结果

1
2
3
4
5
6
- (void)test {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务1");
});
NSLog(@"任务2");
}

2.打印结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(sem, timeout);
NSLog(@"开始执行任务1");
sleep(2);
NSLog(@"任务1执行结束");
dispatch_semaphore_signal(sem);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
NSLog(@"开始执行任务2");
dispatch_semaphore_wait(sem, timeout);
NSLog(@"任务2执行结束");
dispatch_semaphore_signal(sem);
});

3.以下关于 CALayer 的描述,哪项是正确的?

A. CALayer 只能处理基本的触摸事件
B. CALayer 的绘制主要在 CPU 端完成,而 CAAnimation 的动画执行在 GPU 上加速
C. CALayer.contents 只能设置为 CGImageRef
D. CALayer 不能有层级结构,而 UIView 可以

4.设计 service 层,播放 gif 或视频,service 被谁持有?只是类方法作为工具方法的话意义不大

5.从某一个入口进入到页面,在 viewWillAppear 里打日志,可能是不同的页面,入口是后端下发的,不用 baseVC,如何实现?

面试前需要完成的AI编程题见最后。

参考答案

1.打印结果

1
2
3
4
5
6
- (void)test {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务1");
});
NSLog(@"任务2");
}

死锁,没有任何输出

若改为 dispatch_async,输入顺序是:任务2 任务1

2.打印结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(sem, timeout);
NSLog(@"开始执行任务1");
sleep(2);
NSLog(@"任务1执行结束");
dispatch_semaphore_signal(sem);
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
NSLog(@"开始执行任务2");
dispatch_semaphore_wait(sem, timeout);
NSLog(@"任务2执行结束");
dispatch_semaphore_signal(sem);
});

执行过程分析

  1. 创建信号量 sem,初始值为 1。
  2. 信号量初始为 1,表示允许一个线程进入临界区。

任务 1

  • 立即开始执行(因为是异步提交)。
  • dispatch_semaphore_wait(sem, timeout) 时,sem 的值为 1 → 减为 0 → 立即通过。
  • 打印:开始执行任务1
  • 然后 sleep(2),意味着任务 1 占用信号量约 2 秒。

任务 2

  • 延迟 1 秒后才开始(因为 sleep(1))。
  • 打印:开始执行任务2
  • 此时(第 1 秒时),任务 1 还在执行(sleep(2)),sem = 0。
  • 所以 dispatch_semaphore_wait(sem, timeout) 会阻塞等待信号量变为 1。

超时判断

  • 任务 2 最多等待 3 秒。
  • 任务 1 在第 2 秒时执行完毕,打印:任务1执行结束
  • 并 dispatch_semaphore_signal(sem),使 sem = 1。
  • 此时任务 2 等待时间仅约 1 秒(没超时),于是立即通过。
  • 继续打印:任务2执行结束

最终打印顺序

1
2
3
4
开始执行任务1
开始执行任务2
任务1执行结束
任务2执行结束

延伸思考

如果把任务 1 的 sleep(2) 改成 sleep(5),则任务 2 等待信号量会超时 3 秒后直接返回。
此时 dispatch_semaphore_wait 返回非 0(超时),任务 2 会继续执行后续代码,打印顺序会变为:

1
2
3
4
开始执行任务1
开始执行任务2
任务2执行结束 ← 超时先执行
任务1执行结束

3.以下关于 CALayer 的描述,哪项是正确的?

A. CALayer 只能处理基本的触摸事件
B. CALayer 的绘制主要在 CPU 端完成,而 CAAnimation 的动画执行在 GPU 上加速
C. CALayer.contents 只能设置为 CGImageRef
D. CALayer 不能有层级结构,而 UIView 可以

解析:

A.错误

CALayer 不处理任何触摸事件,触摸事件完全由 UIView(或更上层的 UIResponder)处理。

B.正确

  • 解释:
    • CALayer 的内容绘制(drawInContext:)通常在 CPU 上进行;
    • 绘制完成后,Core Animation 将位图数据提交到 GPU
    • 当应用 CAAnimation(比如 position、opacity、transform)时,动画由 Render Server + GPU 执行;
    • 因此动画可以在主线程外平滑运行,不会被 CPU 卡顿直接阻塞。

C.错误

  • CALayer 的 contents 是 id 类型,通常用来显示 CGImageRef(UIImage 的底层图像)
  • 但也可以设置为其他类型(如 CIImage、NSImage,底层仍会转成 CGImageRef)。

D.错误

  • CALayer 同样有层级结构

    1
    [parentLayer addSublayer:childLayer];

4.设计 Service 层,播放 GIF 或视频,service 被谁持有?只是类方法作为工具方法的话意义不大

设计一个 Service 层(播放服务),支持以下功能:

  1. 支持 GIF 和视频 播放。
  2. 对外提供统一接口(不关心内部是视频还是 GIF)。
  3. 支持控制播放、暂停、停止。
  4. 可扩展(未来可能支持 Live Photo、动画序列帧等)。

我们用 策略模式 + 简单工厂 来实现。

  1. 播放协议(统一接口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// LJRPPlayable.h
@protocol LJRPPlayable <NSObject>

/// 准备播放资源
- (void)prepareWithURL:(NSURL *)url inView:(UIView *)containerView;

/// 开始播放
- (void)play;

/// 暂停
- (void)pause;

/// 停止并释放资源
- (void)stop;

/// 当前是否在播放
- (BOOL)isPlaying;

@end
  1. GIF 播放器实现

你可以用 FLAnimatedImageView 或 SDAnimatedImageView 来播放 GIF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// LJRPGifPlayer.h
#import "LJRPPlayable.h"
#import <SDWebImage/SDAnimatedImageView.h>

@interface LJRPGifPlayer : NSObject <LJRPPlayable>
@end


// LJRPGifPlayer.m
#import "LJRPGifPlayer.h"

@interface LJRPGifPlayer ()
@property (nonatomic, strong) SDAnimatedImageView *imageView;
@property (nonatomic, assign) BOOL isPlaying;
@end

@implementation LJRPGifPlayer

- (void)prepareWithURL:(NSURL *)url inView:(UIView *)containerView {
self.imageView = [[SDAnimatedImageView alloc] initWithFrame:containerView.bounds];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[containerView addSubview:self.imageView];
[self.imageView sd_setImageWithURL:url];
}

- (void)play {
[self.imageView startAnimating];
self.isPlaying = YES;
}

- (void)pause {
[self.imageView stopAnimating];
self.isPlaying = NO;
}

- (void)stop {
[self.imageView stopAnimating];
[self.imageView removeFromSuperview];
self.imageView = nil;
self.isPlaying = NO;
}

- (BOOL)isPlaying {
return self.isPlaying;
}

@end
  1. 视频播放器实现

使用 AVPlayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// LJRPVideoPlayer.h
#import "LJRPPlayable.h"
#import <AVFoundation/AVFoundation.h>

@interface LJRPVideoPlayer : NSObject <LJRPPlayable>
@end


// LJRPVideoPlayer.m
#import "LJRPVideoPlayer.h"

@interface LJRPVideoPlayer ()
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@end

@implementation LJRPVideoPlayer

- (void)prepareWithURL:(NSURL *)url inView:(UIView *)containerView {
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
self.player = [AVPlayer playerWithPlayerItem:item];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = containerView.bounds;
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[containerView.layer addSublayer:self.playerLayer];
}

- (void)play {
[self.player play];
}

- (void)pause {
[self.player pause];
}

- (void)stop {
[self.player pause];
[self.playerLayer removeFromSuperlayer];
self.player = nil;
self.playerLayer = nil;
}

- (BOOL)isPlaying {
return self.player.rate != 0;
}

@end
  1. Service 层:统一调度入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// LJRPPlayService.h
#import <Foundation/Foundation.h>
#import "LJRPPlayable.h"

typedef NS_ENUM(NSUInteger, LJRPPlayType) {
LJRPPlayTypeGif,
LJRPPlayTypeVideo
};

@interface LJRPPlayService : NSObject

+ (instancetype)sharedService;

- (id<LJRPPlayable>)createPlayerWithType:(LJRPPlayType)type;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// LJRPPlayService.m
#import "LJRPPlayService.h"
#import "LJRPGifPlayer.h"
#import "LJRPVideoPlayer.h"

@implementation LJRPPlayService

+ (instancetype)sharedService {
static LJRPPlayService *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}

- (id<LJRPPlayable>)createPlayerWithType:(LJRPPlayType)type {
switch (type) {
case LJRPPlayTypeGif:
return [[LJRPGifPlayer alloc] init];
case LJRPPlayTypeVideo:
return [[LJRPVideoPlayer alloc] init];
default:
return nil;
}
}

@end
  1. 使用示例
1
2
3
4
5
6
NSURL *url = [NSURL URLWithString:@"https://example.com/demo.mp4"];
UIView *container = self.view; // 播放容器

id<LJRPPlayable> player = [[LJRPPlayService sharedService] createPlayerWithType:LJRPPlayTypeVideo];
[player prepareWithURL:url inView:container];
[player play];

同样地,如果是 GIF:

1
2
3
id<LJRPPlayable> player = [[LJRPPlayService sharedService] createPlayerWithType:LJRPPlayTypeGif];
[player prepareWithURL:[NSURL URLWithString:@"https://example.com/demo.gif"] inView:container];
[player play];

优点总结:

  • 高扩展性:未来新增 LJRPLivePhotoPlayer 只需实现 LJRPPlayable。
  • 低耦合:上层只依赖 Service 和 Protocol。
  • 清晰职责:播放逻辑下沉到 Service 层,UI 层只负责展示。

5.从某一个入口进入到页面,在 viewWillAppear 里打日志,可能是不同的页面,入口是后端下发的,不用 baseVC,如何实现?

  • 页面可能是不同的 VC,不共用 BaseViewController;
  • 页面入口是后端下发的路由跳转;
  • 想在页面的 viewWillAppear 里打日志(比如统计从哪个入口进入了哪个页面);
  • 希望不用改每个 VC 的代码(或者尽量少改)。

在全局对 UIViewController 的 viewWillAppear: 做一次 Method Swizzling,自动插入日志逻辑。

无需修改每个页面,只需在工程初始化时执行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// UIViewController+LogAppear.m
#import "UIViewController+LogAppear.h"
#import <objc/runtime.h>

@implementation UIViewController (LogAppear)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(ljrp_viewWillAppear:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}

- (void)ljrp_viewWillAppear:(BOOL)animated {
[self ljrp_viewWillAppear:animated]; // 调用原实现

// 过滤系统VC(如 UINavigationController / UIInputWindowController)
NSString *className = NSStringFromClass([self class]);
if (![className hasPrefix:@"UI"]) {
NSLog(@"[RouteLog] %@ will appear via route: %@", className, [self ljrp_routeEntry]);
}
}

// 可选:保存入口信息
- (void)setLjrp_routeEntry:(NSString *)entry {
objc_setAssociatedObject(self, @selector(ljrp_routeEntry), entry, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)ljrp_routeEntry {
return objc_getAssociatedObject(self, _cmd);
}

@end

附:面试前需要完成的AI编程题

AI编程面试题

答题说明

  • 请使用如通义灵码、Trae、Cursor及Windsurf等领先的国内外AI辅助开发工具来提升您的编程效率
  • 题目中如有需要调用大模型的场景,请自行注册阿里云大模型、deepseek、火山引擎等大模型服务的API Key后进行调用,或者其他任何适合调用大模型的方法均可使用
  • 题目考核的重点是对AI编程技术的理解与应用能力,而非严格的限制性要求。在满足基本要求的基础上,您可以自由发挥创意,设计独特的用户界面,并灵活选择合适的框架和编程语言。
  • 面试前请将代码整理并打包发送给面试官。建议面试时携带答题所用的电脑,以便现场演示实现效果

以下题目,任选一个作答即可

一、题目:用户注册登录系统

开发一个用户注册、登录、个人信息认证系统。

任务需求:

  • 语言、开发框架不限
  • 系统包含Web前端界面和后端服务,支持用户和管理员登录
  • 界面布局自行设计
  • 用户通过手机号码注册(不需要发送验证码,仅把手机号当账号名)
  • 通过账号密码登录
  • 登录后,支持账号退出登录
  • 用户登录后,可查看个人注册信息,并显示实名制认证状态
  • 用户登录后,上传身份证正反面照片、姓名、身份证号信息到后端服务,进行实名认证
  • 以管理员身份登录,查看照片、姓名、身份证号等用户信息,并设置用户实名信息是否验证通过

二、题目:新闻评论生成器

开发一个全栈式新闻评论生成系统,用户输入新闻链接后,系统自动生成新闻摘要并创建多条多样化的评论,支持不同语言风格和观点倾向。

任务需求:

  • 语言、开发框架不限
  • 系统包含Web前端界面和后端服务
  • 界面布局自行设计
  • 支持输入一个网页url链接,并从网页中获取新闻,去除广告、导航等无关内容
  • 使用大模型,根据新闻内容生成摘要,要求简洁,包含标题,主要事件和关键信息
  • 使用大模型,为新闻生成多条不同语言风格、观点的评论,支持按照设定的风格和观点比例生成
  • 支持以CSV格式导出全部评论
  • 支持历史记录展示
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×