【iOS】知乎日报

文章目录

  • 前言
  • 一、首页
    • 1.网络的异步请求
    • 2.避免同一网络请求执行多次
    • 3.下拉刷新与上拉加载的实现
      • 下拉刷新
      • 上拉加载
  • 二、网页
    • 1.webView的实现
    • 2.webView的滑动加载
    • 3.网页与首页内容的同步更新
  • 三、评论区
    • Masonory实现行高自适应
  • 四、收藏中心
    • 通过FMDB实现数据持久化
      • 1.创建或打开数据库
      • 2.数据库操作
        • 建表操作
        • 增加数据
        • 删除数据
        • 查询数据
        • 修改数据库字段
  • 总结


前言

近期耗时一个月完成了第一个项目知乎日报,用到了AFNetworkingJSONModelMasonryFMDB等一系列第三方库以及其他新学的知识点,整体采用了MVC框架进行编写,特此撰写博客总结

一、首页

实现效果:
在这里插入图片描述
我们通过我们的视图层级图展开我们首页功能的分析:
在这里插入图片描述

1.网络的异步请求

我们从上述层级示意图以及动画效果展示,我们可以看出这个页面主要是将轮播图Tableviewcell共同添加到一个TableView上实现的页面。

在这里我们用到了知乎日报提供的开源接口实现我们对图片以及数据的请求——知乎日报 API 分析(如何规范api设计)

在这里笔者是使用AFNetworking对API进行请求的。详见【iOS】使用单例封装通过AFNetworking实现的网络请求

在刚开始请求数据来构建页面时,笔者请求到的数据总是为空,因此View中总是无法显示请求的图片以及数据,或是请求到了数据,但并没有在View中显示出来,细分析其原因,是因为网络请求是一个异步且耗时的过程。笔者给出一个例子:

- (void)getCurrentModel {[[Manager shareManager] NetWorkGetWithRecentData:^(CurrentModel *model) {self.modelDictionary = [model toDictionary];for (int i = 0; i < 5; i++) {[self->_topURLArray addObject:self.modelDictionary[@"top_stories"][i][@"url"]];[self->_topIDArray addObject:self.modelDictionary[@"top_stories"][i][@"id"]];[self.topPageTitleArray addObject:self.modelDictionary[@"top_stories"][i][@"title"]];[self.topPageImageURLArray addObject:self.modelDictionary[@"top_stories"][i][@"image"]];}[self.topArray addObject:[model toDictionary]];[self.allArray addObject:[model toDictionary]];// 异步执行任务创建方法dispatch_async(dispatch_get_main_queue(), ^{[self.homeView.allArray addObject:[model toDictionary]];});} andError:^(NSError * _Nullable error) {NSLog(@"失败");}];NSLog(@"%@", _topArray);NSLog(@"%@", _allArray);[self initHomeView];}

在这段代码中我们先用[self initHomeView]对我们的视图进行初始化,再去请求我们的数据对视图进行赋值,结果就会出现这样的页面
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可以看到我们打印的结果为空,并且View中的控件没有被赋值,这就是涉及到了异步请求的知识。简单的来说就是当我们使用NetWorkGetWithRecentData:方法时,我们的网络请求被放在了后台线程进行执行,但这并不会阻塞我们的主线程,我们的程序的主线程会继续执行。

也就是说即使我们的网络请求还没有请求成功,我们的程序也会继续向下执行,这就导致了我们的View的空白

使用AFNetworking等网络请求库时,它们通常会处理底层的多线程操作,将网络请求放在后台线程中执行,以确保不会阻塞主线程。这是为了避免在主线程中执行网络请求导致的界面卡顿和不流畅的用户体验。

现在我们利用代码来验证一下这个结论:

- (void)getCurrentModel {NSLog(@"111");[[Manager shareManager] NetWorkGetWithRecentData:^(CurrentModel *model) {self.modelDictionary = [model toDictionary];for (int i = 0; i < 5; i++) {[self->_topURLArray addObject:self.modelDictionary[@"top_stories"][i][@"url"]];[self->_topIDArray addObject:self.modelDictionary[@"top_stories"][i][@"id"]];[self.topPageTitleArray addObject:self.modelDictionary[@"top_stories"][i][@"title"]];[self.topPageImageURLArray addObject:self.modelDictionary[@"top_stories"][i][@"image"]];}[self.topArray addObject:[model toDictionary]];[self.allArray addObject:[model toDictionary]];// 异步执行任务创建方法dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"222");[self initHomeView];[self.homeView.allArray addObject:[model toDictionary]];});} andError:^(NSError * _Nullable error) {NSLog(@"失败");}];NSLog(@"333");}

假如我们这段代码都在一个线程中执行,那么1,2,3会相继输出

但是结果并非如此
在这里插入图片描述
在网络请求中的NSLog(@"222");是最后才输出的,这也证明了我们的网络请求是一个异步的过程。


说完了问题,我们该如何解决这个问题呢,这里就需要使用到我们的GCD—— dispatch_async(dispatch_get_main_queue(), ^{ });

使用这个方法,能在其他线程中调用我们的主线程,也就是当后台线程的进程执行完之后,返回我们的主线程进行任务执行
这就保证了当我们的网络请求请求完之后我们才会对我们的UI进行布局,防止了UI出现空白的情况。

关于GCD的知识笔者还不甚了解,后续学到会加以补充,现在只懂简单的使用与其基本原理


2.避免同一网络请求执行多次

在进行下拉加载数据时,笔者使用了- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;协议来监听tableView.contentOffset以此来判断是否需要获取新数据。
但是网络请求是一个耗时的过程,假如我们连续滑动页面,就会出现同一网络请求被执行多次的情况,我们的程序会因为线程阻塞而崩溃。为了避免这种情况,我们就需要对网络请求进行锁定。
笔者在代码中使用了一个BOOL变量对网络请求进行锁定,当监听到需要加载数据的时候,我们的BOOL变量就为YES,请求成功数据后再变为NO,在BOOL变量为YES时无法进行相同的网络请求,这就避免了同一网络请求执行多次。

- (void)getPastModel:(NSString *)date {if (self.isLoadingMoreData) {return;}self.isLoadingMoreData = YES; // 设置加载标志pastData++;numbersOfLoad++;date = [self.homeModel pastDateForJson:numbersOfLoad];[[Manager shareManager] NetWorkGetWithPastData:^(PastModel * _Nonnull model) {self.pastModelDictionary = [model toDictionary];
//        [self.homeView.pastArray addObject:self.pastModelDictionary];[self.homeView.pastTimeArray addObject:[self.homeModel pastDate:numbersOfLoad]];[self.homeView.allArray addObject:[model toDictionary]];[self.allArray addObject:[model toDictionary]];NSLog(@"%@", self.allArray);dispatch_async(dispatch_get_main_queue(), ^{[self.homeView hideLoadMoreView];[self.homeView.tableView reloadData];self.isLoadingMoreData = NO; // 重置加载标志});} andError:^(NSError * _Nullable error) {NSLog(@"失败: %@", error); // 添加日志} andDate:date];}

3.下拉刷新与上拉加载的实现

通过首页的动画演示,我们可以看到在笔者上拉与下拉时都有对应的菊花控件显示,在这里笔者分别用了两种方法去使用菊花控件在这里插入图片描述

下拉刷新

笔者使用了MJRefresh第三方库进行菊花控件的创建,使用第三方库的创建十分简单,只有一行代码 self.homeView.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)];

随后我们使用loadNewData方法模拟加载效果:

- (void)loadNewData {// 模拟网络请求加载新数据dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 加载完成后结束下拉刷新[self.homeView.tableView.mj_header endRefreshing];// 刷新 UITableView 的数据
//        [self.homeView.tableView reloadData];});
}

上拉加载

对于上拉加载我们同样也可以使用MJRefresh,但是笔者在这里使用了UIKit自带的控件UIActivityIndicatorView

首先在上拉时创建并展现菊花控件

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {// 用户结束拖拽手势if (self.tableView.contentOffset.y + self.tableView.frame.size.height >= self.tableView.contentSize.height) {
//        self.backgroundColor = [UIColor whiteColor];[self showLoadMoreView];[[NSNotificationCenter defaultCenter] postNotificationName:@"reNew" object:nil userInfo:nil];
//        NSLog(@"%d", ([self.viewModelDictionary[@"stories"] count] + 1) * pastData);
//        [_tableView reloadData];}
}- (void)showLoadMoreView {// 创建和配置加载动画视图self.activityIndicator.frame = CGRectMake(0, 0, 320, 33);self.activityIndicator.tag = 123; // 设置一个标记以便后续移除[self.activityIndicator startAnimating];// 将加载动画视图添加到UIScrollView的底部self.tableView.tableFooterView = self.activityIndicator;
}- (void)hideLoadMoreView {// 停止加载动画UIActivityIndicatorView *activityIndicator = [self.tableView.tableFooterView viewWithTag:123];[activityIndicator stopAnimating];// 隐藏加载动画视图self.tableView.tableFooterView = nil;
}

随后在网络请求完成后在主线程移除菊花控件的展现

- (void)getPastModel:(NSString *)date {if (self.isLoadingMoreData) {return;}self.isLoadingMoreData = YES; // 设置加载标志pastData++;numbersOfLoad++;date = [self.homeModel pastDateForJson:numbersOfLoad];[[Manager shareManager] NetWorkGetWithPastData:^(PastModel * _Nonnull model) {self.pastModelDictionary = [model toDictionary];
//        [self.homeView.pastArray addObject:self.pastModelDictionary];[self.homeView.pastTimeArray addObject:[self.homeModel pastDate:numbersOfLoad]];[self.homeView.allArray addObject:[model toDictionary]];[self.allArray addObject:[model toDictionary]];NSLog(@"%@", self.allArray);dispatch_async(dispatch_get_main_queue(), ^{[self.homeView hideLoadMoreView];[self.homeView.tableView reloadData];self.isLoadingMoreData = NO; // 重置加载标志});} andError:^(NSError * _Nullable error) {NSLog(@"失败: %@", error); // 添加日志} andDate:date];}

二、网页

网页效果实现
在这里插入图片描述

1.webView的实现

在我们的知乎日报API中,其给我们提供了webView的URL,我们可以通过使用WKWebView来加载我们的网页

根据MVC的原则,我们会在首页的Viewcontroller中获取我们的URL然后将其传到我们网页的Viewcontroller中,最后在View中加载我们的网页

步骤1:
导入#import <UIKit/UIKit.h>

步骤2:

 _webView = [[WKWebView alloc] init];NSURL* urlWeb = [NSURL URLWithString:_allArray[(_nowPage - 5) / 5][@"stories"][_nowPage % 5][@"url"]];NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];[_webView loadRequest:webRequest];

这样我们就完成了我们webView的加载


2.webView的滑动加载

同时通过动画我们可以看到每向后滑动一页,我们的webView才会进行加载,这就节约了我们的内存,减少了用户等待的时间。

笔者这里的思路是通过协议监听当前scrollerView的页数,当当前网页没有加载过便用通知传值通知View层进行Webview的加载

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {NSInteger currentPage = (scrollView.contentOffset.x / Width) + 0.5;_pageNumber = [NSNumber numberWithInteger:currentPage + 5];//滑动cell只加载当前webview,不加载多余webViewif (scrollView.tag == 77) {// 当滚动视图向右滚动且快接近画布右边缘时,触发加载数据的操作if (scrollView.contentOffset.x >  ([_allArray count] * 5 * Width  - Width) && self.isLoadingMoreData == NO) {self.isLoadingMoreData = YES;[self loadMoreData];} else {if (currentPage != (_webView.nowPage - 5) && isLoadingWebView == NO && self.isLoadingMoreData == NO && ![self.nowPageSet containsObject:_pageNumber]) {NSLog(@"%ld", (long)currentPage);isLoadingWebView = YES;_webView.nowPage = currentPage + 5;[self.nowPageSet addObject:_pageNumber];NSLog(@"%ld..", (long)_webView.nowPage);[[NSNotificationCenter defaultCenter] postNotificationName:@"newPage" object:nil userInfo:nil];}}}}- (void)addView {
//    NSLog(@"%@111",_allArray);if (_isCollectionWebView == YES) {_scrollView.tag = 55;_scrollView.contentOffset = CGPointMake(Width * _nowPage, 0);self.scrollView.contentSize = CGSizeMake(Width * [_webURLArray count], 0);NSLog(@"%@111", _webURLArray);for (int i = 0; i < [_webURLArray count]; i++) {_webView = [[WKWebView alloc] init];NSURL* urlWeb = [NSURL URLWithString:_webURLArray[i]];NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];[_webView loadRequest:webRequest];_webView.frame = CGRectMake(Width * i, 0, Width, Height * 0.92);[self.scrollView addSubview:_webView];}} else {if (_nowPage < 5) {_scrollView.tag = 66;_scrollView.contentOffset = CGPointMake(Width * _nowPage, 0);self.scrollView.contentSize = CGSizeMake(Width * 5, 0);for (int i = 0; i < 5; i++) {_webView = [[WKWebView alloc] init];NSURL* urlWeb = [NSURL URLWithString:_allArray[0][@"top_stories"][i][@"url"]];NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];[_webView loadRequest:webRequest];_webView.frame = CGRectMake(Width * i, 0, Width, Height * 0.92);[self.scrollView addSubview:_webView];}} else {_scrollView.tag = 77;self.scrollView.contentSize = CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);_scrollView.contentOffset = CGPointMake(Width * (_nowPage - 5), 0);_webView = [[WKWebView alloc] init];NSURL* urlWeb = [NSURL URLWithString:_allArray[(_nowPage - 5) / 5][@"stories"][_nowPage % 5][@"url"]];NSURLRequest* webRequest = [[NSURLRequest alloc] initWithURL:urlWeb];[_webView loadRequest:webRequest];_webView.frame = CGRectMake(Width * (_nowPage - 5), 0, Width, Height * 0.875);[self.scrollView addSubview:_webView];}}}

同时笔者为了实现webView的流畅滑动,在滑动到最后一页需要请求新数据时,笔者将 self.scrollView.contentSize 设为了CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);

[self.allArray count]在这里也会实时更新,请求到新数据之后新数据会添加到[self.allArray count]中

在这里+Width的意义就是当我们滑动到最后一页时,我们需要时间去等待数据的加载,如果没有多处一页空白,那么只有当数据加载完成后我们的scrollerView才会滑动到下一页。
+Width则实现了无需等待,我们可以直接滑动到下一页,数据加载完成后会自动呈现在我们的scrollerView

- (void)layoutNewScrollView {self.scrollView.contentSize = CGSizeMake(Width * [self.allArray count] * 5 + Width, 0);
}

3.网页与首页内容的同步更新

当我们在网页页面滑动获取新数据的同时,我们返回首页时数据也需要同步更新。

这里笔者的思路是通过设置全局变量与通知传值来告诉首页我们网页的数据已经更新,同时将网页的数组同步给首页的数组,从而实现数据的同步更新。

##网页
- (void)pressButtonReturn {if (_isCollectionWebView == NO) {NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];userInfo[@"key1"] = self.allArray;userInfo[@"key2"] = self.pastTimeArray;[self.navigationController popViewControllerAnimated:YES];[[NSNotificationCenter defaultCenter] postNotificationName:@"update" object:nil userInfo:userInfo];}else {[self.navigationController popViewControllerAnimated:YES];}
}##首页
- (void)update:(NSNotification *)send {NSArray *newDataArray = send.userInfo[@"key1"];NSArray *newPastTimeArray = send.userInfo[@"key2"];if (![self.homeView.allArray isEqualToArray:newDataArray] || ![self.homeView.pastTimeArray isEqualToArray:newPastTimeArray]) {self.homeView.allArray = [newDataArray mutableCopy];self.allArray = [newDataArray mutableCopy];self.homeView.pastTimeArray = [newPastTimeArray mutableCopy];[self.homeView.tableView reloadData];}//    self.homeView.allArray = send.userInfo[@"key1"];//    self.allArray = send.userInfo[@"key1"];//    self.homeView.pastTimeArray = send.userInfo[@"key2"];//原来的这段代码让self.homeView.allArray与self.allArray同时指向了send.userInfo[@"key1"],也就是说我之后在此对self.allArray进行修改self.homeView.allArray也会同步修改,反之亦然
}

对于如何获取全局变量可以参考以下博客:【iOS】浅析static,const,extern关键字

三、评论区

实现效果:
在这里插入图片描述

Masonory实现行高自适应

这个知识点笔者会专门再写一篇博客讲述,后面会将博客补上

四、收藏中心

实现效果:
在这里插入图片描述

通过FMDB实现数据持久化

在使用FMDB之前,我们简单了解一下什么是FMDB

FMDB 是 iOS 平台的 SQLite 数据库框架
FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API

更通俗的讲,FMDB就是iOS的数据库,它支持SQL语句来执行操作

核心类 FMDB有三个主要的类

FMDatabase

一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句。

FMResultSet

使用FMDatabase执行查询后的结果集

FMDatabaseQueue

用于在多线程中执行多个查询或更新,它是线程安全的

笔者在这里简单介绍一下FMDB的使用步骤:

1.创建或打开数据库

 - (void)createDataBase {// 获取数据库文件的路径NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];NSString *path = [docPath stringByAppendingPathComponent:@"DailyNews.sqlite"];FMDatabase *db = [FMDatabase databaseWithPath:path];if ([db open]) {NSLog(@"Open database Success");} else {NSLog(@"fail to open database");}
}

在这里我们的path会有三种情况:

  • 具体文件路径
    如果不存在会自动创建,(使用绝对路径),就像我们的此时的path为[docPath stringByAppendingPathComponent:@"DailyNews.sqlite"];
  • 空字符串 @""
    会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除。
  • nil
    会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁

2.数据库操作

在FMDB中,除查询以外的所有操作,都称为“更新”

  • create
  • drop
  • insert
  • update
  • delete
建表操作

语法:create table table_name (field1 type1, field2 type2, field3 type3...);

在SQLite3中可以不指定字段的数据类型,SQLite3会自动推断类型。常用的格式有:
文本: Text
整形: Integer
二进制数据: Blob
浮点型: Real Float、Double
布尔型: Boolean
时间型: Time
日期型: Date
时间戳: TimeStamp

代码示例:

if ([db open]) {BOOL result = [db executeUpdate:@"CREATE TABLE 't_DailyNews' ('webPageID' TEXT, 'webPageTitle' TEXT, 'webPageImageURL' TEXT, 'webURL' TEXT)"];if (result) {NSLog(@"创表成功");}NSLog(@"Open database Success");} else {NSLog(@"fail to open database");}
增加数据

语法:INSERT INTO table_name (field1, field2...) VALUES (var1, var2...);

代码示例:

- (void)insertDataBase {if ([_dataBase open]) {NSString *insertSql = @"insert into 't_DailyNews'(webPageID,webPageTitle,webPageImageURL, webURL) values(?,?,?,?)";BOOL result = [_dataBase executeUpdate:insertSql, _webPageID, _webPageTitle, _webPageImageURL, _webURL];if (result) {NSLog(@"添加数据成功");} else {NSLog(@"添加数据失败");}}
}
删除数据

语法:NSString *deleteSQL = @"DELETE FROM your_table WHERE condition = ?";
删除所有符合条件的数据,不设置条件时清空所有数据。

代码示例:

- (void)deleteDataBase {NSString *sql = @"delete from t_DailyNews where (webPageID) = (?) and (webPageTitle) = (?) and (webURL) = (?)";if ([_dataBase open]) {BOOL result = [_dataBase executeUpdate:sql, _webPageID, _webPageTitle, _webURL];if (result) {NSLog(@"删除数据成功");} else {NSLog(@"删除数据失败");}}
}
查询数据

语法:NSString *querySQL = @"SELECT * FROM your_table WHERE condition = ?";

代码示例:

- (void)traverseDataBase {[_dataBase open];NSString *selectSql = @"select * from t_DailyNews";FMResultSet *rs = [_dataBase executeQuery:selectSql];//FMResultSet专门用来进行查询操作while ([rs next]) {NSString *webPageID = [rs stringForColumn:@"webPageID"];NSString *webPageTitle = [rs stringForColumn:@"webPageTitle"];NSString *webURL = [rs stringForColumn:@"webURL"];if ([webPageID isEqualToString:_webPageID] && [webPageTitle isEqualToString:_webPageTitle] && [webURL isEqualToString:_webURL]) {_webView.buttonCollection.selected = YES;[_dataBase close];return;}}_webView.buttonCollection.selected = NO;[_dataBase close];return;
}
修改数据库字段

添加列:

ALTER TABLE your_table
ADD COLUMN new_column_name INTEGER;

删除列:

ALTER TABLE your_table
DROP COLUMN column_to_be_deleted;

重命名列:

ALTER TABLE your_table
RENAME COLUMN old_column_name TO new_column_name;

笔者这里仅给出添加列的示例:

- (void)xiugai {// 获取数据库文件的路径NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];NSString *path = [docPath stringByAppendingPathComponent:@"DailyNewsOfGood.sqlite"];FMDatabase *db = [FMDatabase databaseWithPath:path];// Assuming db is your FMDatabase instanceif ([db open]) {NSString *alterTableSQL = @"ALTER TABLE 't_DailyNewsOfGood' ADD COLUMN countsOfGood TEXT";BOOL success = [db executeUpdate:alterTableSQL];if (success) {NSLog(@"Table altered successfully");} else {NSLog(@"Error altering table: %@", [db lastErrorMessage]);}[db close];} else {NSLog(@"Error opening database: %@", [db lastErrorMessage]);}}

这里需要注意的是如果我们需要修改数据库中表的字段,不能在创表的代码上进行修改,因为此时数据库已经创建成功


讲完了数据库的基本操作,我们来讲讲我们的收藏效果是如何实现的

当我们点击我们的收藏按钮时,就将当前页面的webView与ID等一系列标志添加到数据库中,当我们打开收藏中心时我们的程序首先会读取数据库中的内容,从而将我们收藏的内容展现在我们UI上。

反之删除也是同理,我们取消收藏时就以当前页面的webView与ID等一系列标志与数据库中的数据进行匹配,如果匹配成功则成功删除数据库中对应的数据。从而实现了收藏的取消

同时我们在滑动我们的webView时还会通过遍历数据库实时对当前webView是否存在于我们的数据库中进行判断

- (void)judgeCollectionInOrOut {[_dataBase open];NSString *selectSql = @"select * from t_DailyNews";FMResultSet *rs = [_dataBase executeQuery:selectSql];//FMResultSet专门用来进行查询操作while ([rs next]) {NSString *webPageID = [rs stringForColumn:@"webPageID"];NSString *webPageTitle = [rs stringForColumn:@"webPageTitle"];NSString *webURL = [rs stringForColumn:@"webURL"];if ([webPageID isEqualToString:_webPageID] && [webPageTitle isEqualToString:_webPageTitle] && [webURL isEqualToString:_webURL]) {_webView.buttonCollection.selected = YES;[_dataBase close];return;}}_webView.buttonCollection.selected = NO;[_dataBase close];return;
}

总结

知乎日报耗时一个月,在此期间碰到了许多问题,例如网络请求的异步加载问题,Masonry实现行高自适应的控件约束问题,单元格内按钮的复用问题,tableview的优化问题以及使用MVC框架中各个层的职责问题,总结便是学习iOS之路任重道远,还需更加努力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/201822.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

内存可见性与指令重排序

文章目录 内存可见性内存可见性问题代码演示JMM&#xff08;Java Memory Model&#xff09; 指令重排序指令重排序问题代码演示指令重排序分析 volatile关键字volatile 保证内存可见性 & 禁止指令重排序volatile 不保证原子性 在上一节介绍线程安全问题的过程中&#xff0c…

分享一篇很就以前的文档-VMware Vsphere菜鸟篇

PS&#xff1a;由于内容是很久以前做的记录&#xff0c;在整理过程中发现了一些问题&#xff0c;简单修改后分享给大家。首先ESXI节点和win7均运行在VMware Workstation上面&#xff0c;属于是最底层&#xff0c;而新创建的CentOS则是嵌套后创建的操作系统&#xff0c;这点希望…

Dubbo从入门到上天系列第十八篇:Dubbo引入注册中心简介以及DubboAdmin简要介绍,为后续详解Dubbo各种注册中心做铺垫!

一&#xff1a;Dubbo注册中心引言 1&#xff1a;什么是Dubbo的注册中心&#xff1f; Dubbo注册中心是Dubbo服务治理中极其重要的一个概念。它主要是用于对Rpc集群应用实例进行管理。 对于我们的Dubbo服务来讲&#xff0c;至少有两部分构成&#xff0c;一部分是Provider一部分是…

看不惯AI版权作品被白嫖!Stability AI副总裁选择了辞职,曾领导开发Stable Audio

近日&#xff0c;OpenAI的各种大瓜真是让人吃麻了。 而就在Sam Altmam被开除前两天&#xff0c;可能没太多人注意到Stability AI副总裁Newton—Rex因看不惯StabilityAI在版权保护上的行为选择辞职一事。 大模型研究测试传送门 GPT-4传送门&#xff08;免墙&#xff0c;可直接…

基于VM虚拟机下Ubuntu18.04系统,Hadoop的安装与详细配置

参考博客&#xff1a; https://blog.csdn.net/duchenlong/article/details/114597944 与上面这个博客几乎差不多&#xff0c;就是java环境配置以及后面的hadoop的hdfs-site.xml文件有一些不同的地方。 准备工作 1.更新 # 更新 sudo apt update sudo apt upgrade2.关闭防火…

【C++ 设计模式】面向对象设计原则 Template Method 模式 Strategy 策略模式

一、面向对象设计原则 重新认识面向对象 理解隔离变化 • 从宏观层面来看&#xff0c;面向对象的构建方式更能适应软件的变化&#xff0c; 能将变化所带来的影响减为最小 各司其职 • 从微观层面来看&#xff0c;面向对象的方式更强调各个类的“责任” • 由于需求变化导…

NFC:应用场景广泛的短距离通信技术

NFC&#xff1a;应用场景广泛的短距离通信技术 一、NFC 技术介绍1.1 NFC 技术应用场景1.2 NFC 技术优点1.3 NFC 工作原理 二、NFC 开发2.1 NFC 应用开发流程2.2 NFC 读取和写入2.3 NFC 读写功能示例 三、总结 一、NFC 技术介绍 NFC &#xff08;Near-field communication&…

基于向量加权平均算法优化概率神经网络PNN的分类预测 - 附代码

基于向量加权平均算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于向量加权平均算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于向量加权平均优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xf…

SpringBoot——》配置logback日志文件

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

关于一些bug的解决1、el-input的输入无效2、搜索之后发现数据不对3、el多选框、单选框点击无用4、

el-input输入无效 原来的代码是 var test null 但是我发现不能输入任何值 反倒修改test的初始值为123是可以的 于是我确定绑定没问题 就是修改的问题 于是改成 var test ref&#xff08;&#xff09; v-model绑定的值改成test.value就可以了 因为ref是相应式的 可以通过输入…

有依次对应关系的数组X、Y、Z,如何排序其中一个X数组,使得另外的数组还与排序完成后的数组相对应(C语言实现)

1. 目的 有依次对应关系的数组X、Y、Z&#xff0c;排序其中一个X数组&#xff0c;使得另外的数组还与排序完成后的数组相对应&#xff0c;并打印出排序完成后的X、Y、Z数组。 2. 具体实现 以下面的这个对应关系为例&#xff0c;进行相应编程实现。 X [3.7,7.7,-6.6,1.5,-4.5…

深度学习环境配置(Anaconda+pytorch+pycharm+cuda)

NVIDIA驱动安装 首先查看电脑的显卡版本&#xff0c;步骤为&#xff1a;此电脑右击-->管理-->设备管理器-->显示适配器。就可以看到电脑显卡的版本了。 然后按照电脑信息&#xff0c;到地址 去安装相应的驱动&#xff0c;Notebooks是笔记本的意思&#xff0c;然后下…

[SIGGRAPH-23] 3D Gaussian Splatting for Real-Time Radiance Field Rendering

pdf | proj | code 本文提出一种新的3D数据表达形式3D Gaussians。每个Gaussian由以下参数组成&#xff1a;中心点位置、协方差矩阵、可见性、颜色。通过世界坐标系到相机坐标系&#xff0c;再到图像坐标系的仿射关系&#xff0c;可将3D Gaussian映射到相机坐标系&#xff0c;通…

【MySQL】多表查询、子查询、自连接、合并查询详解,包含大量示例,包你会。

复合查询 前言正式开始一些开胃菜多表查询自连接子查询单行子查询多行子查询in关键字all关键字any关键字多列子查询在from中使用子查询 合并查询union 和 union all 前言 我前面博客讲的所有的查询都是在单表中进行的&#xff0c;从这里开始就要专门针对查询这个话题进行进一步…

连接k8s和凌鲨

通过连接k8s和凌鲨&#xff0c;可以让研发过程中的重用操作更加方便。 更新容器镜像调整部署规模查看日志运行命令 架构 所有操作通过k8s proxy连接&#xff0c;通过设置namespace label赋予访问权限。只有赋予特定label的namespace才能被访问。 使用步骤 部署k8s proxy 你…

【机器学习】On the Identifiability of Nonlinear ICA: Sparsity and Beyond

前言 本文是对On the Identifiability of Nonlinear ICA: Sparsity and Beyond (NIPS 2022)中两个结构稀疏假设的总结。原文链接在Reference中。 什么是ICA(Independent component analysis)&#xff1f; 独立成分分析简单来说&#xff0c;就是给定很多的样本X&#xff0c;通…

电子学会C/C++编程等级考试2022年12月(一级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:加一 输入一个整数x,输出这个整数加1后的值,即x+1的值。 时间限制:1000 内存限制:65536输入 一个整数x(0 ≤ x ≤ 1000)。输出 按题目要求输出一个整数。样例输入 9样例输出 10 答案: //参考答案: #include<bits/st…

Linux中的进程程序替换

Linux中的进程程序替换 1. 替换原理2. 替换函数3. 函数解释4. 命名理解程序替换的意义 1. 替换原理 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的…

C++ DAY08 异常

概念 异常事件&#xff08;如&#xff1a;除 0 溢出&#xff0c;数组下标越界&#xff0c;所要读取的文件不存在 , 空指针&#xff0c;内存不足 等等&#xff09; 在 C 语言对错误的处理是两种方法&#xff1a; 一是使用整型的返回值标识错误&#xff1b; 二是使用 errn…

含分布式电源的配电网可靠性评估(matlab代码)

1主要内容 该程序参考《基于仿射最小路法的含分布式电源配电网可靠性分析》文献方法&#xff0c;通过概率模型和时序模型分别进行建模&#xff0c;实现基于概率模型最小路法的含分布式电源配电网可靠性评估以及时序模型序贯蒙特卡洛模拟法的含分布式电源配电网可靠性评估。程序…