iOS 知识点个人总结(不定期持续性更新)

1.单个控制器监听进入后台

在该控制器的-viewDidLoad方法中,添加代码监听notification,也可以使用监听APP返回方法

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(someMethod:)

name:UIApplicationDidBecomeActiveNotification object:nil];

监听方法的具体实现

-(void)someMethod:(NSNotification *)noti

{

        //从该控制器进入后台时需要执行的代码

}

恢复到前台

- (void)applicationDidBecomeActive:(UIApplication *)application{

    NSLog(@"---applicationDidBecomeActive----");

}

2.十六进制字符串转换成字节数组.

目标字符串 NSString *str = @"0200107580FD7590FD750FD7590FD75A0FD";

调用方法:NSData *temp = [string hexToBytes];  转换后

 byte数组 Byte byteArr[] = { 0x02, 0x00, 0x10, ... ,  0xFD };


3、对于项目中的第三库一定要进行再次封装,包括网络、刷新、提示、模型转换等所有能封装的部分,一直用MJExtension来做字典转模型,突然想用YYModel了,项目中替换很麻烦,刷新也一样,一直用第三方框架,突然想自己写,改起来麻烦的不要不要的;

4、熟悉一下测试的几种方案,例如交叉测试(一个功能正在运行,另一个功能运行对它的影响,例如 扫码时打开灯光,突然退到后台,再回来查看,可能灯光已经熄灭,按钮还没改变状态)等,这样写程序时才知道往哪些方面考虑

5、多写点代码块,写起代码来会很快,我把自定义Cell都封装了代码块,用xib布局cell就没有纯手写这么快了,改起来还麻烦,这就是代码块的好处;

6、主控制器因为代码比较多,结构一定要清晰,才方便寻找,插入的类按 服务工具类+MVC 划分,属性按修饰符划分,下面代码按功能划分,如下

#import "ViewController.h"

// 工具和服务类

#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 - 系统方法,界面显示到销毁

#pragma mark - 代理

#pragma mark - 按钮点击,通知,定时器

#pragma mark - 辅助方法#pragma mark - 懒加载


@end


7、定时器不是马上开始的,多久触发一次事件,多久才开始,记得在退出页面的时候释放定时器,否则控制器不会释放;

8、如果错误提示中出现了duplicate这样的字眼,很可能就是引入了.m文件

9、UIView的tag不能为0;

10、字典转JSON字符串;

NSData *data = [NSJSONSerialization dataWithJSONObject:params options:NSJSONWritingPrettyPrinted error:nil];

NSString *paramStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

11、有时在动画过程中,需要避免用户重复操作,否则很容易崩溃。

建议动画过程中设置[[UIApplication sharedApplication] beginIgnoringInteractionEvents];

允许用户操作[[UIApplication sharedApplication] endIgnoringInteractionEvents];

12、如果延时执行事件会被多次触发,那是一件很危险的事情,需要取消前面的延时执行事件

[selfperformSelector:<#(nonnull SEL)#>

withObject:<#(nullable id)#>afterDelay:<#(NSTimeInterval)#>];// 延时执行


[NSObject cancelPreviousPerformRequestsWithTarget:<#(nonnull id)#>

selector:<#(nonnull SEL)#>object:<#(nullable id)#>]// 取消延时执行


13、测试某部分代码的运行时间

NSTimeInterval beginTime = CFAbsoluteTimeGetCurrent();

......// 执行代码

NSTimeInterval endTime = CFAbsoluteTimeGetCurrent();

time = endTime-beginTime;// 运行时间


14、对某个控件截图

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、添加长按手势,在手势开始时才执行方法,避免方法被调用两次

- (void)longPress:(UILongPressGestureRecognizer *)longPressGesture{

    if (UIGestureRecognizerStateBegan != longPressGesture.state) {

        return;

    }

    ... // 执行方法

}


17、输入框有值时才能点击return key

textField.enablesReturnKeyAutomatically = YES;


18、isKindOfClass判断对象是否是一个类的成员,或者是派生自该类的成员isMemberOfClass确定对象是否是当前类的成员;

19、tableView设置cell的分割线从屏幕左侧边缘开始

 cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0);


20、tableView当内容不够时,去掉底部的分割线

self.tableView.tableFooterView = [[UIView alloc]init];


21、UITableView设置为Plain的样式时,你又有多组时,组头就会默认有悬浮效果,停留在上边,如果不想组头悬浮在上边,可以将样式设为Grouped,把足部设置很小,解决这问题;

UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(x,y,w,h) style:UITableViewStyleGrouped];

tableView.sectionFooterHeight = 0.0001;


22、设置UITableViewCell之间的间距,在自定义cell中重写setFrame方法

-(void)setFrame:(CGRect)frame{

    frame.origin.y += 5;

    frame.size.height -= 5;

    [super setFrame:frame];

}


23、修改UISearchController上searchBar的风格,遍历子控件(很有用),找到合适的, 设置想要的子控件的颜色和分格;

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、监测WKWebView的加载进度

[wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];


25、一个控件获取在某个控件上的坐标点的四种方式

// 获取View在window上的坐标点的四种写法

// 使用 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、某个控件有双击和单击时,设置双击失败时,才触发单击

[oneTap requireGestureRecognizerToFail:doubleTap];


28、设置图片捏合缩放,双击放大

// 首先把imageView添加到scrollview中

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{

    return self.imageView;

    // 在代理返回当前imageView现

}


- (void)scrollViewDidZoom:(UIScrollView *)scrollView{

    // 保证绕着中心点缩放

        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;

        // 以点击点为中心缩放到最大

        [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、寻找当前控件的控制器

-(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属性是指第一响应的不是键盘,而是赋值给inputView的那个view, inputAccessoryView 是指往键盘上添加另一个view;


31、设置键盘在UIScrollView拖动动时消失

scrollview.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag


32、设置UITableViewCell之间的分割线颜色;

self.tableView.separatorColor = [UIColor redColor];


33、iOS9以后点击状态栏,UIScrollView可返回顶部;


34、显示状态栏的网络请求菊花;

// 显示菊花-----NO为关闭菊花

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;


35、让UILabel的字体适应指定的宽度,当宽度大时,字体不变, 宽度小时, 字体变小适应宽度

label.adjustsFontSizeToFitWidth = YES;


36、监听拖动进度条时的状态

监听拖动进度条时的状态

[slider addTarget:self action:@selector(sliderChange:event:) forControlEvents:UIControlEventValueChanged];


- (void)sliderChange:(UISlider *)slider event:(UIEvent *)event{

    UITouch *touch = [[event allTouches] anyObject];

    // 根据状态来做相应的事情,避免拖动时一直调用某些事件

    switch (touch.phase) {

        case UITouchPhaseBegan: // 开始

        case UITouchPhaseMoved: // 拖动

        case UITouchPhaseEnded: // 结束

        default:

            break;

        }

}


37、让UITableView的某一行滚到底部;

[self.tableView scrollToRowAtIndexPath:indexPath    atScrollPosition:UITableViewScrollPositionBottom animated:YES];


38、以Modal的形式push出一个界面;

[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、阻止设备自动锁屏[UIApplication sharedApplication].idleTimerDisabled = YES;在后台不管用,退出当前页面时,记得设为NO

40、为了避免循环引用,在block中我们一般都用弱引用,但是block中的弱引用对象可能会提前释放,造成崩溃,我们需要在block中强引用一下这个弱对象;

__weak __typeof(self)weakSelf = self;

view.callback = ^(ViewStatus status) {

    // 强引用这个对象,避免执行到一半 self释放,造成崩溃;

    __strong __typeof(weakSelf)strongSelf = weakSelf;

    [strongSelf doSomething];

}


41、如果一个参数中需要包含多个枚举值,用NS_OPTIONS,不要用NS_ENUM

// 位运算保证任意组合的枚举值进行或运算能得到唯一的值

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、快速生成一个带值的可变字典

NSMutableDictionary *mutDic = @{@"key":@"value"}.mutableCopy;


43、忽略编译器警告

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"    // 设置要忽略的类型 这里是GNU警告

// 代码

#pragma clang diagnostic pop


44、为了避免多线程访问数据库,造成数据混乱,让读写方法都在同一个队列中进行;

static const void * const IOKey = &IOKey;


// 开辟一个单例队列

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); // 设置队列的的标记 });

    return queue;

}


typedef void(^dispatch_block)(void);

void io_sync_safe(dispatch_block block){

    // 如果是自己设置的队列,执行block

    if (dispatch_get_specific(IOKey)) {

        block();

    } else// 如果不是自己设置的队列,先创建队列,再执行block {

        dispatch_sync(NTESDispatchIOQueue(), ^() {

        block();

    });

}


45、监听UITextField值的改变,可以使用这个方法

[_textViewaddTarget:self.action:@selector(textFieldDidChangeValue:)forControlEvents:UIControlEventEditingChanged]

而不要使用这个代理,因为可能监听不到

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { },


46、把C++算法库封装在OC文件里形成静态库时,记得把OC的.m文件改成.mm文件


47、如果视图里面存在唯一一个UIScrollView或其子类View时,会主动设置相应的内边距,避免被导航栏遮住,如果我们的导航栏不透明,原点会从我们的导航栏下方算起,导致上方留白,解决这问题:

if (@available(iOS 11.0, *)) {

        self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;        //UIScrollView也适用

}else {

        self.automaticallyAdjustsScrollViewInsets = NO;

 }


48、如果数组中的元素是唯一的,或者要查询某个数据是否在该数组中,不要使用NSArray, 而是使用集合NSSet,集合采用的是哈希表,查询速度更快;

49、动画里的万能神器CAShapeLayer,性能特别好,能实现很多神奇的效果,做什么动画前优先考虑它,尤其做股票财经、健康APP类的,经常需要画图,如果遇到性能问题,试试它;

50、swift中为了避免循环引用,我们使用weak或者unowned来解决,但它们是有区别的; unowned更像OC里的unsafe_unretained,当引用对象释放了以后,它仍然会保持对引用对象的一个无效引用,如果尝试调用方法和成员属性的话,程序就会崩溃;  weak则会在引用的内容被释放后,它会自动地变成 nil;  因此我们在使用它时,如果你引用对象不会释放, 使用unowned,写起来方便点;  如果你引用对象会被释放,请使用weak,例如网络请求;

51、在swift中用强制解包!一定要非常小心,尽量少用,一不小心程序就崩溃了;

52、在swift中数组和字典都属于值类型,相当于int类型,跟oc不一样,当你把数组赋值给另一个数组,修改数组的值不会影响另一个数组;

53、__bridge 用于OC指针与c语言中的 void *互相转换,虽然 id 和void *能够相互转换。但转换为void *,其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。具体细节,参考 __bridge 的那些事儿

54、void* 类型指针通用变体类型指针;可以不经转换,赋给其他指针,函数指针除外;malloc返回的就是void*类型。

NULL指针:是一个标准规定的宏定义;#define NULL ((void *) 0)      用来表示空指针常量;

零指针:指针值为0,零值指针,没有存储任何内存地址的指针;可以使任意一种指针类型,eg:void * ;int * ;double *;

空指针:指针赋值为0;0*7;3-3等之后,指针即变成空指针;即:空指针不指向任何实际的对象或者函数;NULL指针和零指针都是空指针。

野指针指向垃圾内存的指针;(1)指针变量没有初始化(2)指针被delete或者free之后没有置为空(3)指针操作超越了变量的范围

悬垂指针:指向曾经存放对象的内存,但是该对象已经不存在了;delete操作完成后的指针就是悬垂指针,此时需要将指针置为0变为零值指针;


55、WKWebView 与 JS 交互,需要添加 [userController addScriptMessageHandler:self name:@"callFunction"];  callFunction 可自定义,与 JS 保持一致,才可交互。

于控制器中添加 evaluateJavaScript:方法

56.关于 -> 语法 与 .语法

self -> _topview 这个是把 self 当成了个结构体指针;

self.topview 是把self 当成了一个对象,或者说是结构体变量;

另外,self-> 不会触发 set方法,self. 会触发。

. 左侧可以是结构体变量,也可以是对象;-> 左侧肯定是当成 结构体指针。

像这种用法, k 就是个结构体变量,p 是取了k 的地址,所以p 是个 astrct 类型指针,他就可以用->

成员变量 + 读写方法 = 类属性

简单直接的理解方式:.语法是去访问类属性,->是访问成员变量。 . 语法用于寻址,在c 语言中,左侧必须是个类型变量。而 - > 用于间接寻址。比如下图

验证上面所说

第一行 &k 和 &(k.a)的地址完全一样,是因为在这个结构体中,于内存存储的时候,最开始的部分存放的就是a,然后紧接着就是b,当拿到这个结构题类型的变量K, 实际上就是拿到存储的首地址。

而第二行的 p ,其实是 指针p 自身的地址,而指针指向的是 k。


57.Implicit declaration of function XXX is invalid in C99

    ** 和CNCopySupportedInterfaces CNCopyCurrentNetworkInfo 等相关的错 需要 #import <SystemConfiguration/CaptiveNetwork.h>


58.关于 armv7、armv7s、arm64、i386、x86_64 的区别

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 是针对intel通用微处理器32位处理器

x86_64是针对x86架构的64位处理器

模拟器32位处理器测试需要i386架构;

模拟器64位处理器测试需要x86_64架构;

真机32位处理器需要armv7,或者armv7s架构;

真机64位处理器需要arm64架构;


58.界面出来之前 xib 中的 frame 都不要去使用。可以在viewDidAppear 方法里设置🤷♂️


59.使用CocoaPods创建自己的私有库,首次使用 pod lib lint 可能遇到的问题

验证

这个问题是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验证发现3个及以上的warning就会报这个错,如果只是验证一下工程,能确保对外发布之前能修复,可以使用 --allow-warnings

pod lib lint --allow-warnings

如果验证通过,会看到 TestLib passed validation ,到这一步既完成验证。


60.WKWebView 背景透明

[self.webView setOpaque:NO];

[self.webView setBackgroundColor:[UIColor clearColor]];

[self.webView.scrollView setBackgroundColor:[UIColor clearColor]];


61.ARC 情况下堆、栈注意点

Block0在堆,Block1在栈

如图第二个Block处不使用Copy发生崩溃,通过打印可以发现2个Block所处区域不同,前者 blk0 在堆区,而后面的blk1在栈,容易被系统回收,在ARC 的情况下一般就直接拷贝到堆上去。

验证


62.App 或 Mac 程序内使用WebView播放网络视频,禁止自动全屏:

    目前在做一个Mac程序的监控设备播放,由JavaScript控制播放,发现程序在加载页面后会自动弹出播放器并进行全屏播放,碰巧视频是采用如 .m3u8 格式,结果在退出全屏后,除非重新加载页面,否则视频无法播放;

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中创建,需要勾选Inline Playback选项:

63.手机连接打印机

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.关于可变数组添加对象

    假设有临时变量可变数组A,可变数组B,可变数组B中有若干元素,当可变数组A执行 AddObject 添加数组B后,仅是添加可变数组B的指针,即数组B的首地址,当操作数组B后,如移除数组元素,数组A中的数据也会随之改变。

    同理。数组A在添加字符串C后,改变字符串C的值,数组A中的元素也会改变。

65.dealloc 方法被调用后,不代表对象的内存立刻被回收。详情可以了解一下runloop内部autoRelase的释放机制.


66.ARC情况下,关于控制器dealloc未执行,导致没有移除通知的预防措施

    假设 A、B、C三个类,都继承自 D 类,即D类为基类,层级关系为A push B,B push C,由此可以知道,程序会当C出栈时,会执行dealloc方法,然后D类执行dealloc,接着B执行dealloc,D又执行dealloc,最后是A执行dealloc,D再执行dealloc。故而我们可以根据这一特性来设计。

    首先定义一个全局的NSCache 来存储 某个类class,其内部监听所有KVO的Key,即 class 作为NSCache 的key, 所监听 key的数组作为Value,也就是一个类有多个通知的情况,然后在基类的delloc中,检查是否存在这样的key-value,如存在,for循环Remove。

    解释:假设因为C类中的block强弱引用问题,或NSTime未释放等原因导致deallo未执行,通知未移除。而当B类出栈时若正常执行dealloc,程序会自动调用基类的dealloc,其方法内检查到NSCache中有通知未移除,就移除通知。以达到防止内存泄漏的目的。

67. arrarWithArray 等效于 alloc init 的方式创建数组

深拷贝
输出结果

68. 关于copy与mutableCopy

69. 不可变字符串相当于常量,初始化后,只有当程序结束运行才会被释放,并不会因为出了函数作用域或者控制器被销毁而释放

验证:开始执行for循环,循环创建1亿个String对象,CPU与内存开始上涨

循环执行中

循环结束,CPU使用率下降,内存占有率不变

循环结束


70. WWDC 2020 ,苹果宣布当用户退款成功时,无论哪种内购类型,开发者都能收到退款通知!

当Apple受理了用户(玩家)的退款申请后,即允许退款后,苹果服务器会发送通知,(商家)通过处理退款信息以响应退款通知,达到因用户(退款)而采取的相应行动,如扣除相关订阅服务或游戏道具等。

流程

详细说明:https://developer.apple.com/documentation/storekit/in-app_purchase/handling_refund_notifications

在苹果后台可以配置一个退款通知的回调地址(一个App配置一条链接):

退款回调链接

配置的回调链接必须满足条件:

满足应用传输安全要求(使用https)

URL 最长 255 字符

注意:这里的 https 是指苹果的 App Transport Security (ATS),其中有协议的要求,比如使用 Transport Layer Security (TLS) protocol 1.2 版本,具体见苹果文档:Preventing Insecure Network Connections | Apple Developer Documentation


苹果把回调的通知分为2种类型:

退款通知类型

取消通知类型

其中新增加的退款通知类型是针对:

消耗型

非消耗型

非续期订阅

取消通知类型是针对:

自动续期订阅


退款通知流程

流程


退款通知的内容:

苹果返回的通知内容为 JSON 对象数据,所有的退款订单的通知是在 unified_receipt 里的 latest_receipt_info 数组中:

退款数据格式
数据说明

在 unified_receipt 里的 latest_receipt_info 是一个数组,其中包含的最近的100次应用内购买交易:

数据中每个退款订单的主要字段:

详细的返回字段见官方文档:

responseBody | Apple Developer Documentation

unified_receipt | Apple Developer Documentation

responseBody.Latest_receipt_info | Apple Developer Documentation


退款通知的响应

您的服务器应发送HTTP状态代码,以指示服务器到服务器的通知接收是否成功:

如果回调接收成功,则发送 HTTP 200。您的服务器不需要返回数据。

如果回调接收不成功,请发送 HTTP 50x 或 40x 让 App Store 重试该通知。App Store在一段时间内尝试重试该通知,但在连续失败尝试后最终停止(3次)。


注意事项:

当您使用包含退款交易的收据 transaction_data 向苹果服务器校验 verifyReceipt 时,JSON响应中不存在退款交易,自动续订订阅除外。

收到 REFUND 通知时,您有责任为每笔退款交易存储,监控并采取适当的措施。(因为苹果只通知一次,暂时无法在苹果后台查询退款的订单。也不能由开发者主动去苹果服务器查询。)


自动续订订阅通知

这个取消通知之前就一直有,所以这里不重复了,需要的自行搜索。

自动续订订阅的相关文档:

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点,1.退款通知目前并不能在沙盒中测试;2.苹果后台也无法看到退款的订单详情;


71. 命令行修改App版本号

    cd 进入工程所在目录后

修改 Version 版本号 - 如改成 3.0.0

/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString 3.0.0" Info.plist

修改 Build 版本号 - 如改成 3.0.202103051700

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion 3.0.202103051700" Info.plist


72. 真机调试下查看沙盒文件

连接真机设备,打开Xcode ,选择Window -->Device and Simulator ,快捷键 Command + Shift + 2;

Device and Simulator

找到对应的APP,选择设置图标,点击Download Container 下载到桌面

Download Container

完成后就会在桌面上看见如下文件,右击 ——> 选择“显示包内容”

沙盒文件
沙盒目录

如果要想替换真机中的沙盒文件也是如上操作,在已下载好的文件中修改完成后。打开Xcode,Command + Shift + 2。选择Replace Container,选择修改后的沙盒文件,点击Open。

Replace Container


73. 获取不到内购套餐产品信息的情况

1.检查App的Bundle ID是否与苹果商店中配置的信息统一;

2.检查产品ID是否填写错误;

3.检查是否同意App付费协议,及填写银行卡等税务信息;

        当在苹果商店配置好内购套餐后,即便不提交审核(二进制文件缺失),或者审核失败,App在沙盒环境下,仍然能获取、购买App内购套餐;


74. 简单的线程死锁

线程死锁


75.并行与串行的区别

并行与串行的区别

在子线程中启动定时器,需要使用Runloop


76.OC的动态消息机制

调用某个对象的私有方法

私有方法
NSInvocation调用方法


77.字符转换

将普通话转换为拉丁符号,且带音标,包括希伯来语、阿拉伯语、泰语等多种语言。对处理除英语以外的语言和非拉丁文字时非常有用

示例
支持的类型


78.将时间戳转换为NSDate 或 NSString (如今日0点时间戳1639929600 -> 2021-12-20 00:00:00)

将时间戳转换为NSDate 或 NSString


79.在字典A中SetValue一个从字典B转换成的字符串B‘,然后将字典A转成字符串A’导致Web端解析失败的问题

示例

    因为往字典中Set 字符串pkgStr,会自动为Value添加引号,导致最后将字典infoData转成字符串时,会出现 "webInfo":"{xxxx 的情况,导致Web解析失败。

    解决方法,直接在 infoData 中Set另一个字典 pkgInfo,不需要转成字符串后再写入

解决方案
例子

这样就不会出现 Web 解析失败的问题;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容