1.單個(gè)控制器監(jiān)聽進(jìn)入后臺(tái)
在該控制器的-viewDidLoad方法中,添加代碼監(jiān)聽notification,也可以使用監(jiān)聽APP返回方法
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(someMethod:)
name:UIApplicationDidBecomeActiveNotification object:nil];
監(jiān)聽方法的具體實(shí)現(xiàn)
-(void)someMethod:(NSNotification *)noti
{
? ? ? ? //從該控制器進(jìn)入后臺(tái)時(shí)需要執(zhí)行的代碼
}
恢復(fù)到前臺(tái)
- (void)applicationDidBecomeActive:(UIApplication *)application{
????NSLog(@"---applicationDidBecomeActive----");
}
2.十六進(jìn)制字符串轉(zhuǎn)換成字節(jié)數(shù)組.
目標(biāo)字符串 NSString *str = @"0200107580FD7590FD750FD7590FD75A0FD";
調(diào)用方法:NSData *temp = [string hexToBytes]; ?轉(zhuǎn)換后
?byte數(shù)組?Byte byteArr[] = { 0x02, 0x00, 0x10, ... , ?0xFD };
3、對(duì)于項(xiàng)目中的第三庫(kù)一定要進(jìn)行再次封裝,包括網(wǎng)絡(luò)、刷新、提示、模型轉(zhuǎn)換等所有能封裝的部分,一直用MJExtension來(lái)做字典轉(zhuǎn)模型,突然想用YYModel了,項(xiàng)目中替換很麻煩,刷新也一樣,一直用第三方框架,突然想自己寫,改起來(lái)麻煩的不要不要的;
4、熟悉一下測(cè)試的幾種方案,例如交叉測(cè)試(一個(gè)功能正在運(yùn)行,另一個(gè)功能運(yùn)行對(duì)它的影響,例如 掃碼時(shí)打開燈光,突然退到后臺(tái),再回來(lái)查看,可能燈光已經(jīng)熄滅,按鈕還沒改變狀態(tài))等,這樣寫程序時(shí)才知道往哪些方面考慮
5、多寫點(diǎn)代碼塊,寫起代碼來(lái)會(huì)很快,我把自定義Cell都封裝了代碼塊,用xib布局cell就沒有純手寫這么快了,改起來(lái)還麻煩,這就是代碼塊的好處;
6、主控制器因?yàn)榇a比較多,結(jié)構(gòu)一定要清晰,才方便尋找,插入的類按 服務(wù)工具類+MVC 劃分,屬性按修飾符劃分,下面代碼按功能劃分,如下
#import "ViewController.h"
// 工具和服務(wù)類
#import "Header.h"
#import "Tool1.h"
#import "Tool2.h"
// Model
#import "Model1.h"
#import "Model2.h"
#import "Model3.h"
// View
#import "View1.h"
#import "View2.h"
#import "View3.h"
// Controller
#import "ViewController1.h"
#import "ViewController2.h"
#import "ViewController3.h"
@interface ViewController ()
// 按修飾符劃分
@property (nonatomic, assign) NSInteger num1;
@property (nonatomic, assign) NSInteger num2;
@property (nonatomic, strong, nonnull) NSMutableArray<NSString *> *object3;@property (nonatomic, strong, nonnull) NSMutableArray<NSNumber *> *object4;
@property (nonatomic, copy, nullable) NSString *object5;
@property (nonatomic, copy, nullable) NSString *object6;
@property (nonatomic, weak) UILabel *object1;
@property (nonatomic, weak) UILabel *object2;
@end
@implementation ViewController
- (void)viewDidLoad {? ?
????[super viewDidLoad]; ? ?
}
#pragma mark - 初始化View
- (void)initView{
?}
#pragma mark - 系統(tǒng)方法,界面顯示到銷毀
#pragma mark - 代理
#pragma mark - 按鈕點(diǎn)擊,通知,定時(shí)器
#pragma mark - 輔助方法#pragma mark - 懶加載
@end
7、定時(shí)器不是馬上開始的,多久觸發(fā)一次事件,多久才開始,記得在退出頁(yè)面的時(shí)候釋放定時(shí)器,否則控制器不會(huì)釋放;
8、如果錯(cuò)誤提示中出現(xiàn)了duplicate這樣的字眼,很可能就是引入了.m文件
9、UIView的tag不能為0;
10、字典轉(zhuǎn)JSON字符串;
NSData *data = [NSJSONSerialization dataWithJSONObject:params options:NSJSONWritingPrettyPrinted error:nil];
NSString *paramStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
11、有時(shí)在動(dòng)畫過(guò)程中,需要避免用戶重復(fù)操作,否則很容易崩潰。
建議動(dòng)畫過(guò)程中設(shè)置[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
允許用戶操作[[UIApplication sharedApplication] endIgnoringInteractionEvents];
12、如果延時(shí)執(zhí)行事件會(huì)被多次觸發(fā),那是一件很危險(xiǎn)的事情,需要取消前面的延時(shí)執(zhí)行事件
[selfperformSelector:<#(nonnull SEL)#>
withObject:<#(nullable id)#>afterDelay:<#(NSTimeInterval)#>];// 延時(shí)執(zhí)行
[NSObject cancelPreviousPerformRequestsWithTarget:<#(nonnull id)#>
selector:<#(nonnull SEL)#>object:<#(nullable id)#>]// 取消延時(shí)執(zhí)行
13、測(cè)試某部分代碼的運(yùn)行時(shí)間
NSTimeInterval beginTime = CFAbsoluteTimeGetCurrent();
......// 執(zhí)行代碼
NSTimeInterval endTime = CFAbsoluteTimeGetCurrent();
time = endTime-beginTime;// 運(yùn)行時(shí)間
14、對(duì)某個(gè)控件截圖
UIGraphicsBeginImageContextWithOptions(view.bounds.size,NO,0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
15、添加毛玻璃效果
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc]initWithEffect:effect];
effectView.contentView = 控件;
16、添加長(zhǎng)按手勢(shì),在手勢(shì)開始時(shí)才執(zhí)行方法,避免方法被調(diào)用兩次
- (void)longPress:(UILongPressGestureRecognizer *)longPressGesture{
????if (UIGestureRecognizerStateBegan != longPressGesture.state) {
????????return;
????}
????... // 執(zhí)行方法
}
17、輸入框有值時(shí)才能點(diǎn)擊return key
textField.enablesReturnKeyAutomatically = YES;
18、isKindOfClass判斷對(duì)象是否是一個(gè)類的成員,或者是派生自該類的成員isMemberOfClass確定對(duì)象是否是當(dāng)前類的成員;
19、tableView設(shè)置cell的分割線從屏幕左側(cè)邊緣開始
?cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0);
20、tableView當(dāng)內(nèi)容不夠時(shí),去掉底部的分割線
self.tableView.tableFooterView = [[UIView alloc]init];
21、UITableView設(shè)置為Plain的樣式時(shí),你又有多組時(shí),組頭就會(huì)默認(rèn)有懸浮效果,停留在上邊,如果不想組頭懸浮在上邊,可以將樣式設(shè)為Grouped,把足部設(shè)置很小,解決這問題;
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(x,y,w,h) style:UITableViewStyleGrouped];
tableView.sectionFooterHeight = 0.0001;
22、設(shè)置UITableViewCell之間的間距,在自定義cell中重寫setFrame方法
-(void)setFrame:(CGRect)frame{
????frame.origin.y += 5;
????frame.size.height -= 5;
????[super setFrame:frame];
}
23、修改UISearchController上searchBar的風(fēng)格,遍歷子控件(很有用),找到合適的, 設(shè)置想要的子控件的顏色和分格;
UIImageView *barImageView = self.searchController.searchBar.subviews[0].subviews[0];
barImageView.layer.borderColor = [UIColor lightGrayColor];
barImageView.layer.borderWidth = 1;
UIView *textView = self.searchController.searchBar.subviews[0].subviews[1];
textView.backgroundColor = [UIColor whiteColor];
24、監(jiān)測(cè)WKWebView的加載進(jìn)度
[wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
25、一個(gè)控件獲取在某個(gè)控件上的坐標(biāo)點(diǎn)的四種方式
// 獲取View在window上的坐標(biāo)點(diǎn)的四種寫法
// 使用 convertRect:toRect 方法
CGRect rect = [view convertRect:view.bounds toRect:window];
CGRect rect = [view.superView convertRect:view.frame toRect:window];
// 使用 convertRect:from 方法
CGRect rect = [window convertRect:view.boundsfrom:view];
CGRect rect = [window convertRect:view.framefrom:view.superView];
26、iOS11獲取最上面的window
// iOS 11 以前
UIView *windowView = [[UIApplication sharedApplication].windows lastObject];
// iOS 11 以后
UIView *windowView = [[UIApplication sharedApplication].windows firstObject];
27、某個(gè)控件有雙擊和單擊時(shí),設(shè)置雙擊失敗時(shí),才觸發(fā)單擊
[oneTap requireGestureRecognizerToFail:doubleTap];
28、設(shè)置圖片捏合縮放,雙擊放大
// 首先把imageView添加到scrollview中
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
????return self.imageView;
????// 在代理返回當(dāng)前imageView現(xiàn)
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView{
????// 保證繞著中心點(diǎn)縮放
????????CGSize boundsSize = self.scrollView.bounds.size;
????????CGRect frameToCenter = self.imageView.frame;
????????if (frameToCenter.size.width < boundsSize.width) {
????????????frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) * 0.5f);
????????} else {
????????????frameToCenter.origin.x = 0;
????????}
????????if (frameToCenter.size.height < boundsSize.height) {
????????????????frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) * 0.5f);
????????}else {
????????????????frameToCenter.origin.y = 0;
????????}
????????if (!CGRectEqualToRect(self.imageView.frame, frameToCenter)) {
????????????????self.imageView.frame = frameToCenter;
????????}
}
-(void)doubleTap:(UITapGestureRecognizer *)tapBgRecognizer{
????CGFloat scale = 4; // 最大縮放比例
????if (self.self.imageView.frame.frame.size.width < kScreenWidth * scale) {
????????CGPoint point = [tapBgRecognizer locationInView:self.imageView.frame];
????????CGFloat xSize = kScreenWidth / scale;
????????CGFloat ySize = kScreenHeight / scale;
????????CGRect zoomRect = CGRectMake(point.x - xSize * 0.5f, point.y - ySize * 0.5f, xSize, ySize);
????????self.scrollView.maximumZoomScale = scale;
????????// 以點(diǎn)擊點(diǎn)為中心縮放到最大
????????[self.scrollView zoomToRect:zoomRect animated:YES];
????} else {
????????[UIView animateWithDuration:0.25 animations:^{
????????self.scrollView.zoomScale = 1.0;
????????self.scrollView.contentSize = self.scrollView.bounds.size;
? ? ????self.self.imageView.frame.frame = self.originFrame; }];
????}
}
29、尋找當(dāng)前控件的控制器
-(UIViewController*)ht_viewController{
????for(UIView *next = self; next; ?next = next.superview){
????????UIResponder *nextResponder = [next nextResponder];
????????if([nextResponder isKindOfClass:[UIViewController class]]{
????????????return (UIViewController *)nextResponder;
????????}
????}
????return nil;
}
30、UITextField的inputView屬性是指第一響應(yīng)的不是鍵盤,而是賦值給inputView的那個(gè)view, inputAccessoryView 是指往鍵盤上添加另一個(gè)view;
31、設(shè)置鍵盤在UIScrollView拖動(dòng)動(dòng)時(shí)消失
scrollview.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag
32、設(shè)置UITableViewCell之間的分割線顏色;
self.tableView.separatorColor = [UIColor redColor];
33、iOS9以后點(diǎn)擊狀態(tài)欄,UIScrollView可返回頂部;
34、顯示狀態(tài)欄的網(wǎng)絡(luò)請(qǐng)求菊花;
// 顯示菊花-----NO為關(guān)閉菊花
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
35、讓UILabel的字體適應(yīng)指定的寬度,當(dāng)寬度大時(shí),字體不變, 寬度小時(shí), 字體變小適應(yīng)寬度
label.adjustsFontSizeToFitWidth = YES;
36、監(jiān)聽拖動(dòng)進(jìn)度條時(shí)的狀態(tài)
監(jiān)聽拖動(dòng)進(jìn)度條時(shí)的狀態(tài)
[slider addTarget:self action:@selector(sliderChange:event:) forControlEvents:UIControlEventValueChanged];
- (void)sliderChange:(UISlider *)slider event:(UIEvent *)event{
????UITouch *touch = [[event allTouches] anyObject];
????// 根據(jù)狀態(tài)來(lái)做相應(yīng)的事情,避免拖動(dòng)時(shí)一直調(diào)用某些事件
????switch (touch.phase) {
????????case UITouchPhaseBegan: // 開始
????????case UITouchPhaseMoved: // 拖動(dòng)
????????case UITouchPhaseEnded: // 結(jié)束
????????default:
????????????break;
????????}
}
37、讓UITableView的某一行滾到底部;
[self.tableView scrollToRowAtIndexPath:indexPath? ? atScrollPosition:UITableViewScrollPositionBottom animated:YES];
38、以Modal的形式push出一個(gè)界面;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.75];
[self.navigationController pushViewController:vc animated:NO];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.navigationController.view cache:NO];
[UIView commitAnimations];
39、阻止設(shè)備自動(dòng)鎖屏[UIApplication sharedApplication].idleTimerDisabled = YES;在后臺(tái)不管用,退出當(dāng)前頁(yè)面時(shí),記得設(shè)為NO
40、為了避免循環(huán)引用,在block中我們一般都用弱引用,但是block中的弱引用對(duì)象可能會(huì)提前釋放,造成崩潰,我們需要在block中強(qiáng)引用一下這個(gè)弱對(duì)象;
__weak __typeof(self)weakSelf = self;
view.callback = ^(ViewStatus status) {
????// 強(qiáng)引用這個(gè)對(duì)象,避免執(zhí)行到一半 self釋放,造成崩潰;
????__strong __typeof(weakSelf)strongSelf = weakSelf;
????[strongSelf doSomething];
}
41、如果一個(gè)參數(shù)中需要包含多個(gè)枚舉值,用NS_OPTIONS,不要用NS_ENUM
// 位運(yùn)算保證任意組合的枚舉值進(jìn)行或運(yùn)算能得到唯一的值
typedefNS_OPTIONS(NSUInteger,TestName){
????????TestNameXiaoHua=1<<0,// 小花
????????TestNameXiaoBai=1<<1,// 小白
????????TestNameXiaoHei=1<<2// 小黑
};
[self eat:TestNameXiaoHua | TestNameXiaoBai];// 讓小花和小白有飯吃;
-(void)eat:(TestName)name{
????if( (name & TestNameXiaoHua) || (name & TestNameXiaoBai) ){
????????NSLog(@"有飯吃");
????}
}
42、快速生成一個(gè)帶值的可變字典
NSMutableDictionary *mutDic = @{@"key":@"value"}.mutableCopy;
43、忽略編譯器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"????// 設(shè)置要忽略的類型 這里是GNU警告
// 代碼
#pragma clang diagnostic pop
44、為了避免多線程訪問數(shù)據(jù)庫(kù),造成數(shù)據(jù)混亂,讓讀寫方法都在同一個(gè)隊(duì)列中進(jìn)行;
static const void * const IOKey = &IOKey;
// 開辟一個(gè)單例隊(duì)列
dispatch_queue_t NTESDispatchIOQueue(){
????static dispatch_queue_t queue;
????static dispatch_once_t onceToken;
????dispatch_once(&onceToken, ^{
????// dispatch_queue_create("", DISPATCH_QUEUE_SERIAL) queue = dispatch_queue_create("io.queue", 0);
????dispatch_queue_set_specific(queue, IOKey, (void *)IOKey, NULL); // 設(shè)置隊(duì)列的的標(biāo)記 });
????return queue;
}
typedef void(^dispatch_block)(void);
void io_sync_safe(dispatch_block block){
????// 如果是自己設(shè)置的隊(duì)列,執(zhí)行block
????if (dispatch_get_specific(IOKey)) {
????????block();
????} else// 如果不是自己設(shè)置的隊(duì)列,先創(chuàng)建隊(duì)列,再執(zhí)行block {
????????dispatch_sync(NTESDispatchIOQueue(), ^() {
????????block();
????});
}
45、監(jiān)聽UITextField值的改變,可以使用這個(gè)方法
[_textViewaddTarget:self.action:@selector(textFieldDidChangeValue:)forControlEvents:UIControlEventEditingChanged]
而不要使用這個(gè)代理,因?yàn)榭赡鼙O(jiān)聽不到
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { },
46、把C++算法庫(kù)封裝在OC文件里形成靜態(tài)庫(kù)時(shí),記得把OC的.m文件改成.mm文件
47、如果視圖里面存在唯一一個(gè)UIScrollView或其子類View時(shí),會(huì)主動(dòng)設(shè)置相應(yīng)的內(nèi)邊距,避免被導(dǎo)航欄遮住,如果我們的導(dǎo)航欄不透明,原點(diǎn)會(huì)從我們的導(dǎo)航欄下方算起,導(dǎo)致上方留白,解決這問題:
if (@available(iOS 11.0, *)) {
????????self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;????????//UIScrollView也適用
}else {
????????self.automaticallyAdjustsScrollViewInsets = NO;
?}
48、如果數(shù)組中的元素是唯一的,或者要查詢某個(gè)數(shù)據(jù)是否在該數(shù)組中,不要使用NSArray, 而是使用集合NSSet,集合采用的是哈希表,查詢速度更快;
49、動(dòng)畫里的萬(wàn)能神器CAShapeLayer,性能特別好,能實(shí)現(xiàn)很多神奇的效果,做什么動(dòng)畫前優(yōu)先考慮它,尤其做股票財(cái)經(jīng)、健康A(chǔ)PP類的,經(jīng)常需要畫圖,如果遇到性能問題,試試它;
50、swift中為了避免循環(huán)引用,我們使用weak或者unowned來(lái)解決,但它們是有區(qū)別的; unowned更像OC里的unsafe_unretained,當(dāng)引用對(duì)象釋放了以后,它仍然會(huì)保持對(duì)引用對(duì)象的一個(gè)無(wú)效引用,如果嘗試調(diào)用方法和成員屬性的話,程序就會(huì)崩潰;? weak則會(huì)在引用的內(nèi)容被釋放后,它會(huì)自動(dòng)地變成 nil;? 因此我們?cè)谑褂盟鼤r(shí),如果你引用對(duì)象不會(huì)釋放, 使用unowned,寫起來(lái)方便點(diǎn);? 如果你引用對(duì)象會(huì)被釋放,請(qǐng)使用weak,例如網(wǎng)絡(luò)請(qǐng)求;
51、在swift中用強(qiáng)制解包!一定要非常小心,盡量少用,一不小心程序就崩潰了;
52、在swift中數(shù)組和字典都屬于值類型,相當(dāng)于int類型,跟oc不一樣,當(dāng)你把數(shù)組賦值給另一個(gè)數(shù)組,修改數(shù)組的值不會(huì)影響另一個(gè)數(shù)組;
53、__bridge 用于OC指針與c語(yǔ)言中的 void *互相轉(zhuǎn)換,雖然?id 和void *能夠相互轉(zhuǎn)換。但轉(zhuǎn)換為void *,其安全性與賦值給__unsafe_unretained修飾符相近,甚至?xí)汀H绻芾頃r(shí)不注意賦值對(duì)象的所有者,就會(huì)因懸垂指針而導(dǎo)致程序崩潰。具體細(xì)節(jié),參考?__bridge 的那些事兒
54、void* 類型指針:通用變體類型指針;可以不經(jīng)轉(zhuǎn)換,賦給其他指針,函數(shù)指針除外;malloc返回的就是void*類型。
NULL指針:是一個(gè)標(biāo)準(zhǔn)規(guī)定的宏定義;#define NULL ((void *) 0) ? ? ?用來(lái)表示空指針常量;
零指針:指針值為0,零值指針,沒有存儲(chǔ)任何內(nèi)存地址的指針;可以使任意一種指針類型,eg:void * ;int * ;double *;
空指針:指針賦值為0;0*7;3-3等之后,指針即變成空指針;即:空指針不指向任何實(shí)際的對(duì)象或者函數(shù);NULL指針和零指針都是空指針。
野指針:指向垃圾內(nèi)存的指針;(1)指針變量沒有初始化(2)指針被delete或者free之后沒有置為空(3)指針操作超越了變量的范圍
懸垂指針:指向曾經(jīng)存放對(duì)象的內(nèi)存,但是該對(duì)象已經(jīng)不存在了;delete操作完成后的指針就是懸垂指針,此時(shí)需要將指針置為0變?yōu)榱阒抵羔槪?/p>
55、WKWebView 與 JS 交互,需要添加?[userController addScriptMessageHandler:self name:@"callFunction"]; ?callFunction 可自定義,與 JS 保持一致,才可交互。

于控制器中添加 evaluateJavaScript:方法

56.關(guān)于 -> 語(yǔ)法 與 .語(yǔ)法

self -> _topview 這個(gè)是把 self 當(dāng)成了個(gè)結(jié)構(gòu)體指針;
self.topview 是把self 當(dāng)成了一個(gè)對(duì)象,或者說(shuō)是結(jié)構(gòu)體變量;
另外,self-> 不會(huì)觸發(fā) set方法,self. 會(huì)觸發(fā)。
. 左側(cè)可以是結(jié)構(gòu)體變量,也可以是對(duì)象;->?左側(cè)肯定是當(dāng)成 結(jié)構(gòu)體指針。

像這種用法, k 就是個(gè)結(jié)構(gòu)體變量,p 是取了k 的地址,所以p 是個(gè) astrct 類型指針,他就可以用->
成員變量 + 讀寫方法 = 類屬性
簡(jiǎn)單直接的理解方式:.語(yǔ)法是去訪問類屬性,->是訪問成員變量。?. 語(yǔ)法用于尋址,在c 語(yǔ)言中,左側(cè)必須是個(gè)類型變量。而 - > 用于間接尋址。比如下圖

驗(yàn)證上面所說(shuō)


第一行 &k 和 &(k.a)的地址完全一樣,是因?yàn)樵谶@個(gè)結(jié)構(gòu)體中,于內(nèi)存存儲(chǔ)的時(shí)候,最開始的部分存放的就是a,然后緊接著就是b,當(dāng)拿到這個(gè)結(jié)構(gòu)題類型的變量K, 實(shí)際上就是拿到存儲(chǔ)的首地址。
而第二行的 p ,其實(shí)是 指針p 自身的地址,而指針指向的是 k。
57.Implicit declaration of function XXX is invalid in C99
? ? ** 和CNCopySupportedInterfaces CNCopyCurrentNetworkInfo 等相關(guān)的錯(cuò)?需要?#import <SystemConfiguration/CaptiveNetwork.h>
58.關(guān)于?armv7、armv7s、arm64、i386、x86_64 的區(qū)別
arm64:iPhone6s | iphone6s plus|iPhone6| iPhone6 plus|iPhone5S | iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
i386 是針對(duì)intel通用微處理器32位處理器
x86_64是針對(duì)x86架構(gòu)的64位處理器
模擬器32位處理器測(cè)試需要i386架構(gòu);
模擬器64位處理器測(cè)試需要x86_64架構(gòu);
真機(jī)32位處理器需要armv7,或者armv7s架構(gòu);
真機(jī)64位處理器需要arm64架構(gòu);
58.界面出來(lái)之前 xib 中的 frame 都不要去使用??梢栽趘iewDidAppear 方法里設(shè)置??♂?
59.使用CocoaPods創(chuàng)建自己的私有庫(kù),首次使用?pod lib lint 可能遇到的問題

這個(gè)問題是pod依賴的組件fourflusher與xcode版本不匹配造成的,可以使用如下命令:
1.sudo gem uninstall fourflusher?
2.sudo gem install fourflusher
必要情況下還需要更新pod
sudo gem update cocoapods
[!] TestLib did not pass validation, due to 3 warnings (but you can use `--allow-warnings` to ignore them).?
pod驗(yàn)證發(fā)現(xiàn)3個(gè)及以上的warning就會(huì)報(bào)這個(gè)錯(cuò),如果只是驗(yàn)證一下工程,能確保對(duì)外發(fā)布之前能修復(fù),可以使用 --allow-warnings
pod lib lint --allow-warnings
如果驗(yàn)證通過(guò),會(huì)看到 TestLib passed validation ,到這一步既完成驗(yàn)證。
60.WKWebView 背景透明
[self.webView setOpaque:NO];
[self.webView setBackgroundColor:[UIColor clearColor]];
[self.webView.scrollView setBackgroundColor:[UIColor clearColor]];
61.ARC 情況下堆、棧注意點(diǎn)

如圖第二個(gè)Block處不使用Copy發(fā)生崩潰,通過(guò)打印可以發(fā)現(xiàn)2個(gè)Block所處區(qū)域不同,前者?blk0 在堆區(qū),而后面的blk1在棧,容易被系統(tǒng)回收,在ARC 的情況下一般就直接拷貝到堆上去。

62.App 或 Mac 程序內(nèi)使用WebView播放網(wǎng)絡(luò)視頻,禁止自動(dòng)全屏:
? ? 目前在做一個(gè)Mac程序的監(jiān)控設(shè)備播放,由JavaScript控制播放,發(fā)現(xiàn)程序在加載頁(yè)面后會(huì)自動(dòng)彈出播放器并進(jìn)行全屏播放,碰巧視頻是采用如?.m3u8 格式,結(jié)果在退出全屏后,除非重新加載頁(yè)面,否則視頻無(wú)法播放;
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];? ?
configuration.allowsInlineMediaPlayback = true;? ?
self.webView =[[WKWebView alloc] initWithFrame:CGRectMake(0,20,W,H-20)configuration: configuration];
如果使用的是UIWebView:
self.webView.allowsInlineMediaPlayback = YES;
如果是在Xib中創(chuàng)建,需要勾選Inline Playback選項(xiàng):

63.手機(jī)連接打印機(jī)
let printVC = UIPrintInteractionController.init()? ? ? ?
let printInfo = UIPrintInfo.printInfo()? ? ? ?
printInfo.outputType = .general? ? ? ?
printInfo.duplex = .longEdge? ? ? ?
printVC.showsPaperSelectionForLoadedPapers = true? ? ? ?
printVC.printInfo = printInfo? ? ? ?
printVC.printFormatter = webView.viewPrintFormatter()? ? ? ?
printVC.present(animated: true, completionHandler: nil)
64.關(guān)于可變數(shù)組添加對(duì)象
? ? 假設(shè)有臨時(shí)變量可變數(shù)組A,可變數(shù)組B,可變數(shù)組B中有若干元素,當(dāng)可變數(shù)組A執(zhí)行 AddObject 添加數(shù)組B后,僅是添加可變數(shù)組B的指針,即數(shù)組B的首地址,當(dāng)操作數(shù)組B后,如移除數(shù)組元素,數(shù)組A中的數(shù)據(jù)也會(huì)隨之改變。
? ? 同理。數(shù)組A在添加字符串C后,改變字符串C的值,數(shù)組A中的元素也會(huì)改變。
65.dealloc 方法被調(diào)用后,不代表對(duì)象的內(nèi)存立刻被回收。詳情可以了解一下runloop內(nèi)部autoRelase的釋放機(jī)制.
66.ARC情況下,關(guān)于控制器dealloc未執(zhí)行,導(dǎo)致沒有移除通知的預(yù)防措施
????假設(shè) A、B、C三個(gè)類,都繼承自 D 類,即D類為基類,層級(jí)關(guān)系為A push B,B push C,由此可以知道,程序會(huì)當(dāng)C出棧時(shí),會(huì)執(zhí)行dealloc方法,然后D類執(zhí)行dealloc,接著B執(zhí)行dealloc,D又執(zhí)行dealloc,最后是A執(zhí)行dealloc,D再執(zhí)行dealloc。故而我們可以根據(jù)這一特性來(lái)設(shè)計(jì)。
? ? 首先定義一個(gè)全局的NSCache 來(lái)存儲(chǔ) 某個(gè)類class,其內(nèi)部監(jiān)聽所有KVO的Key,即?class 作為NSCache 的key, 所監(jiān)聽 key的數(shù)組作為Value,也就是一個(gè)類有多個(gè)通知的情況,然后在基類的delloc中,檢查是否存在這樣的key-value,如存在,for循環(huán)Remove。
? ? 解釋:假設(shè)因?yàn)镃類中的block強(qiáng)弱引用問題,或NSTime未釋放等原因?qū)е耫eallo未執(zhí)行,通知未移除。而當(dāng)B類出棧時(shí)若正常執(zhí)行dealloc,程序會(huì)自動(dòng)調(diào)用基類的dealloc,其方法內(nèi)檢查到NSCache中有通知未移除,就移除通知。以達(dá)到防止內(nèi)存泄漏的目的。
67. arrarWithArray 等效于 alloc init 的方式創(chuàng)建數(shù)組


68. 關(guān)于copy與mutableCopy
69. 不可變字符串相當(dāng)于常量,初始化后,只有當(dāng)程序結(jié)束運(yùn)行才會(huì)被釋放,并不會(huì)因?yàn)槌隽撕瘮?shù)作用域或者控制器被銷毀而釋放
驗(yàn)證:開始執(zhí)行for循環(huán),循環(huán)創(chuàng)建1億個(gè)String對(duì)象,CPU與內(nèi)存開始上漲

循環(huán)結(jié)束,CPU使用率下降,內(nèi)存占有率不變

70.?WWDC 2020 ,蘋果宣布當(dāng)用戶退款成功時(shí),無(wú)論哪種內(nèi)購(gòu)類型,開發(fā)者都能收到退款通知!
當(dāng)Apple受理了用戶(玩家)的退款申請(qǐng)后,即允許退款后,蘋果服務(wù)器會(huì)發(fā)送通知,(商家)通過(guò)處理退款信息以響應(yīng)退款通知,達(dá)到因用戶(退款)而采取的相應(yīng)行動(dòng),如扣除相關(guān)訂閱服務(wù)或游戲道具等。

詳細(xì)說(shuō)明:https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications
在蘋果后臺(tái)可以配置一個(gè)退款通知的回調(diào)地址(一個(gè)App配置一條鏈接):

配置的回調(diào)鏈接必須滿足條件:
滿足應(yīng)用傳輸安全要求(使用https)
URL 最長(zhǎng) 255 字符
注意:這里的?https?是指蘋果的 App Transport Security (ATS),其中有協(xié)議的要求,比如使用 Transport Layer Security (TLS) protocol 1.2 版本,具體見蘋果文檔:Preventing Insecure Network Connections | Apple Developer Documentation。
蘋果把回調(diào)的通知分為2種類型:
退款通知類型
取消通知類型
其中新增加的退款通知類型是針對(duì):
消耗型
非消耗型
非續(xù)期訂閱
取消通知類型是針對(duì):
自動(dòng)續(xù)期訂閱
退款通知流程

退款通知的內(nèi)容:
蘋果返回的通知內(nèi)容為 JSON 對(duì)象數(shù)據(jù),所有的退款訂單的通知是在?unified_receipt?里的?latest_receipt_info?數(shù)組中:


在?unified_receipt?里的?latest_receipt_info?是一個(gè)數(shù)組,其中包含的最近的100次應(yīng)用內(nèi)購(gòu)買交易:

數(shù)據(jù)中每個(gè)退款訂單的主要字段:


詳細(xì)的返回字段見官方文檔:
responseBody | Apple Developer Documentation
unified_receipt | Apple Developer Documentation
responseBody.Latest_receipt_info | Apple Developer Documentation
退款通知的響應(yīng)
您的服務(wù)器應(yīng)發(fā)送HTTP狀態(tài)代碼,以指示服務(wù)器到服務(wù)器的通知接收是否成功:
如果回調(diào)接收成功,則發(fā)送 HTTP?200。您的服務(wù)器不需要返回?cái)?shù)據(jù)。
如果回調(diào)接收不成功,請(qǐng)發(fā)送 HTTP?50x?或?40x?讓 App Store 重試該通知。App Store在一段時(shí)間內(nèi)嘗試重試該通知,但在連續(xù)失敗嘗試后最終停止(3次)。
注意事項(xiàng):
當(dāng)您使用包含退款交易的收據(jù)?transaction_data?向蘋果服務(wù)器校驗(yàn)?verifyReceipt?時(shí),JSON響應(yīng)中不存在退款交易,自動(dòng)續(xù)訂訂閱除外。
收到?REFUND?通知時(shí),您有責(zé)任為每筆退款交易存儲(chǔ),監(jiān)控并采取適當(dāng)?shù)拇胧?。(因?yàn)樘O果只通知一次,暫時(shí)無(wú)法在蘋果后臺(tái)查詢退款的訂單。也不能由開發(fā)者主動(dòng)去蘋果服務(wù)器查詢。)
自動(dòng)續(xù)訂訂閱通知
這個(gè)取消通知之前就一直有,所以這里不重復(fù)了,需要的自行搜索。
自動(dòng)續(xù)訂訂閱的相關(guān)文檔:
Handling Subscriptions Billing | Apple Developer Documentation
In-App Purchases and Using Server-to-Server Notifications - WWDC 2019 - Videos - Apple Developer
Subscription Offers Best Practices - WWDC 2019 - Videos - Apple Developer
最后有2點(diǎn),1.退款通知目前并不能在沙盒中測(cè)試;2.蘋果后臺(tái)也無(wú)法看到退款的訂單詳情;
71.?命令行修改App版本號(hào)
? ? cd 進(jìn)入工程所在目錄后
修改 Version 版本號(hào) - 如改成?3.0.0
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString 3.0.0" Info.plist
修改 Build 版本號(hào) - 如改成?3.0.202103051700
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion 3.0.202103051700" Info.plist
72. 真機(jī)調(diào)試下查看沙盒文件
連接真機(jī)設(shè)備,打開Xcode ,選擇Window -->Device and Simulator?,快捷鍵 Command + Shift + 2;

找到對(duì)應(yīng)的APP,選擇設(shè)置圖標(biāo),點(diǎn)擊Download?Container 下載到桌面

完成后就會(huì)在桌面上看見如下文件,右擊 ——> 選擇“顯示包內(nèi)容”


如果要想替換真機(jī)中的沙盒文件也是如上操作,在已下載好的文件中修改完成后。打開Xcode,Command + Shift + 2。選擇Replace Container,選擇修改后的沙盒文件,點(diǎn)擊Open。

73. 獲取不到內(nèi)購(gòu)套餐產(chǎn)品信息的情況
1.檢查App的Bundle ID是否與蘋果商店中配置的信息統(tǒng)一;
2.檢查產(chǎn)品ID是否填寫錯(cuò)誤;
3.檢查是否同意App付費(fèi)協(xié)議,及填寫銀行卡等稅務(wù)信息;
????????當(dāng)在蘋果商店配置好內(nèi)購(gòu)套餐后,即便不提交審核(二進(jìn)制文件缺失),或者審核失敗,App在沙盒環(huán)境下,仍然能獲取、購(gòu)買App內(nèi)購(gòu)套餐;
74. 簡(jiǎn)單的線程死鎖

75.并行與串行的區(qū)別

在子線程中啟動(dòng)定時(shí)器,需要使用Runloop
76.OC的動(dòng)態(tài)消息機(jī)制
調(diào)用某個(gè)對(duì)象的私有方法


77.字符轉(zhuǎn)換
將普通話轉(zhuǎn)換為拉丁符號(hào),且?guī)б魳?biāo),包括希伯來(lái)語(yǔ)、阿拉伯語(yǔ)、泰語(yǔ)等多種語(yǔ)言。對(duì)處理除英語(yǔ)以外的語(yǔ)言和非拉丁文字時(shí)非常有用


78.將時(shí)間戳轉(zhuǎn)換為NSDate 或 NSString (如今日0點(diǎn)時(shí)間戳1639929600 -> 2021-12-20 00:00:00)

79.在字典A中SetValue一個(gè)從字典B轉(zhuǎn)換成的字符串B‘,然后將字典A轉(zhuǎn)成字符串A’導(dǎo)致Web端解析失敗的問題

? ? 因?yàn)橥值渲蠸et 字符串pkgStr,會(huì)自動(dòng)為Value添加引號(hào),導(dǎo)致最后將字典infoData轉(zhuǎn)成字符串時(shí),會(huì)出現(xiàn) "webInfo":"{xxxx 的情況,導(dǎo)致Web解析失敗。
? ? 解決方法,直接在 infoData 中Set另一個(gè)字典 pkgInfo,不需要轉(zhuǎn)成字符串后再寫入


這樣就不會(huì)出現(xiàn) Web 解析失敗的問題;
80.關(guān)于App后臺(tái)推送標(biāo)題、副標(biāo)題、正文及圖片音頻視頻等內(nèi)容正常應(yīng)由服務(wù)器確定,但App配置文件與代碼優(yōu)先級(jí)更高。優(yōu)先級(jí)為:App配置文件(TARGETS——NotificationService——Build Settings——Info.plist Value內(nèi)修改Display Name)、NotificationService.m內(nèi)UNMutableNotificationContent對(duì)象的title屬性、服務(wù)器推送內(nèi)容中的title字段,以上內(nèi)容都為空時(shí),系統(tǒng)默認(rèn)以App名作為推送標(biāo)題。