1、UI相关
1.1、UICollectionReusableView
1.2、willMoveToSuperView
willMoveToSuperView:这个方法是在视图即将加入或者移除某个window时调用。如果 newWindow为空就表示移除,也就是view即将被销毁。类似于UIViewController的viewWillDisappear方法。
1.3、estimatedRowHeight
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = 200;
1.4、将控件坐标转换到window坐标
-(CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
-(CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
-(CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
-(CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;
//例子
CGRect rect = [tableViewCell converRect:cellBtn.frame toView:nil];
1.5、插入、删除、移动tableView的cell
-(void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
-(void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
-(void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
//例子
[self.table deleteRowsAtIndexPaths:@[[self.table indexPathForCell:tableViewCell]] withRowAnimation:UITableViewRowAnimationAutomatic];
1.6、UIView动画
//delay:延迟; Damping:阻尼; Velocity:回弹; options:动画模式;
[UIView animateWithDuration:2.f delay:1.f usingSpringWithDamping:0.5 initialSpringVelocity:0.5 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.view.frame = CGRectMake(0, 0, 200, 100);
} completion:^(BOOL finished) {
NSLog(@"动画完成",);
}];
1.7、scrollView
scrollView滚动分页效果scrollView.pagingEnabled = YES;- inset自动适配
//默认添加刘海保护,设置 Never 关闭 collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
1.8、KVO可以移除Crash风险、可使用复杂的方法名和参数
- (void)viewDidLoad {
[super viewDidLoad];
//监听self.view的name属性,变化新值时触发下边业务逻辑
[self.view addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
//业务逻辑
}
//释放时要注销监测
- (void)dealloc {
[self.view removeObserver:self forKeyPath:@"name"];
}
- 三方KVO库:
KVOController
1.9、图片
1.9.1、图片展示方式
+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable UIImage *)imageWithData:(NSData *)data;
+ (nullable UIImage *)imageWithData:(NSData *)data scale:(CGFloat)scale API_AVAILABLE(ios(6.0));
+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage;
1.9.2、展示一组图片称为动图
UIImageView *imageView = [UIImageView new];
imageView.animationImages = @[[UIImage imageNamed:@"image1.png"],[UIImage imageNamed:@"image2.png"]];
imageView.animationDuration = 1;
[imageView startAnimating];
1.10、WKWebView
//加载网页方式
- (void)webViewloadURLType{
switch (self.loadType) {
case loadWebURLString:{
//创建一个NSURLRequest 的对象
NSMutableURLRequest * Request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.URLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[Request addValue:[UserInfo shareInstance].token forHTTPHeaderField:@"token"];
//加载网页
[self.wkWebView loadRequest:Request];
break;
}
case loadWebHTMLString:{
[self loadHostPathURL:self.URLString];
break;
}
case POSTWebURLString:{
// JS发送POST的Flag,为真的时候会调用JS的POST方法
self.needLoadJSPOST = YES;
//POST使用预先加载本地JS方法的html实现,请确认WKJSPOST存在
// [self loadHostPathURL:@"WKJSPOST"];
NSString * jscript = [NSString stringWithFormat:@"%@my_post(\"%@\", %@)",POST_JS,self.URLString,self.postData];
// 调用JS代码
[self.wkWebView evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) {
NSLog(@"%@",error);
}];
break;
}
}
}
//设置网页的配置文件
WKWebViewConfiguration * Configuration = [[WKWebViewConfiguration alloc]init];
//允许视频播放
Configuration.allowsAirPlayForMediaPlayback = YES;
// 允许在线播放
Configuration.allowsInlineMediaPlayback = YES;
// 允许可以与网页交互,选择视图
Configuration.selectionGranularity = YES;
// web内容处理池
Configuration.processPool = [[WKProcessPool alloc] init];
//自定义配置,一般用于 js调用oc方法(OC拦截URL中的数据做自定义操作)
WKUserContentController * UserContentController = [[WKUserContentController alloc]init];
// 添加消息处理,注意:self指代的对象需要遵守WKScriptMessageHandler协议,结束时需要移除
[UserContentController addScriptMessageHandler:self name:@"WXPay"];
// 是否支持记忆读取
Configuration.suppressesIncrementalRendering = YES;
// 允许用户更改网页的设置
Configuration.userContentController = UserContentController;
_wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:Configuration];
_wkWebView.backgroundColor = [UIColor colorWithRed:240.0/255 green:240.0/255 blue:240.0/255 alpha:1.0];
// 设置代理
_wkWebView.navigationDelegate = self;
_wkWebView.UIDelegate = self;
//kvo 添加进度监控
[_wkWebView addObserver:self forKeyPath:NSStringFromSelector( @selector(estimatedProgress)) options:0 context:WkwebBrowserContext];
//开启手势触摸
_wkWebView.allowsBackForwardNavigationGestures = YES;
// 设置 可以前进 和 后退
//适应你设定的尺寸
[_wkWebView sizeToFit];
1.11、UIGestureRecognizerDelegate
//手势不同阶段、通过Delegate的方式扩展在手势识别过程中的自定义操作
//处理是否响应手势、是否支持多手势、多个手势冲突如何处理
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible, // the recognizer has not yet recognized its gesture, but may be evaluating touch events. this is the default state
UIGestureRecognizerStateBegan, // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop
UIGestureRecognizerStateChanged, // the recognizer has received touches recognized as a change to the gesture. the action method will be called at the next turn of the run loop
UIGestureRecognizerStateEnded, // the recognizer has received touches recognized as the end of the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
UIGestureRecognizerStateCancelled, // the recognizer has received touches resulting in the cancellation of the gesture. the action method will be called at the next turn of the run loop. the recognizer will be reset to UIGestureRecognizerStatePossible
UIGestureRecognizerStateFailed, // the recognizer has received a touch sequence that can not be recognized as the gesture. the action method will not be called and the recognizer will be reset to UIGestureRecognizerStatePossible
// Discrete Gestures – gesture recognizers that recognize a discrete event but do not report changes (for example, a tap) do not transition through the Began and Changed states and can not fail or be cancelled
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
};
1.12、CALayer(CA:Core Animation)
- 每个UIView都有一个CALayer负责内容的绘制与动画
- UIKit其他组件对应的展示Layer
- CAScrollLayer
- CATextLayer
- 和UIView相同的结构(subLayer)
1.12.1、CALayer动画
-
KeyPath可用的key:#define angle2Radian(angle) ((angle)/180.0*M_PI) transform.rotation.x 围绕x轴翻转 参数:角度 angle2Radian(4) transform.rotation.y 围绕y轴翻转 参数:同上 transform.rotation.z 围绕z轴翻转 参数:同上 transform.rotation 默认围绕z轴 transform.scale.x x方向缩放 参数:缩放比例 1.5 transform.scale.y y方向缩放 参数:同上 transform.scale.z z方向缩放 参数:同上 transform.scale 所有方向缩放 参数:同上 transform.translation.x x方向移动 参数:x轴上的坐标 100 transform.translation.y x方向移动 参数:y轴上的坐标 transform.translation.z x方向移动 参数:z轴上的坐标 transform.translation 移动 参数:移动到的点 (100,100) opacity 透明度 参数:透明度 0.5 backgroundColor 背景颜色 参数:颜色 (id)[[UIColor redColor] CGColor] cornerRadius 圆角 参数:圆角半径 5 borderWidth 边框宽度 参数:边框宽度 5 bounds 大小 参数:CGRect contents 内容 参数:CGImage contentsRect 可视内容 参数:CGRect 值是0~1之间的小数 hidden 是否隐藏 position shadowColor shadowOffset shadowOpacity shadowRadius -
CABasicAnimationCAAnimationGroup *group = [CAAnimationGroup animation]; // 平移 CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"]; anim.toValue = [NSValue valueWithCGPoint:CGPointMake(arc4random_uniform(200), arc4random_uniform(500))]; anim.beginTime = 0.5; // 缩放 CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; // 0 ~ 1 static CGFloat scale = 0.1; scale = scale < 1 ? 1.5 : 0.2; anim1.toValue = @(scale); anim.beginTime = 1; // 旋转 CABasicAnimation *anim2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; anim2.toValue = @(arc4random_uniform(360) / 180.0 * M_PI); anim2.beginTime = 1.5; //添加动画组 group.animations = @[anim,anim1,anim2]; group.duration = 2; // 告诉在动画结束的时候不要移除 group.removedOnCompletion = NO; // 始终保持最新的效果 group.fillMode = kCAFillModeForwards; [_backView.layer addAnimation:group forKey:nil]; } -
CAKeyframeAnimation-
keyTimes:可以为对应的关键帧指定对应的时间点,其
取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的 -
values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
-
path:可以设置一个
CGPathRef\CGMutablePathRef,让层跟着路径移动;path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"position"; //绘制路径 CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(150, 100, 100, 100)); animation.path = path; CGPathRelease(path); //参数设置 animation.repeatCount = MAXFLOAT; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; animation.duration = 4.0f; //增加动画阻尼感 animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; animation.delegate = self; [_backView.layer addAnimation:animation forKey:nil]; -
说明:CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
-
-
CABasicAnimation 只能从一个数值(fromValue)变到另一个数值(toValue),而 CAKeyframeAnimation 会使用一个NSArray保存这些数值
1.12.2、CAEmitterLayer:粒子发射器
//设置layer的frame
CAEmitterLayer *emitter = [[CAEmitterLayer alloc] init];
CGRect frame = CGRectMake(0, 100, self.view.frame.size.width, 50);
emitter.frame = frame;
emitter.emitterSize = frame.size;
emitter.emitterShape = kCAEmitterLayerRectangle;
emitter.emitterPosition = CGPointMake(frame.size.width/2, frame.size.height/2);
CAEmitterCell *emitterCell = [[CAEmitterCell alloc] init];
emitterCell.contents = ( __bridge id)[UIImage imageNamed:@"zhanghu.png"].CGImage;
emitterCell.birthRate = 20; //每秒创建20个雪花
emitterCell.lifetime = 3.5; //在屏幕上保持3.5秒
emitter.emitterCells = @[emitterCell];
emitterCell.yAcceleration = 70.0;
emitterCell.xAcceleration = 10.0;
emitterCell.velocity = 20.0; //速度
emitterCell.emissionLongitude = (CGFloat)(M_PI_2);
emitterCell.velocityRange = 200.0;
emitterCell.emissionRange = (CGFloat)M_PI_2;
emitterCell.color = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0].CGColor; //设置全部都是一种颜色
emitterCell.scale = 0.8;
emitterCell.scaleRange = 0.8;
emitterCell.scaleSpeed = -0.15;
emitterCell.alphaRange = 0.75;
emitterCell.alphaSpeed = -0.15;
emitterCell.emissionLongitude = (CGFloat)(-M_PI);
emitterCell.lifetimeRange = 1.0; //2.5-5
[_backView.layer addSublayer:emitter];
- 动画实现框架:Lottie / FaceBook POP 粒子发射器实现
1.13、代码规范
XCFormat:Xcode Extensions中下载,系统偏好设置中使用
1.14、键盘监听
- 键盘的监听Key,常用2种
示例
- (void)observeKeyboard {
//监听键盘改变
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(_doTextViewAnimationWithNotification:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
//监听到键盘改变结束位置完成动画
- (void)_doTextViewAnimationWithNotification:(NSNotification *)noti{
CGFloat duration = [[noti.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
CGRect keyboardFrame = [[noti.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
if (keyboardFrame.origin.y >= SCREEN_HEIGHT) {
//收起
[UIView animateWithDuration:duration animations:^{
self.textView.frame = CGRectMake(0, self.backgroundView.bounds.size.height, SCREEN_WIDTH, UI(100));
}];
}else{
self.textView.frame = CGRectMake(0, self.backgroundView.bounds.size.height, SCREEN_WIDTH, UI(100));
[UIView animateWithDuration:duration animations:^{
self.textView.frame = CGRectMake(0, self.backgroundView.bounds.size.height - keyboardFrame.size.height - UI(100), SCREEN_WIDTH, UI(100));
}];
}
}
监听到的参数
2、网络
2.1、基本网络流程
NSURL->NSURLRequest(增加请求参数和设置) ->NSURLSession(返回数据)
2.1.1、NSURL
-
方法
//网络资源 + (nullable instancetype)URLWithString:(NSString *)URLString relativeToURL:(nullable NSURL *)baseURL; //本地资源:cache/url ---> file:///cache/url + (NSURL *)fileURLWithPath:(NSString *)path isDirectory:(BOOL) isDir relativeToURL:(nullable NSURL *)baseURL -
NSURL可包装参数
/* Returns the data representation of the URL's relativeString. If the URL was initialized with -initWithData:relativeToURL:, the data representation returned are the same bytes as those used at initialization; otherwise, the data representation returned are the bytes of the relativeString encoded with NSUTF8StringEncoding. */ @property (readonly, copy) NSData *dataRepresentation API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)); @property (nullable, readonly, copy) NSString *absoluteString; @property (readonly, copy) NSString *relativeString; // The relative portion of a URL. If baseURL is nil, or if the receiver is itself absolute, this is the same as absoluteString @property (nullable, readonly, copy) NSURL *baseURL; // may be nil. @property (nullable, readonly, copy) NSURL *absoluteURL; // if the receiver is itself absolute, this will return self. /* Any URL is composed of these two basic pieces. The full URL would be the concatenation of [myURL scheme], ':', [myURL resourceSpecifier] */ @property (nullable, readonly, copy) NSString *scheme; @property (nullable, readonly, copy) NSString *resourceSpecifier; /* If the URL conforms to rfc 1808 (the most common form of URL), the following accessors will return the various components; otherwise they return nil. The litmus test for conformance is as recommended in RFC 1808 - whether the first two characters of resourceSpecifier is @"//". In all cases, they return the component's value after resolving the receiver against its base URL. */ @property (nullable, readonly, copy) NSString *host; @property (nullable, readonly, copy) NSNumber *port; @property (nullable, readonly, copy) NSString *user; @property (nullable, readonly, copy) NSString *password; @property (nullable, readonly, copy) NSString *path; @property (nullable, readonly, copy) NSString *fragment; @property (nullable, readonly, copy) NSString *parameterString API_DEPRECATED("The parameterString method is deprecated. Post deprecation for applications linked with or after the macOS 10.15, and for all iOS, watchOS, and tvOS applications, parameterString will always return nil, and the path method will return the complete path including the semicolon separator and params component if the URL string contains them.", macosx(10.2,10.15), ios(2.0,13.0), watchos(2.0,6.0), tvos(9.0,13.0)); @property (nullable, readonly, copy) NSString *query; @property (nullable, readonly, copy) NSString *relativePath; // The same as path if baseURL is nil
2.1.2、NSURLRequest
- NSURLRequest 对象代表一个请求,包含 NSURL对象、请求方法、请求头、请求体等
//请求方法&POST请求 @property (nullable, readonly, copy) NSString *HTTPMethod; @property (nullable, readonly, copy) NSData *HTTPBody; //header @property (nullable, readonly, copy) NSDictionary<NSString *, NSString *> *allHTTPHeaderFields; - (void)addValue:(NSString *)value forHTTPHeaderField:(NSString *)field; - (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
2.1.3、NSURLSession
-
负责接收、发送和处理请求
-
一个 Session 可以创建多个请求
-
APP可以创建不同配置的多个Session
-
把 Request封装为
Task,控制状态、开始取消、进度 -
NSURLSessionConfiguration会话配置//配置会话 NSURLSessionConfiguration * configuration = NSURLSessionConfiguration.defaultSessionConfiguration;- timeout时间
- cookie缓存策略
- 最大并发数、是否支持蜂窝
- 其他网络行为的行为与策略
- 根据Configuration不同,提供默认、后台、自定义
-
创建 NSURLSession 会话
//单例创建会话 NSURLSession *session = [NSURLSession sharedSession]; //创建会话 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; operationQueue.maxConcurrentOperationCount = 1; NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:operationQueue];
2.1.4、NSURLSessionTask
- 通过 NSURLSession 的方法创建 Task
//NSURLSessionDataTask - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler; - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler; //NSURLSessionDownloadTask - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler; - (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler; - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- 创建好的 SessionTask 是
suspend状态的,调用resume开始执行- (void)resume; //恢复 - (void)suspend; //暂停 - (void)cancel; //取消
2.1.5、Response
总结
-
iOS9新增ATS(App Transport Security)特性,默认要求都是HTTPS进行请求,Info.plist中需要添加字段增加对HTTP的支持
-
系统 NSURLSession 加载数据流程
- 创建 & 使用默认 Session
- 通过地址和参数创建 Task
- 开始 & 取消 Task
- 在 Handler 中处理数据
-
简单实现
NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionTask * dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { //返回数据 }]; [dataTask resume];
2.2、管理工具
2.3、解析数据
- 数据交换格式
- JSON
- protobuf:内容不具备可读性,但是性能高,是个二进制流
2.3.1、NSJSONSerialization
- 提供JSON数据和系统对象间的转换
//二进制转id类型 //NSJSONReadingOptions:是否是可变类型数据 & 外层数据格式 + (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error; //Json转二进制 //NSJSONWritingOptions:缩进 & 单行 + (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
2.3.2、YYModel
//Convert json to Model:
User *user = [User yy_modelWithJSON:json];
//Convert model to json:
NSDictionary *json = [user yy_modelToJSONObject];
2.3.3、返回数据
- 请求后返回的数据要保持在主线程
//返回数据 id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error]; NSMutableArray *dataArray = @[].mutableCopy; for (NSDictionary *info in (NSArray *)jsonObj) { LZModel * model = [LZModel modelWithDictionary:info]; [dataArray addObject:model]; } dispatch_async(dispatch_get_main_queue(), ^{ if (finishBlock) { finishBlock(dataArray); } }); //只声明LZModel,不引入LZModel.h @class LZModel
3、数据存储
3.1、沙盒
-
Bundles:plist和二进制文件等
-
Datas:APP访问的文件
Document:可以进行备份和恢复,体积较大,设置后可以共享(可在iTunes中看到),一般存用户数据(比如音乐、视频)Library:开发者最常使用,可自定义子文件夹- Cache:不需要缓存的,体积较大,可以通过清理缓存被清除掉的
- Preference:用户偏好设置,NSUserDefault,支持备份
- 自定义文件夹:较常用
- tmp:临时文件不会备份,启动时可能被清除
-
获取沙盒地址
- NSPathUtilities.h
NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde); //获取 NSArray * pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);- 上文的
NSSearchPathDirectory,常用NSCachesDirectory、NSLibraryDirectory、NSDocumentDirectorytypedef NS_ENUM(NSUInteger, NSSearchPathDirectory) { NSApplicationDirectory = 1, // supported applications (Applications) NSDemoApplicationDirectory, // unsupported applications, demonstration versions (Demos) NSDeveloperApplicationDirectory, // developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory. NSAdminApplicationDirectory, // system and network administration applications (Administration) NSLibraryDirectory, // various documentation, support, and configuration files, resources (Library) NSDeveloperDirectory, // developer resources (Developer) DEPRECATED - there is no one single Developer directory. NSUserDirectory, // user home directories (Users) NSDocumentationDirectory, // documentation (Documentation) NSDocumentDirectory, // documents (Documents) NSCoreServiceDirectory, // location of CoreServices directory (System/Library/CoreServices) NSAutosavedInformationDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 11, // location of autosaved documents (Documents/Autosaved) NSDesktopDirectory = 12, // location of user's desktop NSCachesDirectory = 13, // location of discardable cache files (Library/Caches) NSApplicationSupportDirectory = 14, // location of application support files (plug-ins, etc) (Library/Application Support) NSDownloadsDirectory API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 15, // location of the user's "Downloads" directory ...... }; NSSearchPathDomainMask,常用NSUserDomainMask//上文的 NSSearchPathDomainMask typedef NS_OPTIONS(NSUInteger, NSSearchPathDomainMask) { NSUserDomainMask = 1, // user's home directory --- place to install user's personal items (~) NSLocalDomainMask = 2, // local to the current machine --- place to install items available to everyone on this machine (/Library) NSNetworkDomainMask = 4, // publically available location in the local area network --- place to install items available on the network (/Network) NSSystemDomainMask = 8, // provided by Apple, unmodifiable (/System) NSAllDomainsMask = 0x0ffff // all domains: all of the above and future items };
3.2、iOS文件管理
3.2.1、NSFileManager
- 单例,提供APP内文件 & 文件夹的管理功能
- 创建、删除、查询、移动、复制文件
- 读取文件内容 & 属性
- 通过 NSURL 或者 NSString 作为 Path
- NSFileManagerDelegate:提供移动、复制、删除等操作的具体自定义实现
//简单的增删查(未使用 NSFileManagerDelegate )
//调取 Library 下的 Cache 文件夹路径
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = [pathArray firstObject];
//NSFileManager单例
NSFileManager *fileManager = [NSFileManager defaultManager];
//创建文件夹
NSString *dataPath = [cachePath stringByAppendingPathComponent:@"LZCache"];
NSError *createError;
[fileManager createDirectoryAtPath:cachePath withIntermediateDirectories:YES attributes:nil error:&createError];
//创建一个list文件,里边存入一串字符串
NSString *listDataPath = [dataPath stringByAppendingPathComponent:@"list"];
NSData *listData = [@"存入的字符串abc" dataUsingEncoding:NSUTF8StringEncoding];
[fileManager createFileAtPath:listDataPath contents:listData attributes:nil];
//查询文件
BOOL fileExist = [fileManager fileExistsAtPath:listDataPath];
//删除文件
if (fileExist) {
NSError *removeError;
[fileManager removeItemAtPath:listDataPath error:&removeError];
}
3.2.2、NSFileHandle
对 NSFileManager 功能的细化补充
- 读取 & 写文件
- 读取指定的长度 & 在指定位置追加/截断
- 截断 & 立即刷新
- 常用于追加数据
//创建
+ (nullable instancetype)fileHandleForReadingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForWritingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForUpdatingAtPath:(NSString *)path;
//读 & 写
- (NSData *)readDataOfLength:(NSUInteger)length
- (void)seekToFileOffset:(unsigned long long)offset
- (void)writeData:(NSData *)data
//接着上边 FileManager 代码,使用 NSFileHandle 对生成的文件进行精细操作
NSFileHandle *fileHandler = [NSFileHandle fileHandleForUpdatingAtPath:listDataPath];
//操作位置找到末尾
[fileHandler seekToEndOfFile];
//追加字符串
[fileHandler writeData:[@"def" dataUsingEncoding:NSUTF8StringEncoding]];
//立即刷新文件
[fileHandler synchronizeFile];
//关闭文件
[fileHandler closeFile];
3.3、NSCoder
- 归档(序列化)& 解归档(反序列化)
- 提供简单的函数,在 二进制数据 和 Object 间进行转换
- 抽象类,具体功能需要子类实现(现在使用
NSSecureCoding替代原有NSCoding)
//归档
- (void)encodeObject:(nullable id)object forKey:(NSString *)key;
- (void)encodeConditionalObject:(nullable id)object forKey:(NSString *)key;
- (void)encodeBool:(BOOL)value forKey:(NSString *)key;
- (void)encodeInt:(int)value forKey:(NSString *)key;
- (void)encodeInt32:(int32_t)value forKey:(NSString *)key;
- (void)encodeInt64:(int64_t)value forKey:(NSString *)key;
- (void)encodeFloat:(float)value forKey:(NSString *)key;
- (void)encodeDouble:(double)value forKey:(NSString *)key;
- (void)encodeBytes:(nullable const uint8_t *)bytes length:(NSUInteger)length forKey:(NSString *)key;
//解归档
- (BOOL)containsValueForKey:(NSString *)key;
- (nullable id)decodeObjectForKey:(NSString *)key;
- (nullable id)decodeTopLevelObjectForKey:(NSString *)key error:(NSError **)error API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)) NS_SWIFT_UNAVAILABLE("Use 'decodeObject(of:, forKey:)' instead");
- (BOOL)decodeBoolForKey:(NSString *)key;
- (int)decodeIntForKey:(NSString *)key;
- (int32_t)decodeInt32ForKey:(NSString *)key;
- (int64_t)decodeInt64ForKey:(NSString *)key;
- (float)decodeFloatForKey:(NSString *)key;
- (double)decodeDoubleForKey:(NSString *)key;
- (nullable const uint8_t *)decodeBytesForKey:(NSString *)key returnedLength:(nullable NSUInteger *)lengthp;
3.3.1、NSKeyedArchiver
- NSCoder的子类
- 提供简单的函数,在 二进制数据 和 Object 间进行转换
- 提供基本的 Delegate
实例演示
@interface LZModel : NSObject<NSSecureCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic,assign) NSInteger age;
@end
@implementation LZModel
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:@"name"];
[coder encodeInteger:self.age forKey:@"age"];
}
@implementation LZModelLoader
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
self.name = [coder decodeObjectForKey:@"name"];
self.age = [coder decodeIntegerForKey:@"age"];
}
return self;
}
+ (BOOL)supportsSecureCoding
return YES;
}
@end
- (void)loadDataSuceesed:(void(^)(NSArray *result))finishBlock {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"www.baidu.com"] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:3];
NSURLSession *session = [NSURLSession sharedSession];
__weak typeof(self)WeakSelf = self;
NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
__strong typeof(WeakSelf)StrongSelf = WeakSelf;
//返回数据
id jsonObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
NSArray *dataArray = [(NSDictionary *)jsonObj objectForKey:@"result"];
NSMutableArray *listModelArray = @[].mutableCopy;
for (NSDictionary *info in dataArray) {
LZModel *model = [LZModel modelWithDictionary:info];
[listModelArray addObject:model];
}
[WeakSelf archiveListDataWithArray:listModelArray];
dispatch_async(dispatch_get_main_queue(), ^{
if (finishBlock) {
finishBlock(listModelArray.copy);
}
});
}];
[dataTask resume];
}
- (void)archiveListDataWithArray:(NSArray <LZModel *>*)array {
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = [pathArray firstObject];
NSFileManager *fileManager = [NSFileManager defaultManager];
//创建文件夹
NSString *dataPath = [cachePath stringByAppendingPathComponent:@"LZData"];
NSError *creatError;
[fileManager createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:&creatError];
//创建文件
NSString *listDataPath = [dataPath stringByAppendingPathComponent:@"list"];
//将数据转化为二进制文件(需要被转化文件实现 NSSecureCoding 中归档、解归档)
NSError *archiveError;
NSData *listData = [NSKeyedArchiver archivedDataWithRootObject:array requiringSecureCoding:YES error:&archiveError];
//将二进制文件存入创建的文件中
[fileManager createFileAtPath:listDataPath contents:listData attributes:nil];
}
- (void)unarchiveListWithFilePath:(NSString *)filePath {
NSFileManager *fileManager = [NSFileManager defaultManager];
//将文件内容读取成二进制文件
NSData *readListData = [fileManager contentsAtPath:filePath];
//将二进制文件解归档(NSSet 中添加解归档内容所有包含的类型)
NSError *unarchiveError;
id unarchiveObj = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSArray class],[LZModel class], nil] fromData:readListData error:&unarchiveError];
NSLog(@"%@",(NSArray <LZModel *>*)unarchiveObj);
}
3.4、NSUserDefault
- 文件存储在
Library/Preferences中,属于系统数据库 - 通常存储 轻量级、基本数据类型,复杂的 Model 要转化成 NSData
- 与 NSCoder 存储在文件中属于同一纬度,只是一个存在自定义文件夹的文件中,另一个存在系统数据库中,所以获取二进制文件后的解归档操作与 NSCoder 没有区别
//将 NSUserDefaults 内容读取成二进制文件 NSData *readListData = [[NSUserDefaults standardUserDefaults] dataForKey:@"listData"]; //将二进制文件解归档(NSSet 中添加解归档内容所有包含的类型) NSError *unarchiveError; id unarchiveObj = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObjects:[NSArray class],[LZModel class], nil] fromData:readListData error:&unarchiveError]; NSLog(@"%@",(NSArray <LZModel *>*)unarchiveObj);
3.5、keychain
- 待补充
3.6、FMDB
- 待补充
总结
4、线程
4.1、NSThread
pthread的封装- 创建新的线程
- 执行之后自动销毁
- 每次都要创建、不好管理也消耗资源
- 手动进行线程同步
- 手动管理线程生命周期
NSThread *downloadImageThead = [[NSThread alloc] initWithBlock:^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:info.picUrl]]];
}];
//设置线程名称
downloadImageThead.name = @"downloadImageThread";
[downloadImageThead start];
4.2、GCD
- 高性能、自动利用CPU
- 线程池模式,自动分配/调度线程,管理线程的生命周期
将线程封装成队列
4.2.1、创建线程队列
//线程优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
//主线程
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//非主线程,可设置优先级
dispatch_queue_t unMainQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//自定义线程,自定义线程名与串行、并发方式(线程名字符串不带 @ )
dispatch_queue_t ownQueue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_CONCURRENT);
4.2.2、同步、异步、延迟派发
//同步
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
//异步
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
//延迟
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
4.2.3、其他方法
dispatch_once //只运行一次,用于创建单例
dispatch_source //事件源,自定义的触发和监听
dispatch_group //管理一组GCD Block
dispatch_semaphore //信号量,用于线程间同步
dispatch_barrier_async //并发队列中的同步点
示例
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//非主线程加载图片数据
dispatch_async(downloadQueue, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:info.picUrl]]];
//主线程UI操作
dispatch_async(mainQueue, ^{
self.imgView.image = image;
});
});
4.3、NSOperation
- GCD 缺乏可定制性与复杂的依赖
- NSOperation 是对 GCD 更面对对象的封装
- 提供任务间的依赖关系
支持取消暂停任务,优先级的设置- 子类继承实现,符合业务逻辑的定制
队列支持最大并发数设置
NSOperationQueue:相当于 GCD中的队列NSOperation:相当于 GCD Block中的任务
//创建线程队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创建线程任务
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(@"线程任务")
}];
//添加 operation
[queue addOperation:operation];
//取消全部 operation
[queue cancelAllOperations];
总结
- 每个线程对应一个 Runloop ,GCD & NSOperation 中会添加到 Queue 中
5、SDWebImage
5.1、实现逻辑
- 在 Cache 中查找图片缓存 --> 找不到时到 Disk 中查找图片缓存
- 再查找不到时,
网络异步并发下载图片、优先级重试 - 对下载的图片进行裁剪、解码
- 将处理后的图片回存到 Disk 与 Cache 中
- 替换设置的占位图给 UIImageView
- 这套逻辑也是常用的下载处理逻辑,并配合
LRU最近淘汰算法对缓存中的内容进行优化
6、音视频
6.1、框架内容
AVKit- AVPlayerViewController
- 需要创建AVPlayer,不需要创建Layer
- 提供基本的UI,如开始、暂停、全屏、进度条
- 设置播放器展示大小、全屏、画中画
- AVPlayerViewControllerDelegate:主要是画中画相关代理方法
- 创建所有视图层服务,用户控制、导航等
- 较高级的接口
- AVPlayerViewController
AVFoundationAVAsset:对资源进行操作 /AVPlayer:播放操作 /AVPlayerLayer:播放器画面,不需要用户交互单独使用Layer- 基于时间的音视频播放框架
- 数据和播放状态管理,较少的UI
- MPMoviePlayerController(过时,被AVKit替代)
6.2、CMTime
- 播放进度回调会使用
- duration / currentTime
- 视频具体秒数:
通过比如 CMTimeMake(24,12) 中的24/12=2的方式获得 CMTimeMake(第几帧,帧率)typedef struct { //帧数 CMTimeValue value; /*!< The value of the CMTime. value/timescale = seconds */ //帧率 CMTimeScale timescale; /*!< The timescale of the CMTime. value/timescale = seconds. */ CMTimeFlags flags; /*!< The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */ CMTimeEpoch epoch; } CMTime
AVFoundation 视频示例
#import "LZVideoPlayer.h"
#import <AVFoundation/AVFoundation.h>
@interface LZVideoPlayer ()
@property (nonatomic,strong,readwrite) AVPlayerItem *videoItem; //播放资源
@property (nonatomic,strong,readwrite) AVPlayer *avPlayer; //播放控制器
@property (nonatomic,strong,readwrite) AVPlayerLayer *playerLayer; //播放图层
@end
@implementation LZVideoPlayer
//单例播放器,保证只有一个视频在播
+ (LZVideoPlayer *)sharePlayer {
static LZVideoPlayer *player;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
player = [[LZVideoPlayer alloc] init];
});
retur player;
}
//播放视频
- (void)playVideoWithUrl:(NSString *)videoUrl attachView:(UIView *)attachView {
//停止原有播放的视频
[self _stopPlay];
//获取视频链接内容,可对资源进行定制处理(Model)
NSURL *videoURL = [NSURL URLWithString:videoUrl];
AVAsset *asset = [AVAsset assetWithURL:videoURL];
_videoItem = [AVPlayerItem playerItemWithAsset:asset];
//监听资源的状态 Status 和 播放进度
[_videoItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
[_videoItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
//获取视频时长,并将CMTime类型时长转换成秒数
CMTime duration = _videoItem.duration;
CGFloat videoDuration = CMTimeGetSeconds(duration);
//AVPlayer(Controller)
_avPlayer = [AVPlayer playerWithPlayerItem:_videoItem];
//播放器播放回调(此处CMTimeMake(1, 1)表示设置成每1秒回调一次)
[_avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
NSLog(@"播放进度: %@",@(CMTimeGetSeconds(time)));
}];
//播放器画面(View)
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_avPlayer];
_playerLayer.frame = attachView.frame;
//添加到控件图层上
[attachView.layer addSublayer:_playerLayer];
//监听视频播放完毕
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(_handlePlayEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}
- (void)_stopPlay {
//移除监听
[_videoItem removeObserver:self forKeyPath:@"status"];
[_videoItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:AVPlayerItemDidPlayToEndTimeNotification];
//移除视图等其他资源
_videoItem = nil;
_avPlayer = nil;
[_playerLayer removeFromSuperlayer];
}
//监听视频播放完成后的自定义方法
- (void)_handlePlayEnd {
//跳到视频第1秒再播放
[_avPlayer seekToTime:CMTimeMake(0, 1)];
[_avPlayer play];
}
//监听 AVPlayerItem加载的资源 的状态和缓冲情况
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"status"]) {
//状态为 ReadyToPlay 时开始播放
if (((NSNumber *)[change objectForKey:NSKeyValueChangeNewKey]).integerValue == AVPlayerItemStatusReadyToPlay) {
[_avPlayer play];
} else {
//其他状态时
}
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSLog(@"缓冲: %@",[change objectForKey:NSKeyValueChangeNewKey]);
}
}
AVPlayerViewController 视频示例
//待补充
开源音视频框架
- 编解码层⾯的封装
- 编码格式 / 封装格式的兼容(hls / rtmp:直播使用的格式)
- 硬解码 & 软解码
ijkPlayer:B站框架
- 其它业务逻辑
- 码流中插⼊⾃定义数据 (直播答题)
- 直播推流
- 预加载(p2p / localServer)
- 图像处理(直播时的瘦腿、美⽩)
- 图像识别(抖⾳)
7、界面布局
7.1、AutoLayout
-
NSLayoutConstraint
[NSLayoutConstraint activateConstraints:@[ [NSLayoutConstraint constraintWithItem:_avatorImageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1 constant:0], [NSLayoutConstraint constraintWithItem:_avatorImageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1 constant:15], ]]; -
VFL
//创建 VFL 语句 NSString *vflString = @"H:|-15-[_avatorImageView]-0-[_nickLabel]-(>=0)-[_commentImageView(==_avatorImageView)]-0-[_commentLabel]-15-[_likeImageView(==_avatorImageView)]-0-[_likeLabel]-15-[_shareImageView(==_avatorImageView)]-0-[_shareLabel]-15-|"; //实现到控件上 [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:vflString options:NSLayoutFormatAlignAllCenterY metrics:nil views:NSDictionaryOfVariableBindings(_avatorImageView, _nickLabel, _commentImageView, _commentLabel, _likeImageView, _likeLabel, _shareImageView, _shareLabel)]];
7.2、iOS适配
- 不同设备屏幕参数
- 创建合适的
适配器adapter
7.2.1、UIScreen & UIDevice
- 获取设备的逻辑尺⼨ - UIScreen
- 基于硬件显示的相关属性
[UIScreen mainScreen]- 主要提供 size / 亮度 / 坐标系 等
- 获取设备的信息 - UIDevice
- 操作系统 / 设备Model
[UIDevice currentDevice]- 设备⽅向 / 电量等
//设备方向 typedef NS_ENUM(NSInteger, UIDeviceOrientation) UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // Device oriented vertically, home button on the bottom UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top UIDeviceOrientationLandscapeLeft, // Device oriented horizontally, home button on the right UIDeviceOrientationLandscapeRight, // Device oriented horizontally, home button on the left UIDeviceOrientationFaceUp, // Device oriented flat, face up UIDeviceOrientationFaceDown // Device oriented flat, face down } API_UNAVAILABLE(tvos);
7.2.2、图片适配
-
存在 Bundle 中
-
存在
ImageAsset中(不需要写后缀,App瘦身)
7.2.3、iPhone X适配
-
Status Bar与Home Indicator -
iPhone X的安全区域
适配宏定义
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
//获取是否是横屏
#define IS_LANDSCAPE (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]))
//横竖屏下的屏幕宽高
#define SCREEN_WIDTH (IS_LANDSCAPE ? [[UIScreen mainScreen ] bounds].size.height : [[UIScreen mainScreen ] bounds].size.width)
#define SCREEN_HEIGHT (IS_LANDSCAPE ? [[UIScreen mainScreen ] bounds].size.width : [[UIScreen mainScreen ] bounds].size.height)
//分别判断 iPhone X、iPhone XR、iPhone XMax机型 (通过判断屏幕宽高与像素缩放比例)
#define IS_IPHONE_X (SCREEN_WIDTH == [LZScreen sizeFor58Inch].width && SCREEN_HEIGHT == [LZScreen sizeFor58Inch].height)
#define IS_IPHONE_XR (SCREEN_WIDTH == [LZScreen sizeFor61Inch].width && SCREEN_HEIGHT == [LZScreen sizeFor61Inch].height && [UIScreen mainScreen].scale == 2)
#define IS_IPHONE_XMAX (SCREEN_WIDTH == [LZScreen sizeFor65Inch].width && SCREEN_HEIGHT == [LZScreen sizeFor65Inch].height && [UIScreen mainScreen].scale == 3)
//定义 X、XR、XMAX 为刘海屏类型
#define IS_IPHONE_X_XR_MAX (IS_IPHONE_X || IS_IPHONE_XR || IS_IPHONE_XMAX)
//设置刘海屏的 Status Bar
#define STATUSBARHEIGHT (IS_IPHONE_X_XR_MAX ? 44 : 20)
//适配器 (数值、Rect)
#define UI(x) UIAdapter(x)
#define UIRect(x,y,width,height) UIRectAdapter(x,y,width,height)
//内联函数计算不同屏幕下缩放传入的数值x (414为以iPhone8P为基准时的宽度)
static inline NSInteger UIAdapter (float x){
//1 - 分机型 特定的比例
//2 - 屏幕宽度按比例适配
CGFloat scale = 414 / SCREEN_WIDTH;
return (NSInteger)x /scale;
}
//不同屏幕下缩放传入的 Rect
static inline CGRect UIRectAdapter(x,y,width,height){
return CGRectMake(UIAdapter(x), UIAdapter(y), UIAdapter(width), UIAdapter(height));
}
//...完善其他方法..size..origin..
@interface LZScreen : NSObject
//iPhoneX
+ (CGSize)sizeFor58Inch;
//iPhone XR
+ (CGSize)sizeFor61Inch;
//iPhone XS Max
+ (CGSize)sizeFor65Inch;
//...其它机型
@end
NS_ASSUME_NONNULL_END
//LZScreen.m
#import "LZScreen.h"
@implementation LZScreen
//iPhoneX
+ (CGSize)sizeFor58Inch{
return CGSizeMake(375,812);
}
//iPhone XR
+ (CGSize)sizeFor61Inch{
return CGSizeMake(414,896);
}
//iPhone XS Max
+ (CGSize)sizeFor65Inch{
return CGSizeMake(414,896);
}
@end
适配总结
- 位置⼤⼩⽂字适配
- iPhoneX 系列特殊的 UI 和交互
- 资源适配
- 使⽤ @2x @3x 图⽚ / ⽹络数据处理
- 使⽤合适的管理图⽚⽅式(Bundle / Asset)
8、APP启动
- main函数前
- 动态链接 / ⼆进制⽂件加载 / runtime / 类的加载等
- main 函数
8.1、UIApplication & UIApplicationDelegate
- 处理 App ⽣命周期 / 系统内存警告
- 处理UI / statusbar / 图标消息数等状态的变化/ ⽅向
- 处理 openURL
-
因为是系统的,所以提供 Delegate / Notifification 两种⽅式处理业务逻辑
-
根据 App状态 调整业务逻辑
- Not running
- Inactive:过渡的中间状态(调入多任务窗口)
- Active:正在前台运⾏,系统分配更多资源
- Background :分配较少资源
- Suspended: 内存不⾜系统⾃动 kill
-
UIApplicationDelegate 方法
8.2、闪屏Splash Screen(开屏启动页、广告)
- 方法一:直接在当前 Window 上 addSubview
- 方法二:创建新的 Window 成为 KeyWindow
- 调整 window 的 level
- 多 Window 的管理
9、APP跳转
- 使⽤
URL Scheme⽀持 App 启动、跳转及参数传递
9.1、URL Scheme
- 可以分享 / 登录 / 拉起App Store等
- 表示的是网址中
://前的内容- weixin://
- test://
- www.baidu.com 中的 https://
- 比如在Safari中输入 weixin:// 就等调起微信
9.1.1、使APP支持Scheme唤醒
-
设置
URL Types- 设置运行后在Safari中即可通过输入 LZSampleApp:// 调起我们的APP
- 设置运行后在Safari中即可通过输入 LZSampleApp:// 调起我们的APP
-
带数据调起APP
- iOS13后默认使用
UIScene,主要用于管理多个 UIWindow 的应用,那么相对应的很多职责就从 appdelegate 转移到了sceneDelegate中,这其中就包括 openURL 的职责(可以从中获取输入的网址信息)//需要调用 sceneDelegate 中的 openURLContexts方法 - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts{} - 删掉 UIScene 的会常规方式调用 openURL 方法,并可获取APP调起来源信息
//url:调起APP时的网址; options:字典,从中可以获取调起的来源 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options return YES; }- 删除
SceneDelegate代理文件 (可选) - 删除 Info.plist 里面的
Application Scene Manifest配置(一定要删除) - 删除 AppDelegate 代理的两个方法:(这两个方法一定要删除,否则使用纯代码创建的Window和导航控制器UINavigationController不会生效)
- application:configurationForConnectingSceneSession:options:
- application: didDiscardSceneSessions:
- 删除
- iOS13后默认使用
9.1.2、通过Scheme唤醒其他APP
- 检测是否安装APP
- (BOOL)canOpenURL:(NSURL *)url API_AVAILABLE(ios(3.0));-
即使安装了APP也会返回NO,因为未在
LSApplicationQueriesSchemes ⽩名单中添加,所以监测不到,只会走苹果默认的打开H5或者跳转App Store等逻辑,不利于业务定制 -
添加 LSApplicationQueriesSchemes 白名单
-
再检测安装即可成功
-
9.2、Universal Link
-
使⽤ HTTP / HTTPS 协议启动客户端
-
避免Scheme重复 / Web 和 Native 统⼀
-
需要配合Web端进⾏注册:将带有访问配置信息的
apple-app-site-association文件上传到服务器,苹果调用 Universal Link 时会根据这个文件的配置路径拉起APP -
apple-app-site-association文件的上传必须是HTTPS服务器
10、组件化
10.1、Target - Action
- 特点:
- 抽离业务逻辑
- 通过中间层
Mediator进⾏调⽤(硬编码) - Mediator 中间层使⽤ runtime 反射,减少对其他类的依赖
- Mediator 内容过多会导致臃肿,因此可以
将业务写到扩展中
- 缺点:
- 反射的方法数较少只有两个
- 反射的方法数较少只有两个
示例
//创建中间层 Mediator
@interface LZMediator : NSObject
//target action
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;
@end
@implementation LZMediator
+ (__kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl{
//runtime 获取类
Class detailCls = NSClassFromString(@"LZDetailViewController");
//使用 runtime 中的 performSelector 查找方法,detailUrl 也可根据业务改为字典等其他类型
UIViewController *controller = [[detailCls alloc] performSelector:NSSelectorFromString(@"initWithUrlString:") withObject:detailUrl];
return controller;
}
//具体使用
__kindof UIViewController *detailController = [LZMediator detailViewControllerWithUrl:model.articleUrl];
[self.navigationController pushViewController:detailController animated:YES];
10.2、URL Scheme
- 特点:
- 使 URL 处理本地的跳转
- 通过中间层进⾏注册 & 调⽤
- 注册表⽆需使⽤反射
- ⾮懒加载 / 注册表的维护 / 参数
- 缺点:
- 传递参数通过 Dictionary,其中的具体内容只能查看注册表才能知道
- 说是 Scheme,其实只是借鉴了其思想,Scheme值 本质是注册表中的 key,是可以随便定义的
示例
//搭建 Scheme注册表
@interface LZMediator : NSObject
//Url Scheme
typedef void(^LZMediatorProcessBlock)(NSDictionary *params);
+ (void)registerScheme:(NSString *)scheme processBlock:(LZMediatorProcessBlock)processBlock;
+ (void)openSchemeUrl:(NSString *)url params:(NSDictionary *)params;
@end
@implementation LZMediator
//创建一个 Scheme 的跳转表
+ (NSMutableDictionary *)mediatorCache{
static NSMutableDictionary *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = @{}.mutableCopy;
});
return cache;
}
//将组件的 scheme 和 Block方法 都注册到跳转表中
+ (void)registerScheme:(NSString *)scheme processBlock:(LZMediatorProcessBlock)processBlock{
if (scheme && processBlock) {
[[[self class] mediatorCache] setObject:processBlock forKey:scheme];
}
}
//调用者根据传入的 Scheme 取出注册到表中的 组件的Block方法,配合传入的 params 参数实现业务
+ (void)openSchemeUrl:(NSString *)url params:(NSDictionary *)params{
//一般以 scheme 为key, url 后的拼接拿来做业务逻辑判断
LZMediatorProcessBlock block = [[[self class] mediatorCache] objectForKey:url];
if (block) {
block(params);
}
}
@end
@implementation LZDetailViewController
//main 函数之前的方法,将被调用的组件设置 Scheme 并注册到 Mediator中间层 中
+ (void)load {
[LZMediator registerScheme:@"detail://" processBlock:^(NSDictionary * _Nonnull params) {
//根据传入的 param 实现业务逻辑
NSString *url = (NSString *)[params objectForKey:@"url"];
UINavigationController *navigationController = (UINavigationController *)[params objectForKey:@"controller"];
LZDetailViewController *controller = [[LZDetailViewController alloc] initWithUrlString:url];
[navigationController pushViewController:controller animated:YES];
}];
}
@end
//通过 Scheme 仅传递参数给注册的组件,具体实现由组件完成
[LZMediator openSchemeUrl:@"detail://" params:@{@"url":model.articleUrl,@"controller":self.navigationController}];
10.3、Protocol - Class
- 特点:
- 增加 Protocol Wrapper层
- 中间件返回 Protocol 对应的 Class
- 解决硬编码的问题
示例
@protocol LZDetailViewControllerProtocol <NSObject>
//对外协议方法
- ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;
@end
@interface LZMediator : NSObject
//Protocol Class
+ (void)registerProtol:(Protocol *)proto class:(Class)cls;
+ (Class)classForProtocol:(Protocol *)protocol;
@end
@implementation LZMediator
//也需要一个注册表
+ (NSMutableDictionary *)mediatorCache{
static NSMutableDictionary *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = @{}.mutableCopy;
});
return cache;
}
//将组件的 类 和 协议名 注册到表中
+ (void)registerProtocol:(Protocol *)protocol class:(Class)cls{
if (protocol && cls) {
[[[self class] mediatorCache] setObject:cls forKey:NSStringFromProtocol(protocol)];
}
}
//调用者根据传入的 协议 取出注册到表中的 组件类
+ (Class)classForProtocol:(Protocol *)protocol{
return [[[self class] mediatorCache] objectForKey:NSStringFromProtocol(protocol)];
}
@end
@implementation LZDetailViewController
//main 函数之前的方法,将被调用的组件设置 Scheme 并注册到 Mediator中间层 中
+ (void)load {
[LZMediator registerProtol: @protocol(LZDetailViewControllerProtocol) class:[self class]];
}
@end
//通过传入 协议,取出 组件类 实现协议方法
Class cls = [LZMediator classForProtocol: @protocol(LZDetailViewControllerProtocol)];
[self.navigationController pushViewController:[[cls alloc] detailViewControllerWithUrl:model.articleUrl] animated:YES];
11、静态库 & 动态库
- 库
Library- 代码共享 / 保护核⼼代码
- 提⾼编译速度 / 减少代码体积(系统)
11.1、制作库
Framework- 资源的打包⽅式
- ⽀持静态库 / 动态库
- 系统Framework都是动态库
- ⽀持 Extension 共享 -
Embedded framework
11.2、静态库(.a ⽂件)
编译时期拷⻉- 增⼤代码体积 / 加载后不可改变
- 开发者使用较多
- ⼀般需要设置
Other Linker Flags等
制作静态库
- 新建工程选择上边
Static Library选项 - 新建文件完成自己的业务,然后运行,Products 文件夹下会自动生成 .a 文件
模拟器与真机架构不同,因此生成的.a也不同,切换后会飘红- 此时生成的静态库只包含创建工程时生成的 LZStatic 文件
- 在 Build Phases 中的
Copy Files中添加想要对外暴露的文件,再运行即可 - 合并静态库(真机、模拟器静态库合并)
lipo -create xx.a xx.a -output xx.a
11.3、动态库(.dylib)
- 编译时期只存储引⽤,
运⾏时加载到内存 - ⽆须拷⻉,减少体积 / 性能损失 / 安全性不好(因此开发者使用较少)
- 只有系统的动态库是真正的动态库
制作动态库
- 新建工程选择上边
Framework选项 - 新建文件完成自己的业务,然后运行,Products 文件夹下会自动生成 .framework 文件(注意事项与静态库相同)
- 在 Build Phases 中的
Headers中,将想要暴露给外界的文件从 Project 中拖拽到 Public中,再运行 - 导入项目中运行会产生报错:(因为 framework 并不是完全的动态库)
dyld:Library not loaded:xxx
Reason: image not found - Embedded Content 中添加 .framework ,选择
Embed & Sign,然后再运行就不会报错了
- 动态库改为静态库(也印证了我们生成的动态库并不是严格的动态库)
12、集成登录、分享功能
12.1、OAuth & OpenID
12.1.1、OAuth 授权
- 第三⽅登录使⽤⽤户名/ 密码
- 安全:登录还是在QQ、微信完成
- ⽤户成本:减少维护用户数据
- 开放的协议,标准的⽅式去访问需要⽤户授权的API服务
12.1.2、OpenID
用于OAuth授权时,QQ、微信等标记待授权用户隐藏明⽂(如QQ号):通过 加密账号 与 请求授权的APP的AppID 再加密,形成独⽴的 OpenID(即: 同一个QQ号授权给不同的APP时生成不同的OpenID)
12.2、集成登录分享的实现逻辑
- 申请接⼊,获取 AppID 和 ApiKey
- 集成 SDK 设置对应的 settings 以及 URL Scheme
- 业务逻辑的基础 UI 和交互(登录按钮 / 分享按钮…)
- 通过⽤户登录验证和授权,获取 Access Token
- 通过 Access Token 获取⽤户的 OpenID
- 调⽤ OpenAPI 请求访问或修改⽤户授权的资源
客户端保存相应⽤户信息,按需展示- 调⽤其它 API 实现分享等逻辑
13、日志与上报
13.1、日志
-
日志记录系统
-
CocoaLumberjack -
注意内容
13.2、Crash
-
iOS 中的 Crash 种类
系统级 :Signal 信号- Mach 内核异常时发送 Unix 信号
- SIGKILL / SIGSEGV…
应⽤级 :NSException- 应⽤或⾃定义异常
- NSInvalidArgumentException…
- 其他
-
简单系统 Crash 收集
- (void)testCrash { //测试crash收集 [self _caughtException]; [@[].mutableCopy addObject:nil]; } #pragma mark - CRASH - (void)_caughtException{ //NSexception NSSetUncaughtExceptionHandler(HandleNSException); //signal signal(SIGABRT, SignalExceptionHandler); signal(SIGILL, SignalExceptionHandler); signal(SIGSEGV, SignalExceptionHandler); signal(SIGFPE, SignalExceptionHandler); signal(SIGBUS, SignalExceptionHandler); signal(SIGPIPE, SignalExceptionHandler); } void SignalExceptionHandler(int signal){ void* callstack[128]; int frames = backtrace(callstack, 128); char **strs = backtrace_symbols(callstack, frames); NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; for (int i = 0; i < frames; i++) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs) //存储crash信息。 } void HandleNSException(NSException *exception){ __unused NSString *reason = [exception reason]; __unused NSString *name = [exception name]; //存储crash信息。 }
13.3、上报
14、定位(CoreLocation)
- 需要
CoreLocation.framework库 - 获取设备的地理位置、⽅向、海拔等信息
- 系统会通过多种⽅式获得(WI-FI / GPS / 蓝⽛…)
14.1、定位服务的使⽤
- ⾸次使⽤需要获取权限
- 通过
CLLocationManager获得当前的位置
14.2、定位权限
- 两个维度的权限设置
- 手机整体权限:隐私 - 定位服务
- 具体App权限:位置服务
- 两个维度的权限获取
[CLLocationManager locationServicesEnabled]- 引导跳转 Setting
[CLLocationManager authorizationStatus]- 设置 info.plist 申请权限的具体原因
- 发起询问
- 在 delegate 中处理授权变化
14.3、重要类
CLLocationManager- 提供位置相关的操作
- 权限
- 开始或停⽌位置相关的服务
CLLocationManagerDelegate- 位置相关权限更新回调
- 定位成功 / 失败回调
- ⽅向 / 指定区域的进⼊离开
CLGeocoderCLLocation:某个位置的地理信息(经度 / 维度 / 海拔…)CLPlacemark:地标信息 (省市街道…)- 提供 CLLocation 和 CLPlacemark 之间的转换
定位服务的基本流程
示例
#import "LZLocation.h"
#import <CoreLocation/CoreLocation.h>
@interface LZLocation()<CLLocationManagerDelegate>
@property(nonatomic, strong, readwrite) CLLocationManager *manager;
@end
@implementation LZLocation
//单例管理
+ (LZLocation *)locationManager{
static LZLocation *location;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
location = [[LZLocation alloc] init];
});
return location;
}
- (instancetype)init{
if (self = [super init]) {
self.manager = [[CLLocationManager alloc] init];
self.manager.delegate = self;
}
return self;
}
- (void)checkLocationAuthorization{
//判断系统是否开启
if(![CLLocationManager locationServicesEnabled]){
//未开启定位引导弹窗
}
//授权状态未决定时请求授权
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
[self.manager requestWhenInUseAuthorization];
}
}
#pragma mark - delegate
//切换授权状态时回调
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
//发起定位信息查询
[self.manager startUpdatingLocation];
} else if (status == kCLAuthorizationStatusDenied){
//授权被拒绝
}
}
//查询到位置后回调
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
//地理信息
CLLocation *location = [locations firstObject];
//将地理信息切换成地标信息
CLGeocoder *coder = [[CLGeocoder alloc] init];
[coder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
//地标信息
}];
//查到了定位,暂时关闭获取位置信息
[self.manager stopUpdatingLocation];
}
15、推送
15.1、UserNotififications
- iOS10 之后统⼀ UserNotififications.framework
UNUserNotifificationCenter 单例管理全部本地、远程推送- 需要兼容低版本机型
- 统⼀的权限的申请
- 在每次 App 启动时调⽤
requestAuthorizationWithOptions - 系统在⾸次会出现弹窗,之后保存⽤户选择
- 远程推送与本地推送 的权限 Options 相同
//通知权限选项 typedef NS_OPTIONS(NSUInteger, UNAuthorizationOptions) { UNAuthorizationOptionBadge = (1 << 0), UNAuthorizationOptionSound = (1 << 1), UNAuthorizationOptionAlert = (1 << 2), UNAuthorizationOptionCarPlay = (1 << 3), UNAuthorizationOptionCriticalAlert API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) = (1 << 4), UNAuthorizationOptionProvidesAppNotificationSettings API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) = (1 << 5), UNAuthorizationOptionProvisional API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0)) = (1 << 6), UNAuthorizationOptionAnnouncement API_DEPRECATED("Announcement authorization is always included", ios(13.0, 15.0), watchos(6.0, 7.0)) API_UNAVAILABLE(macos, tvos) = (1 << 7), UNAuthorizationOptionTimeSensitive API_DEPRECATED("Use time-sensitive entitlement", ios(15.0, 15.0), watchos(8.0, 8.0), macos(12.0, 12.0), tvos(15.0, 15.0)) = (1 << 8), } - 在每次 App 启动时调⽤
15.2、UserNotififications 流程
重要类(UNNotification...)
UNNotificationContent:推送内容- 标题 / 副标题 / 提醒 / 声⾳…
UNNotificationTrigger:推送触发器-
UNPushNotificationTrigger(
封装远程推送的) -
UNTimeIntervalNotificationTrigger:
间隔多久触发 -
UNCalendarNotificationTrigger:
到某天几点触发 -
UNLocationNotificationTrigger:
到某个地方触发
-
UNNotificationRequest- 封装 Content 和 Trigger 为统⼀格式
- 交给 NotificationCenter 处理
UNUserNotificationCenter(集中管理 UNNotificationRequest 的控制中心)- 处理推送权限
- 接收和移除⽌ NotificaitonRequest
UNUserNotificationCenterDelegate- 即将展示推送的处理
- ⽤户进⾏交互⾏为的处理
15.3、本地推送
- 由本地应⽤触发(闹铃 / 待办事项)
- ⽆需⽹络数据,提供和远程推送统⼀的交互
15.4、远程推送
- 普通远程推送
- 静默推送
15.4.1、APNs(Apple Push Notifification services)
- 防⽌每个App 都要维持连接,统一管理远程推送
15.4.2、远程推送的流程
- App 发送
UDID和BundleID给 APNs,⽣成deviceToken - App 发送deviceToken给服务器
- 服务器将信息和设备 deviceToken 发送给 APNs
- APNs根据deviceToken进⾏推送
- 服务器端推送数据格式
推送流程对比
- 本地推送把 推送Request 交给 UNUserNotificationCenter 管理通知
- 远程推送把 推送Request 交给 APNs 管理通知,并先注册APNs服务
示例
//设置推送后图标红点方法
[UIApplication sharedApplication].applicationIconBadgeNumber = 99
//远程推送[[UIApplication sharedApplication] registerForRemoteNotifications]注册APNs后得到deviceToken
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
//可以收敛到LZNotificationManager中实现
//注册成功
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
//注册失败
}
#import "LZNotificationManager.h"
#import <UserNotifications/UserNotifications.h>
#import <UIKit/UIKit.h>
@interface LZNotificationManager ()<UNUserNotificationCenterDelegate>
@end
@implementation LZNotificationManager
//单例控制通知管理
+ (LZNotificationManager *)notificationManager{
static LZNotificationManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[LZNotificationManager alloc] init];
});
return manager;
}
//检查通知授权
- (void)checkNotificationAuthorization{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
//设置授权的通知类型(红点、声音)
[center requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
//本地推送
[self _pushLocalNotification];
//远程推送(需要在主线程中)
dispatch_async(dispatch_get_main_queue(), ^{
//向Apple注册APNs,为了获取deviceToken
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
}
}];
}
//本地推送设置
- (void)_pushLocalNotification{
//推送内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.badge = @(1);
content.title = @"我是标题";
content.body = @"测试用本地推送";
content.sound = [UNNotificationSound defaultSound];
//推送触发器
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.f repeats:NO];
//包装成推送请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"_pushLocalNotification" content:content trigger:trigger];
//将推送请求加入 通知控制中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
NSLog(@"%@",error);
}];
}
#pragma mark - UNUserNotificationCenterDelegate(本地和远程推送都会执行)
//前台接到通知时,通知呈现方式(不实现该方法默认在前台接到通知时没有任何反应)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
//点击通知横幅后进入APP进入
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
//处理badge展示逻辑
//点击之后根据业务逻辑处理
//[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
//处理业务逻辑
completionHandler();
}
@end
16、切换APP图标
- iOS 10.3 以后版本才能切换
- 需要设置图⽚和 info.plist
- Icon files (iOS 5)
- CFBundleAlternateIcons
- 待替换图标名(LOGO)
- UIPrerenderedIcon
- CFBundleIconFiles
- 待替换图标名(LOGO)
- 待替换图标名(LOGO)
- CFBundleAlternateIcons
- Icon files (iOS 5)
- 调用
setAlternateIconName方法//检查是否支持替换图标功能 if ([[UIApplication sharedApplication] supportsAlternateIcons]) { //替换图标 [[UIApplication sharedApplication] setAlternateIconName:@"LOGO" completionHandler:^(NSError * _Nullable error) { NSLog(@"切换成功"); }]; } - 恢复图标
[[UIApplication sharedApplication] setAlternateIconName:nil completionHandler:nil]; - 隐藏弹窗逻辑(可选实现)
新方式
-
向Assets.xcassets内添加一组新的ICON:
-
选择项目-> Build Setting ->搜索 Include all app icon assets,然后改为 YES
-
调用方法与旧方法一致,只是图片名换为创建的图片组名(NewIcon)
[[UIApplication sharedApplication] setAlternateIconName:@"NewIcon" completionHandler:^(NSError * _Nullable error) { NSLog(@"切换成功"); }];
17、Application Extension
- 与系统及其它 App 交互的 扩展功能
- Extension的存在依托于主APP,是主APP的一个 target,但会作为
单独的⼆进制⽂件运⾏ - ⼀个 App 可以有多个 Extension
Host App<---> Extension <--->Container App
17.1、Extension 的⽣命周期和通信
- Containing App:
提交Extension的APP- Extension 作为 Target,但是
独⽴运⾏和存储- 两者生命周期完全无关
- 两者存储空间完全无关,但可以共享部分代码和数据
- Extension通过 Scheme 那种APP间通讯方式的
Open URL调起 Containing App
- Extension 作为 Target,但是
- Host App:
使用Extension的APP- Extension 的使⽤者,在其中进⾏ UI 展示和业务逻辑(例如负一屏)
- ⽣命周期通常和 Containing App 没有关系
- 系统或者 Host App 通过 API 唤起
- 进⾏操作(同步 / 异步功能 / 唤起其它App )
- 执⾏操作之后系统⾃动关闭
17.2、创建Extension
-
选择想要的Extension扩展功能
-
如果不想用Storyboard则需删除后在 Info.plist 中进行配置
-
若需要调起主APP(两者独立)需要调用APP间通讯方式 openURL
// Returns the extension context. Also acts as a convenience method for a view controller to check if it participating in an extension request. @property (nullable, nonatomic,readonly,strong) NSExtensionContext *extensionContext API_AVAILABLE(ios(8.0)); //使用方法 [self.extensionContext openURL:[NSURL URLWithString:@"LZSampleApp://"] completionHandler:^(BOOL success) { NSLog(@"success"); }];- 使用系统提供的
extensionContext上下文执行
- 使用系统提供的
17.2、Extension 代码和数据共享
17.2.1、代码共享
- 通过
embededFramework - 在两个 bundle 中都 copy
- 可将通⽤的业务逻辑(⽹络请求 / 特定样式 UI …)作为Extension,供多处使用
- 注意 API 是否为 Extension 可⽤
- 标记为
NS_EXTENSION_UNAVAILABLE的宏不可使用 [UIApplication sharedApplication]不可使用- 执⾏⻓时间的后台任务也不能使用
- 标记为
共享framework- 方式一:可以项目栏选中要共享的framework,右侧栏勾选想共享的Extension的target
- 方式二:可以直接在该 Extension的target 的
Frameworks and Libraries选项中添加想要的framework
17.2.2、数据共享
- 因为存储空间独立,因此Extension 对 Containing App 的沙盒不能直接访问
- 通过
App Groups进行数据共享-
NSUserDefault
-
NSFileManager
- 将 主APP 和Extension的target 分别开启
App Groups能力 - 配置证书
- 实现共享代码(
不能使用单例,因为内存不同)
以BundleID为SuiteName//存入NSUserDefaults,不能使用单例 NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.LZ.test.PP.LZShare"]; [userDefaults setObject:@"共享数据内容" forKey:@"shareContext"]; //在Extension合适位置取出数据 NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.LZ.test.PP.LZShare"]; [userDefaults objectForKey:@"shareContext"];
-
- 共享数据的同步管理
18、签名与证书
- 保证控制 App 的来源,防止都绕过市场下载应用
- 防⽌Profifiles 丢失 - 增加本地 CSR(开发者身份) / P12(私钥)
- 签名 / 公钥 / 私钥
- 钥匙串
19、iOS唯一标识符
- UDID (iOS 5)
- Mac 地址 (iOS 7)
IDFA- Identififier For Advertising(⼴告标识符)- 同⼀个设备上所有 App 取值相同
- 手机设置中重置后会改变
ASIdentififierManager
IDFV- Identififier For Vendor(应⽤开发商标识符)- App Store 信息或 Bundle ID 进⾏判断
- Bundle ID 除去最后⼀位判断是否相同(前3位,域名倒序那串)
- 所有同一开发商的应⽤被卸载后会重置
UIDevice identififierForVendor
20、框架
IGListKit:复杂列表框架HybridPagekit:复杂内容⻚框架(例如一个页面同时有webview与tableView)- 针对内容展示的通⽤框架
- 完善WKWebView / 复⽤回收
- 多种滚动视图的协同滚动
- ⾯向协议的模块化设计
21、其他
21.1、系统相关
21.1.1、[[NSBundle mainbundle] infoDictionary]
- 可以调取各种手机系统参数
//各种系统参数
- [[NSBundle mainbundle] infoDictionary]
21.1.2、[[NSUserDefaults standardUserDefaults] synchronize]
- 需要注意的是如果程序意外退出,[NSUserDefaults standardUserDefaults]数据不会被系统写入到该文件,不过可以使用 [[NSUserDefaults standardUserDefaults] synchronize 命令直接同步到文件里,来避免数据的丢失
- 同时NSUserDefaults的缓存避免了在每次读取数据时候都打开用户默认数据库的操作,可以通过调用synchronize方法来使内存中的缓存与用户默认系统进行同步
21.2、三方库相关
21.2.1、M1的pod安装
arch -x86_64 Pod install
21.2.2、AFNetworkReachabilityStatus
- AFNetworkReachabilityStatus:网络连接状态
21.3、关键字
21.3.1、@synthesize
@synthesize 在低版本中,类中声明了属性,系统并不会自动生成实例变量,和setter getter方法实现,必须在 类的实现中 声明 @synthesize 属性名 = _属性名(也可以自定义别的名字),@synthesize的作用就是 定义实例变量,同时实现 setter 和 getter方法。但是在高版本(具体哪个版本 ,请自行百度)中就不用在 声明@synthesize,系统会自动生成 _属性名实例变量 和 改属性getter setter方法实现。
- 给属性设置别名
例如 声明了属性 name,那么系统会自动生成 _name 实例变量,同时实现setter 和 getter 方法,即是 对_name 这个变量赋值,获取这个变量的值
@property (nonatomic,copy) NSString *name;
如果我们想要为这个属性设置一个别名可以使用以下方法,设置了别名就不能使用系统自动生成的_name,得使用 _myName
@synthesize name = _myName;
//或者 ,这样生成的实例变量名称和属性名称默认一样
@synthesize name;
- 同时重写setter 和 getter方法:
众所周知,属性的 setter和 getter方法是不能同时重写的,因为你同时重写了这两个方法,系统就不会帮你生成实例变量了,所以会报错,说不存在这个变量。但是有时候需要重写,那么我们就自定义实例变量
@interface ViewController ()
@property(nonatomic, strong) NSString *name;
@end
@implementation ViewController
@synthesize name = _name;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.name = @"hahhah";
NSLog(@"name = %@",_name);
}
- (void)setName:(NSString *)name {
_name = [NSString stringWithFormat:@"我:%@",name];
}
- (NSString *)name {
return _name;
}
@end
- 在@protocol 中声明了 属性,其实是在协议中声明了 setter和 getter方法,但是没有实现,也没有实例变量,所以当实现这个协议,就要实现setter和getter方法,并且添加一个实例变量
@protocol ZFPlayerMediaPlayback <NSObject>
@required
/// The view must inherited `ZFPlayerView`,this view deals with some gesture conflicts.
@property (nonatomic) ZFPlayerView *view;
@optional
//ZFAVPlayerManager 实现了 ZFPlayerMediaPlayback协议
@implementation ZFAVPlayerManager
//声明了实例变量_view,同时实现了 setter和getter方法
@synthesize view = _view;
@end
21.3.2、@dynamic
这个修饰符就是告诉编译器 不自动生成该属性的 setter 和 getter方法,当然也不会生成对应的实例变量
@interface ViewController () <ViewControllerDelegate>
{
NSString *_name ;
}
@property(nonatomic, strong) NSString *name;
@end
@implementation ViewController
@synthesize ppp = _ppp;
@dynamic name; //告诉编译器 不自动生成 getter setter ,由用户自己生成
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"hahhah";
}
- (void)setName:(NSString *)name {
_name = name;
}
- (NSString *)name {
return _name;
}
@end
21.4、实用方法
21.4.1、makeObjectsPerformSelector
//意为数组中的每个元素都执行method方法
makeObjectsPerformSelector:@selector(method:) withObject:obj
//使用该方法时需要注意,withObject的参数需要为对象,而不是基本数据类型等.如想将一个btn数组的按钮都设为selected,可以用
[self.selectedBtnArr makeObjectsPerformSelector:@selector(setSelected:) withObject:@(YES)];