遇到个需求需要涉及到视频播放,那么没办法,先找资料开始进一步了解下这个不熟悉的东西 . 一个是 MP ,一个

是AV,MP是封装好的,用起来非常简单,但是自定义样式就基本不可能了。AVPlayer存在于AVFundation中,更接近

于底层,所以灵活性更强大,废话不多说,咱们先简单写个Demo看下他的工作原理,然后模仿网易新闻写个界面出

来,这里用到了一个封装的框架,如果不熟悉内部原理的同学可以先看看我写的第一个Demo,基本所有逻辑都有。


这里容我啰嗦一句:

开发中,单纯的使用AVPlayer类是无法播放视频的,需要将视频层添加到AVPLayerLayer层,这样视频才能显示出

来,Layer的定义方式有两种,一种是下面这种直接使用PlayerLayer,还有一个就是自己做一个View,然后把他自身的Layer改成playerLayer

第一种方式:

[objc] view plaincopy
  1. self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
  2. self.playerLayer.videoGravity     = AVLayerVideoGravityResizeAspect;
  3. self.playerLayer.frame = self.view.bounds;
  4. [self.view.layer addSublayer:self.playerLayer];


第二种方式:

[objc] view plaincopy
  1. //修改当前view的 layer的 class
  2. +(Class)layerClass
  3. {
  4. //AVPlayerLayer
  5. return [AVPlayerLayer class];
  6. }

不BB了,直接看图说话,先看看最终的效果图   

      


只能上传2M的东东,这视频一帧一帧消耗太快了,都不敢多录了,各位大爷将就着看吧。。。。。。

不要来打我,不然我让我表哥打死你     


先简单介绍下AVPlayer的用法

很多朋友应该和我一样,一开始接触视频的时候都不知道用什么东东来写,如果是大神

就直接下载Demo吧。小白来介绍下,我也第一次用

第一:初始化播放器

[objc] view plaincopy
  1. // 初始化播放器item
  2. self.playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://flv2.bn.netease.com/videolib3/1608/30/zPuaL7429/SD/zPuaL7429-mobile.mp4"]];
  3. self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
  4. // 初始化播放器的Layer
  5. self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
  6. // layer的frame
  7. self.playerLayer.frame = self.backView.bounds;
  8. // layer的填充属性 和UIImageView的填充属性类似
  9. // AVLayerVideoGravityResizeAspect 等比例拉伸,会留白
  10. // AVLayerVideoGravityResizeAspectFill // 等比例拉伸,会裁剪
  11. // AVLayerVideoGravityResize // 保持原有大小拉伸
  12. self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
  13. // 把Layer加到底部View上
  14. [self.backView.layer insertSublayer:self.playerLayer atIndex:0];

第二:给播放器加监听以及屏幕旋转的通知

[objc] view plaincopy
  1. // 监听播放器状态变化
  2. [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
  3. // 监听缓存进去,就是大家所看到的一开始进去底部灰色的View会迅速加载
  4. [self.playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
  5. //旋转屏幕通知
  6. [[NSNotificationCenter defaultCenter] addObserver:self
  7. selector:@selector(onDeviceOrientationChange)
  8. name:UIDeviceOrientationDidChangeNotification
  9. object:nil
  10. ];

第三步:实现KVO的监听方法

[objc] view plaincopy
  1. // 监听播放器的变化属性
  2. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(voidvoid *)context
  3. {
  4. if ([keyPath isEqualToString:@"status"])
  5. {
  6. AVPlayerItemStatus statues = [change[NSKeyValueChangeNewKey] integerValue];
  7. switch (statues) {
  8. // 监听到这个属性的时候,理论上视频就可以进行播放了
  9. case AVPlayerItemStatusReadyToPlay:
  10. // 最大值直接用sec,以前都是
  11. // CMTimeMake(帧数(slider.value * timeScale), 帧/sec)
  12. self.slider.maximumValue = CMTimeGetSeconds(self.playerItem.duration);
  13. [self initTimer];
  14. // 启动定时器 5秒自动隐藏
  15. if (!self.autoDismissTimer)
  16. {
  17. self.autoDismissTimer = [NSTimer timerWithTimeInterval:8.0 target:self selector:@selector(autoDismissView:) userInfo:nil repeats:YES];
  18. [[NSRunLoop currentRunLoop] addTimer:self.autoDismissTimer forMode:NSDefaultRunLoopMode];
  19. }
  20. break;
  21. case AVPlayerItemStatusUnknown:
  22. break;
  23. // 这个就是不能播放喽,加载失败了
  24. case AVPlayerItemStatusFailed:
  25. // 这时可以通过`self.player.error.description`属性来找出具体的原因
  26. break;
  27. default:
  28. break;
  29. }
  30. }
  31. else if ([keyPath isEqualToString:@"loadedTimeRanges"]) // 监听缓存进度的属性
  32. {
  33. // 计算缓存进度
  34. NSTimeInterval timeInterval = [self availableDuration];
  35. // 获取总长度
  36. CMTime duration = self.playerItem.duration;
  37. CGFloat durationTime = CMTimeGetSeconds(duration);
  38. // 监听到了给进度条赋值
  39. [self.progressView setProgress:timeInterval / durationTime animated:NO];
  40. }
  41. }

AVPlayerItemStatusReadyToPlay

AVPlayerItemStatusFailed

这两个属性还比较好理解,是个人都知道,但是这个是什么鬼

AVPlayerItemStatusUnknown

内部是这么解释的

Indicates that the status of the player item is not yet known because it has not tried to load new media resources

for playback.

fk u 我英语不好,看不懂啊,估计是playerItem这个视频资源对象挂了,识别不了,暂时不知道怎么处理

      

第四步:调用Player的方法观察时间变化更新播放进度

来个官方的说话,显得我比较牛B



[objc] view plaincopy
  1. // 调用plaer的对象进行UI更新
  2. - (void)initTimer
  3. {
  4. // player的定时器
  5. __weak typeof(self)weakSelf = self;
  6. // 每秒更新一次UI Slider
  7. [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
  8. // 当前时间
  9. CGFloat nowTime = CMTimeGetSeconds(weakSelf.playerItem.currentTime);
  10. // 总时间
  11. CGFloat duration = CMTimeGetSeconds(weakSelf.playerItem.duration);
  12. // sec 转换成时间点
  13. weakSelf.nowLabel.text = [weakSelf convertToTime:nowTime];
  14. weakSelf.remainLabel.text = [weakSelf convertToTime:(duration - nowTime)];
  15. // 不是拖拽中的话更新UI
  16. if (!weakSelf.isDragSlider)
  17. {
  18. weakSelf.slider.value = CMTimeGetSeconds(weakSelf.playerItem.currentTime);
  19. }
  20. }];
  21. }
[objc] view plaincopy
  1. // sec 转换成指定的格式
  2. - (NSString *)convertToTime:(CGFloat)time
  3. {
  4. // 初始化格式对象
  5. NSDateFormatter *fotmmatter = [[NSDateFormatter alloc] init];
  6. // 根据是否大于1H,进行格式赋值
  7. if (time >= 3600)
  8. {
  9. [fotmmatter setDateFormat:@"HH:mm:ss"];
  10. }
  11. else
  12. {
  13. [fotmmatter setDateFormat:@"mm:ss"];
  14. }
  15. // 秒数转换成NSDate类型
  16. NSDate *date = [NSDate dateWithTimeIntervalSince1970:time];
  17. // date转字符串
  18. return [fotmmatter stringFromDate:date];
  19. }

第五步:给背景View加个手势,点击的时候让title和时间进度条消失或者几秒钟自动消失

[objc] view plaincopy
  1. // 启动定时器 5秒自动隐藏
  2. // 咱们这种初始化定时器的方式需要自己手动加到runloop上
  3. // scheduledTimerWithTimeInterval用这个的时候就不需要手动加到runloop中
  4. if (!self.autoDismissTimer)
  5. {
  6. self.autoDismissTimer = [NSTimer timerWithTimeInterval:8.0 target:self selector:@selector(autoDismissView:) userInfo:nil repeats:YES];
  7. [[NSRunLoop currentRunLoop] addTimer:self.autoDismissTimer forMode:NSDefaultRunLoopMode];
  8. }
[objc] view plaincopy
  1. #pragma mark - 自动隐藏bottom和top
  2. - (void)autoDismissView:(NSTimer *)timer
  3. {
  4. // player的属性rate
  5. /* indicates the current rate of playback; 0.0 means "stopped", 1.0 means "play at the natural rate of the current item" */
  6. if (self.player.rate == 0)
  7. {
  8. // 暂停状态就不隐藏
  9. }
  10. else if (self.player.rate == 1)
  11. {
  12. if (self.bottomView.alpha == 1)
  13. {
  14. [UIView animateWithDuration:1.0 animations:^{
  15. self.bottomView.alpha = 0;
  16. self.topView.alpha = 0;
  17. }];
  18. }
  19. }
  20. }

第六步:来个全屏小屏幕切换示例

其实切换的时候就是把只之前的Layer移除,然后重新布局,加到KeyWindow中去

[objc] view plaincopy
  1. // 全屏显示
  2. -(void)toFullScreenWithInterfaceOrientation:(UIInterfaceOrientation )interfaceOrientation{
  3. // 先移除之前的
  4. [self.backView removeFromSuperview];
  5. // 初始化
  6. self.backView.transform = CGAffineTransformIdentity;
  7. if (interfaceOrientation==UIInterfaceOrientationLandscapeLeft) {
  8. self.backView.transform = CGAffineTransformMakeRotation(-M_PI_2);
  9. }else if(interfaceOrientation==UIInterfaceOrientationLandscapeRight){
  10. self.backView.transform = CGAffineTransformMakeRotation(M_PI_2);
  11. }
  12. // BackView的frame能全屏
  13. self.backView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
  14. // layer的方向宽和高对调
  15. self.playerLayer.frame = CGRectMake(0, 0, kScreenHeight, kScreenWidth);
  16. // remark 约束
  17. [self.bottomView mas_remakeConstraints:^(MASConstraintMaker *make) {
  18. make.height.mas_equalTo(50);
  19. make.top.mas_equalTo(kScreenWidth-50);
  20. make.left.equalTo(self.backView).with.offset(0);
  21. make.width.mas_equalTo(kScreenHeight);
  22. }];
  23. [self.topView mas_remakeConstraints:^(MASConstraintMaker *make) {
  24. make.height.mas_equalTo(50);
  25. make.left.equalTo(self.backView).with.offset(0);
  26. make.width.mas_equalTo(kScreenHeight);
  27. }];
  28. [self.closeButton mas_remakeConstraints:^(MASConstraintMaker *make) {
  29. make.left.equalTo(self.backView).with.offset(5);
  30. make.height.mas_equalTo(30);
  31. make.width.mas_equalTo(30);
  32. make.top.equalTo(self.backView).with.offset(10);
  33. }];
  34. [self.titleLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  35. make.left.equalTo(self.topView).with.offset(45);
  36. make.right.equalTo(self.topView).with.offset(-45);
  37. make.center.equalTo(self.topView);
  38. make.top.equalTo(self.topView).with.offset(0);
  39. }];
  40. [self.nowLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  41. make.left.equalTo(self.slider.mas_left).with.offset(0);
  42. make.top.equalTo(self.slider.mas_bottom).with.offset(0);
  43. make.size.mas_equalTo(CGSizeMake(100, 20));
  44. }];
  45. [self.remainLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
  46. make.right.equalTo(self.slider.mas_right).with.offset(0);
  47. make.top.equalTo(self.slider.mas_bottom).with.offset(0);
  48. make.size.mas_equalTo(CGSizeMake(100, 20));
  49. }];
  50. // 加到window上面
  51. [[UIApplication sharedApplication].keyWindow addSubview:self.backView];
  52. }
  53. // 缩小到cell
  54. -(void)toCell{
  55. // 先移除
  56. [self.backView removeFromSuperview];
  57. __weak typeof(self)weakSelf = self;
  58. [UIView animateWithDuration:0.5f animations:^{
  59. weakSelf.backView.transform = CGAffineTransformIdentity;
  60. weakSelf.backView.frame = CGRectMake(0, 80, kScreenWidth, kScreenHeight / 2.5);
  61. weakSelf.playerLayer.frame =  weakSelf.backView.bounds;
  62. // 再添加到View上
  63. [weakSelf.view addSubview:weakSelf.backView];
  64. // remark约束
  65. [self.bottomView mas_remakeConstraints:^(MASConstraintMaker *make) {
  66. make.left.equalTo(weakSelf.backView).with.offset(0);
  67. make.right.equalTo(weakSelf.backView).with.offset(0);
  68. make.height.mas_equalTo(50);
  69. make.bottom.equalTo(weakSelf.backView).with.offset(0);
  70. }];
  71. [self.topView mas_remakeConstraints:^(MASConstraintMaker *make) {
  72. make.left.equalTo(weakSelf.backView).with.offset(0);
  73. make.right.equalTo(weakSelf.backView).with.offset(0);
  74. make.height.mas_equalTo(50);
  75. make.top.equalTo(weakSelf.backView).with.offset(0);
  76. }];
  77. [self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
  78. make.left.equalTo(weakSelf.backView).with.offset(5);
  79. make.centerY.equalTo(weakSelf.topView);
  80. make.size.mas_equalTo(CGSizeMake(30, 30));
  81. }];
  82. }completion:^(BOOL finished) {
  83. }];
  84. }

基本逻辑差不多介绍完了,效果就这样的


  


下面咱们试着写个网易播放视频的Demo,在tableView中使用下,效果图已经在最上面了

这里无非多了几个属性

@property (nonatomic,strong)NSIndexPath *currentIndexPath; // 当前播放的cell

@property (nonatomic,assign)BOOL isSmallScreen; //是否放置在window上

@property(nonatomic,strong)ViedoTableViewCell *currentCell; // 当前cell

分析1:全屏小屏切换的时候回到指定的cell,那么先点击播放记录位置

1.第一种cell播放:Layer是加载到cell上的背景图片区域的 滚动的时候要记录当前cell

2.第二种全屏播放:Layer是加载到Window上的 frame全屏

3.第三种小窗播放:它其实就是全屏播放的一个特例,也是加载到Window上的,frame自定义

其实不同状态的切换无非就是Layer所在View的位置不停切换

下面这个方法就是记录当前播放的cell下标

[objc] view plaincopy
  1. #pragma mark - 播放器播放
  2. - (void)startPlayVideo:(UIButton *)sender
  3. {
  4. // 获取当前的indexpath
  5. self.currentIndexPath = [NSIndexPath indexPathForRow:sender.tag inSection:0];
  6. // iOS 7 和 8 以上获取cell的方式不同
  7. if ([UIDevice currentDevice].systemVersion.floatValue>=8||[UIDevice currentDevice].systemVersion.floatValue<7) {
  8. self.currentCell = (ViedoTableViewCell *)sender.superview.superview;
  9. }else{//ios7系统 UITableViewCell上多了一个层级UITableViewCellScrollView
  10. self.currentCell = (ViedoTableViewCell *)sender.superview.superview.subviews;
  11. }
  12. ViedoModel *model = [self.viedoLists objectAtIndex:sender.tag];
  13. // 小窗口的时候点击播放另一个 先移除掉
  14. if (self.isSmallScreen) {
  15. [self releaseWMPlayer];
  16. self.isSmallScreen = NO;
  17. }
  18. // 当有上一个在播放的时候 点击 就先release
  19. if (self.wmPlayer) {
  20. [self releaseWMPlayer];
  21. self.wmPlayer = [[WMPlayer alloc]initWithFrame:self.currentCell.mainImageView.bounds];
  22. self.wmPlayer.delegate = self;
  23. self.wmPlayer.closeBtnStyle = CloseBtnStyleClose;
  24. self.wmPlayer.URLString = model.mp4URL;
  25. self.wmPlayer.titleLabel.text = model.title;
  26. //        [wmPlayer play];
  27. }else{
  28. // 当没有一个在播放的时候
  29. self.wmPlayer = [[WMPlayer alloc]initWithFrame:self.currentCell.mainImageView.bounds];
  30. self.wmPlayer.delegate = self;
  31. self.wmPlayer.closeBtnStyle = CloseBtnStyleClose;
  32. self.wmPlayer.titleLabel.text = model.title;
  33. self.wmPlayer.URLString = model.mp4URL;
  34. }
  35. // 把播放器加到当前cell的imageView上面
  36. [self.currentCell.mainImageView addSubview:self.wmPlayer];
  37. [self.currentCell.mainImageView bringSubviewToFront:self.wmPlayer];
  38. [self.currentCell.playButton.superview sendSubviewToBack:self.currentCell.playButton];
  39. [self.tableView reloadData];
  40. }

分析2:上下滚动的时候根据坐标切换cell显示还是小窗显示

[objc] view plaincopy
  1. #pragma mark scrollView delegate
  2. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  3. {
  4. if(scrollView ==self.tableView){
  5. if (self.wmPlayer==nil) {
  6. return;
  7. }
  8. if (self.wmPlayer.superview) {
  9. // 当前cell在tableView中的frame
  10. //            (lldb) po rectInTableView
  11. //            (origin = (x = 0, y = 0), size = (width = 375, height = 300))
  12. CGRect rectInTableView = [self.tableView rectForRowAtIndexPath:self.currentIndexPath];
  13. // 把当前的frame从tableView转换到屏幕View上面去
  14. //            (lldb) po rectInSuperview
  15. //            (origin = (x = 0, y = 61), size = (width = 375, height = 300))
  16. CGRect rectInSuperview = [self.tableView convertRect:rectInTableView toView:[self.tableView superview]];
  17. NSLog(@"Y轴变化:%lf,currentCell:%lf",rectInSuperview.origin.y,self.currentCell.mainImageView.frame.size.height);
  18. // 当网上移出屏幕的时候或者往下移出屏幕的时候,根据逻辑是否加载到小窗上来
  19. if (rectInSuperview.origin.y<-self.currentCell.mainImageView.frame.size.height ||rectInSuperview.origin.y>kScreenHeight-kNavbarHeight-kTabBarHeight) {//往上拖动
  20. // 如果已经小屏幕显示了,就不做任何操作
  21. if ([[UIApplication sharedApplication].keyWindow.subviews containsObject:self.wmPlayer]&&self.isSmallScreen) {
  22. self.isSmallScreen = YES;
  23. }else{
  24. //放widow上,小屏显示 这里的逻辑和展示到全屏是一样的道理,只是位置和frame自己定义就好了,想放哪就放哪
  25. [self toSmallScreen];
  26. }
  27. }else{
  28. // 如果已经在cell里面了,那么就不做任何操作
  29. if ([self.currentCell.mainImageView.subviews containsObject:self.wmPlayer]) {
  30. }else{
  31. // 如果进入屏幕,而且未在cell上,那么动画回currentCell
  32. [self toCell];
  33. }
  34. }
  35. }
  36. }
  37. }
[objc] view plaincopy
  1. // 滚动的时候小屏幕,放window上显示
  2. -(void)toSmallScreen{
  3. //放widow上
  4. [self.wmPlayer removeFromSuperview];
  5. __weak typeof(self)weakSelf = self;
  6. [UIView animateWithDuration:0.5f animations:^{
  7. weakSelf.wmPlayer.transform = CGAffineTransformIdentity;
  8. // 设置window上的位置
  9. weakSelf.wmPlayer.frame = CGRectMake(kScreenWidth/2,kScreenHeight-kTabBarHeight + 40 -(kScreenWidth/2)*0.75, kScreenWidth/2, (kScreenWidth/2)*0.75);
  10. weakSelf.wmPlayer.playerLayer.frame =  weakSelf.wmPlayer.bounds;
  11. // 下面就是更新布局的代码,此处省略了,需要的去下载Demo看看

分析3:用MJRefresh做个JD的加载动画(随便做的,大家随便感受下)



[objc] view plaincopy
  1. MKJRefreshHeader * header = [MKJRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(refreshData)];
  2. header.stateLabel.hidden = YES;
  3. header.lastUpdatedTimeLabel.hidden = YES;
  4. header.mj_h = 80;
  5. self.tableView.mj_header = header;

    这是JD的加载动画View以及重写的MJHeader文件


这里简单的写个重写的方法示例,具体需要看的大家去下载Demo

[objc] view plaincopy
  1. - (void)setState:(MJRefreshState)state
  2. {
  3. MJRefreshCheckState
  4. // 根据状态做事情
  5. // 刷新完毕
  6. if (state == MJRefreshStateIdle) {
  7. if (oldState == MJRefreshStateRefreshing) {
  8. self.arrowView.transform = CGAffineTransformIdentity;
  9. [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
  10. self.loadingView1.alpha = 0.0;
  11. } completion:^(BOOL finished) {
  12. // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态
  13. if (self.state != MJRefreshStateIdle) return;
  14. self.loadingView1.alpha = 1.0;
  15. [self.loadingView1 endRefresing];
  16. self.arrowView.hidden = NO;
  17. }];
  18. } else { // 拉倒即将刷新的时候,又往回缩,不进行刷新
  19. [self.loadingView1 endRefresingDown];
  20. self.arrowView.hidden = NO;
  21. [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
  22. self.arrowView.transform = CGAffineTransformIdentity;
  23. }];
  24. }
  25. } else if (state == MJRefreshStatePulling) { // 继续往下拉的时候
  26. [self.loadingView1 refreing];
  27. NSLog(@"连接点");
  28. self.arrowView.hidden = NO;
  29. [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
  30. self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI);
  31. }];
  32. } else if (state == MJRefreshStateRefreshing) { // 刷新
  33. self.loadingView1.alpha = 1.0; // 防止refreshing -> idle的动画完毕动作没有被执行
  34. [self.loadingView1 refreing];
  35. self.arrowView.hidden = YES;
  36. }
  37. }

尼玛啊,一口气写了那么多,语文水平还没及格的我真的感觉身体被掏空了


篇幅有点多了,感觉没必要什么都写出来,需要的同学去研究下Demo吧,感谢看到这

里的小伙伴,你们都是好人,好人一生平安啊,要不再点个赞???!!!




简单Demo示例地址:点击打开简单Demo链接


类似网易视频播放最终Demo地址:点击打开网易Demo链接

iOS开发之AVPlayer的精彩使用---网易新闻视频播放界面的另类实现相关推荐

  1. iOS开发之AVKit框架使用

    2019独角兽企业重金招聘Python工程师标准>>> iOS开发之AVKit框架使用 一.引言 在iOS开发框架中,AVKit是一个非常上层,偏应用的框架,它是基于AVFounda ...

  2. (0045) iOS 开发之MBProgressHUD 源码学习

    (0045) iOS 开发之MBProgressHUD 源码学习 第一部分:学习所得和分析线程 1.  学习到了kvo 的使用 和屏幕方向的旋转判断. 2. 如果调起这个 HUD 的方法不是在主线程调 ...

  3. (0016)iOS 开发之Mac上Navicat Premium 创建远程连接和本地连接

    1.下载安装 (百度云盘里面有安装文件和注册机) 链接: https://pan.baidu.com/s/1kVG1k71 密码: mr5g 破解教程看这篇博客:http://blog.csdn.ne ...

  4. 李洪强iOS开发之RunLoop的原理和核心机制

    李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研 ...

  5. IOS开发之MD5加密和钥匙串的使用-oc

    IOS开发之MD5加密和钥匙串的使用-oc 源码在我的主页,md5加密是用户登录安全的一个保障.不可逆的,可以暴力破解的. // // ViewController.m // MD5演练 // // ...

  6. IOS开发之CALayer基本属性和使用

    IOS开发之CALayer基本属性和使用 // // ViewController.m // CALayer // // Created by 鲁军 on 2021/2/21. //#import & ...

  7. ios开发之plist 的文件的读写以及沙盒容器路径打印

    ios开发之plist 的文件的读写以及沙盒容器路径打印 核心代码在这里 // // ViewController.m // 21-plist存储和沙盒路径 // // Created by 鲁军 o ...

  8. IOS开发之JSON文件的读写

    IOS开发之JSON文件的读写 // // ViewController.m // 20-JSON的读写 // // Created by 鲁军 on 2021/2/13. //#import &qu ...

  9. IOS开发之JSON序列化从客户端发送到服务器端

    IOS开发之JSON序列化从客户端发送到服务端的准备工作 共有6种情况 需要序列化 请查看源代码. 服务器端接受我们采用的是java的Tomcat服务器.配合 struts 2 controller框 ...

最新文章

  1. 程序设计基本概念(2)-2.19
  2. Hadoop:HDFS NameNode内存全景
  3. Spring Boot配置文件放在jar外部
  4. No Module Named '_pywrap_tensorflow_internal'
  5. 【移动通信】多址技术和调制技术
  6. 一个完整的canvas画图
  7. Linux脚本:xjps查看各个节点java进程
  8. 华为服务器操作系统密码,服务器操作系统密码忘记
  9. linux使用jinja2模板_SaltStack配置管理工具jinja2模板的使用
  10. 休眠事实:了解刷新操作顺序很重要
  11. 轨迹跟踪主要方法_带你入门多目标跟踪(一)领域概述
  12. Hadoop之内存问题
  13. 自动驾驶_视觉定位_高德公开课
  14. Google翻译api接入及Java、Python实现
  15. 机械制图之平面与圆柱体表面的交线
  16. 如何用计算机做函数,office2010中如何利用公式或函数进行计算
  17. 订餐系统(饿了某)java程序实现
  18. Jetson TX1(视频)、TK1(音视频)
  19. 学习网络安全一头雾水,想找些学习资料都不知道哪里入手?
  20. DVWA靶场——下载与安装(全)

热门文章

  1. 人工智能与深度神经网络,人工智能深度神经网络
  2. 【黄啊码】百万级别订单量,如何生成唯一订单ID(雪花算法)
  3. 微信公众号服务器端脑图,微信公众号中隐藏的思维导图工具,帮你随时随地高效思考...
  4. XMU2018摸底测试 星际战争I(Easy)
  5. 软件测试面试题含答案
  6. Game Plug-ins(1)
  7. 几何光学学习笔记(7)- 3.1 理想光学系统
  8. u深度制作linux启动盘制作工具,u深度u盘启动盘制作工具 v3.1.15.316
  9. 8.3列表/菜单/文本域标记
  10. linux 编辑文件乱码,Linux中vi编辑器显示中文乱码的问题