2020年iOS面试题及答案

问题来源:链接:https://juejin.im/post/5e75aba6e51d4526d71d6558

1,分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?

①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime能添加属性的的原因只是通过runtime的objc_setAssociatedObject和objc_getAssociatedObject方法解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

最重要的还是类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
分类方法未实现,编译器也不会报警告。
分类方法与原类中相同会优先调用分类。

分类的结构体

typedef struct objc_category *Category;
struct objc_category {
  char *category_name                          OBJC2_UNAVAILABLE; // 分类名
  char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
  struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
  struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
  struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

2,讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?

atomic是在setter和getter方法里会使用自旋锁spinlock_t来保证setter方法和getter方法的线程的安全。可以看做是getter方法获取到返回值之前不会执行setter方法里的赋值代码。如果不加atomic,可能在getter方法读取的过程中,再别的线成立发生setter操作,从而出现异常值。

加上atomic后,setter和getter方法是线程安全的,原子性的,但是出了getter方法和setter方法后就不能保证线程安全了

@property (atomic, strong) NSArray*                arr;
//thread A
for (int i = 0; i < 10000; i ++) {
    if (i % 2 == 0) {
        self.arr = @[@"1", @"2", @"3"];
    }
    else {
        self.arr = @[@"1"];
    }
}

//thread B
for (int i = 0; i < 100000; i ++) {
    if (self.arr.count >= 2) {
        NSString* str = [self.arr objectAtIndex:1];
    }
}

上面的例子线程B里面可能会因为数组越界而引起crash,因为加入在B线程里判断self.arr.count >= 2的时候数组是self.arr = @[@“1”, @“2”, @“3”];但是当调用[self.arr objectAtIndex:1]可能self.arr的值已经在线程A里被更改为了@[@“1”],此时数组越界了。因此,虽然self.arr是atomic的,还是会出现线程安全问题。

3,被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
被weak修饰的对象在被释放时候会置为nil,不同于assign;

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

struct SideTable {
    // 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

4,Autoreleasepool 所使用的数据结构是什么? AutoreleasePoolPage 结构体了解么?

Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么

6,iOS 中内省的几个方法? class 方法和 objc_getClass 方法有什么区别?

1.当参数obj为Object实例对象
object_getClass(obj)与[obj class]输出结果一直,均获得isa指针,即指向类对象的指针。

2.当参数obj为Class类对象
object_getClass(obj)返回类对象中的isa指针,即指向元类对象的指针;[obj class]返回的则是其本身。

3.当参数obj为Metaclass类对象
object_getClass(obj)返回元类对象中的isa指针,因为元类对象的isa指针指向根类,所有返回的是根类对象的地址指针;[obj class]返回的则是其本身。

4.obj为Rootclass类对象
object_getClass(obj)返回根类对象中的isa指针,因为跟类对象的isa指针指向Rootclass‘s metaclass(根元类),即返回的是根元类的地址指针;[obj class]返回的则是其本身。

总结:
经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

7,RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)
Runloop是什么?
深入理解RunLoop | Garan no dou

字面意思是“消息循环、运行循环”,runloop内部实际上就是一个do-while循环,它在循环监听着各种事件源、消息,对他们进行管理并分发给线程来执行。

1.通知观察者将要进入运行循环。
线程和 RunLoop 之间是一一对应的

2.通知观察者将要处理计时器。

3.通知观察者任何非基于端口的输入源即将触发。

4.触发任何准备触发的基于非端口的输入源。

5.如果基于端口的输入源准备就绪并等待触发,请立即处理该事件。转到第9步。

6.通知观察者线程即将睡眠。

7.将线程置于睡眠状态,直到发生以下事件之一:

事件到达基于端口的输入源。
计时器运行。
为运行循环设置的超时值到期。
运行循环被明确唤醒。

8.通知观察者线程被唤醒。

9.处理待处理事件。

如果触发了用户定义的计时器,则处理计时器事件并重新启动循环。转到第2步。
如果输入源被触发,则传递事件。
如果运行循环被明确唤醒但尚未超时,请重新启动循环。转到第2步。

10.通知观察者运行循环已退出。

8:苹果是如何实现 autoreleasepool的?

arc下编译器会优化成
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
复制代码
向一个结构AutoreleasePoolPage,中写入需要自动释放的对象,类似一种标记,调用objc_autoreleasePoolPop(context)后,就会把这中间的对象release一下。
这里要注意的是,方法返回值是怎么做到自动释放的?
其使用Thread Local Storage(TLS)线程局部存储,每次存入线程或者从线程取出来。
我们没有卸载{}中的自动释放对象,会在每个runloop结束时候去释放,相当于一个大的autoreleasepool中。
参考文章
苹果是如何实现autoreleasepool的

8,哪些场景可以触发离屏渲染?(知道多少说多少)

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等
  • 渐变

3人点赞日记本

作者:在ios写bug的杰克
链接:https://www.jianshu.com/p/061d76449e2a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

【iOS】苹果IAP(内购)中沙盒账号使用注意事项

目标

  • 沙盒账号的正确使用方式
  • 沙盒账号使用的注意事项

1.沙盒账号是什么

iOS应用里面用到了苹果应用内付费(IAP)功能,在项目上线前一定要进行功能测试。测试肯定是需要的,何况这个跟money有关。。。开发完成了之后,如何进行测试呢?难道我测试个内购功能要自己掏钱?就算是公司掏钱,但是苹果要吃掉3成的啊,想想如果是99刀的商品,点下购买的时候心里都有点发慌。。。
苹果当然没这么坑了,测试内购,苹果提供了沙盒账号(也叫沙箱账号)的方式。这个沙箱账号其实是虚拟的AppleID,在开发者账号后台的iTune Connect上配置了之后就能使用沙盒账号测试内购,有了沙盒账号,就能体验一把土豪的感觉了,游戏钻石什么的随便充,反正不用我的钱。

注意:你可以把沙盒账号看做是一个虚拟的AppleID,这个AppleID只有进行内购测试的功能。

2.沙盒账号使用的前提

  • bundleID别搞错了,开发者账号、证书、bundleID要一致
  • 内购的商品ID,价格等相关信息已经录入到开发者后台了(不然那你买什么)
  • 开发者后台已经创建好沙盒测试账号了(下面我们会讲如何创建)
  • 你要有一部真机(iPhone或iPad都行,别用模拟器就好。而且不能是越狱机)
  • 如果你是第一次在这个开发者账号上集成内购功能,请先将iTune Connect上的税务协议都填写好,否则内购时会发现商品ID无效。

税务协议的问题不少童鞋遇到了,可以参考这篇文章http://www.jianshu.com/u/62f0c72a2004

3.沙盒账号创建

登录苹果开发者后台–iTunes Connect–用户和职能–沙箱测试技术员,在这个界面你可以看到当前账号已经创建好的沙盒账号。

沙箱测试技术员管理界面

点击“+”进行创建

创建沙盒账号

新创建的沙盒账号

如图,我创建一个沙盒账号。具体信息:

  • 名字为test1
  • AppleID为jianshutest1@123.com(也就是上面填的电子邮件)
  • App Store地区为中国

注意:

  • 电子邮件不能是别人已经注册过AppleID的邮箱
  • 电子邮箱可以是一个不存在电子邮箱(只要符合格式,随便写)
  • App Store 地区不要乱选。虽然随便哪个地区都可以用来测试(还没上线之前app并没有地区之分),但是在沙盒测试的时候,弹出的购买提示框会根据当前AppleID(沙盒账号)的地区显示语言的。

4.沙盒账号使用流程

  • 1.在iPhone上安装测试包(必须是adhoc签名证书或者develop签名证书打的包,不能是从App Store上下载的)
  • 2.退出iPhone的App Store账号(因为我们需要使用沙盒账号登录)。
    • 操作方法一:打开App Store应用首页滑到最下方–选中AppleID–注销
    • 操作方法二:设置–iTunes Store与App Store–选中AppleID–注销

这里只需要退出账号,退出之后,不需要在这里登录沙盒账号,因为你压根就登录不了。之前已经说过了,沙盒账号是一个假的AppleID账号,不能直接登录的。如果强行登陆,会出现以下报错提示:

使用沙盒账号直接登录报错提示

  • 3.在测试包里面购买商品,系统会让你进行登录,这里我们点击“使用现有的AppleID”就可以输入刚才创建好的沙盒测试账号进行登录了。

点击购买商品后出现登录窗口

输入沙盒账号密码进行登录

  • 4.输入账号之后,有可能会出现如下提示,点击确定之后会跳转到App Store,导致这次购买失败。没关系,我们再次回到测试包,然后购买商品就好

跳转到App Store跳转店面的提示

出现提示的原因:因为AppleID是分地区的。之前我们创建沙盒账号的时候就看到了,需要选择地区。App Store也是分地区的,对应的AppleID只能在App Store对应的地区进行下载和购买东西。我们刚才创建的jianshutest1@123.com这个账号的地区是中国,所以只能在中国店面登录。由于我之前的登录的账号越南的,所以此时AppStore店面是越南店面。所以我们这次登录,系统会跳转到AppStore应用将店面切换到中国。另外,App Store应用切换地区的时候,会报【Your request produced an error】。这个不需要管。

  • 5.点击购买商品之后,成功的话会出现相应提示。

商品内容和价格展示窗口

备注:我们在iTunes Connect上创建商品了之后,除了需要填商品ID,商品名称,商品描述,价格等之外,还要上传一张图片,图片就是上面这个界面。

购买成功提示

沙盒账号注意事项

  • BudleID,证书,商品ID等内容一致,才能进行接下来的储值测试(BundleID都不对,还玩什么)
  • 测试设备需要使用不越狱的真机(越狱机不能进行沙盒储值,模拟器也不能进行沙盒储值)
  • 沙盒账号是不能直接在App Store进行登录的,只能在点击了购买商品之后,在弹出的登录框进行登录。
  • 真实的AppleID不能在adhoc证书和develop证书打出来的包进行沙盒储值测试,所以在沙盒测试之前,需要退出真实的AppleID账号
  • 从App Store上面下载的包不能使用沙盒账号进行储值

关于证书的问题:
1.使用develop签名证书和adhoc签名证书打的ipa包,我把他们叫做测试包,测试包只能使用沙盒账号进行储值,不能使用真实的AppleID进行储值
2.从App Store应用下载的包,我把他们叫做线上包,线上包只能使用真实的AppleID进行储值,不能使用沙盒账号进行储值

另外唠叨一下:
楼主平常上传包的时候是打包了ipa包之后,使用Xcode里面的Application Loader应用上传ipa包的。
虽然很多人上传包使用的是appstore的签名证书,但是,其实使用adhoc的证书打包的ipa包也是可以正常上传并且送审上线的。我平常就是用adhoc的证书打包成ipa包,给测试妹子测试,测试完直接用这个包上传送审了。嘿嘿。
2020.06.28更新:最近苹果App Store Connect改版,不能使用adhoc描述证书的包送审了,真是个悲伤的故事。。

作者:谦言忘语
链接:https://www.jianshu.com/p/1ef61a785508
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

iOS WebView和JS的交互

前言

现在web前端发展越来越快,为了追求应用的快速开发和迭代,许多产品都会选择混合开发,在手机端嵌入web页面,那么这就会导致一个问题,
原生代码怎么和js交互?那么下边我们共同学习一下iOS和web是怎么交互的。

概述

  1. UIWebViewJavaScriptCore的结合使用。但是我们要知道JavaScriptCore这个框架是在iOS7之后官方才发布。
  2. iOS8之后的WKWebView的使用
  3. 请求路径拦截的使用(WebViewJavascriptBridge第三方库)

我们还需要先了解一下JavaScriptCore框架的基本使用

基本使用

1、UIWebView和js的交互

OC调用js

使用UIWebviewstringByEvaluatingJavaScriptFromString方法执行js代码,或注入js代码

js调用OC

1、使用UIWebview想要js调用OC代码我们只能通过拦截的方式,代码如下:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *url = request.URL.absoluteString;
    if ([url rangeOfString:@"myProtocol://"
         ].location != NSNotFound) {
        NSLog(@"拦截成功了");
        return NO;
    }
    return YES;
}

2、 在iOS7之后我们可以使用JavaScriptCore进行和js的通信

使用方式

先创建一个JSProtocol协议,遵循JSExport协议,添加一个打印方法


@protocol JSProtocol <JSExport>

-(void)print:(NSString *)str;

@end

创建一个MyJSObject对象,遵循JSProtocol,实现print方法

-(void)print:(NSString *)str
{
    NSLog(@"log=%@",str);
}

然后当web页面加载完毕的时候,通过[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]获取JSContext对象

OC代码如下:

-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
    
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    NSLog(@"context=%@",context);
    self.context = context;
    // 为js增加log方法
    context[@"log"] = ^(NSString *str){
        NSLog(@"log:%@",str);
    };
    // 为js增加obj对象 遵循该协议jsExport
    MyJSObject *obj = [[MyJSObject alloc] init];
    context[@"obj"] = obj;
    // 为js增加changeLabel的函数
    context[@"changeLabel"] = ^(NSString *str){
        self.text.text = str;
    };
    
    context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        NSLog(@"异常%@",exception);
    };
    
}

html代码

<input name="name" type="text" id="name"/>
<button onclick="changeText();">改变Label值</button>
<button onclick="printLog();">webView的js条用native(输出日志)</button>
<script type="text/javascript">
    function changeText() {
        var value = document.getElementById("name").value;
        log("输入的值="+value);
        changeLabel(value);
    }
    function printLog(str){
        // log() ,obj 是 OC提供的,执行是在ios端
        log("oc已经调用function");
        log("oc注入的方法");
        log("oc注入的obj对象"+ obj.print);
        obj.print(str);
    }
</script>

这里通过JSContextjs增加了logchangeLabel全局方法,挂载到window对象下边。当js调用log或者changeLabel方法时会执行block内的代码,当传入相应的参数,就可以达到jsOC传递参数和交互。

效果图

2、WKWebView和js的交互

WKWebViewiOS8之后才有,所以如果你的app最低支持iOS可以尽情使用。

OC调用js

使用WKWebViewevaluateJavaScript方法执行js代码,或注入js代码

js调用OC

要想WKWebViewjs交互,需要用到WKUserContentController,下面我们来看一下如何初始化WKWebView

    // 创建配置对象
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    
    // 为WKWebViewConfiguration设置偏好设置
    WKPreferences *preferences = [[WKPreferences alloc] init];
    configuration.preferences = preferences;
    
    // 允许native和js交互
    preferences.javaScriptEnabled = YES;
    
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    [userContentController addScriptMessageHandler:self name:@"event"];
    configuration.userContentController = userContentController;
    self.userContentController = userContentController;
    
    // 初始化webview
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 64, self.view.frame.size.width, 300) configuration:configuration];
    // 此处替换你本机的ip
    NSURL *url = [NSURL URLWithString:@"http://172.26.233.156:3001/wkWebView"];
    webView.navigationDelegate = self;
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [webView loadRequest:request];
    [self.view addSubview:webView];
    self.webView = webView;

首先我们先创建WKWebViewConfiguration配置对象,然后创建WKUserContentController对象,然后我们使用WKUserContentControlleraddScriptMessageHandler
注册要接收消息的名称,这样我们才可以在js代码中通过window.webkit.messageHandlers.名称.postMessage发送消息,那么OC怎么接收消息呢?

下边是接收消息的代理

// 接受js发送的消息
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    NSLog(@"%@,%@",message.name,message.body);
    NSString *name = message.name;
    if ([name isEqualToString:@"event"]) {
        // 打开相册
        if ([message.body isEqualToString:@"打开相册"]) {
            UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
            imagePickerController.delegate = self;
            [self.navigationController presentViewController:imagePickerController animated:YES completion:nil];
        }else {
            
        }
    }
}

一定要记得在dealloc方法移除监听名称

[self.userContentController removeScriptMessageHandlerForName:@"event"];

html代码

<button onclick="openAlbum();">打开相册</button>
<script type="text/javascript">
    function openAlbum(){
        window.webkit.messageHandlers.event.postMessage("打开相册");
    }
    function testLog(str) {
        window.webkit.messageHandlers.event.postMessage(str);
        return "123"
    }
</script>

效果图

源码

源码

这里边有两个工程,一个是iOS工程,一个是node工程,用来创建一个web服务器模拟加载远程的web项目

WebViewInteractiveIOS 目录是iOS
WebViewInteractiveWeb web项目

运行

iOS运行这里不多赘述,需要注意的是把webview加载的ip换成本机ip

web运行,执行如下命令,前提是装了node环境的机器

cd WebViewInteractiveWeb
npm install 
npm run server

3、 JSBridge的使用

源码

iOS端webview和jsbridge的使用实现

首先创建一个iOS工程,引入WebViewJavascriptBridge第三方框架,使用pod还是自己手动导入都可以

下面看一下具体实现

创建WKWebview,初始化WebViewJavascriptBridge,设置WebViewJavascriptBridge,WKWebView的代理

// 初始化webview
    WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
    // 此处替换你本机的ip
    NSURL *url = [NSURL URLWithString:kHOSTURL];
    webView.navigationDelegate = self;
    webView.UIDelegate = self;
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
    [webView loadRequest:request];
    [self.view addSubview:webView];
//  self.webView = webView;
    
    self.view.backgroundColor = [UIColor whiteColor];
    // 开启日志
    [WebViewJavascriptBridge enableLogging];
    
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:webView];
    [self.bridge setWebViewDelegate:self];
前端js手动注册和获取JSBridge

/*这段代码是固定的,必须要放到js中*/
function setupWebViewJavascriptBridge(callback) {
    if (window.WebViewJavascriptBridge) {
        return callback(WebViewJavascriptBridge);
    }
    if (window.WVJBCallbacks) {
        return window.WVJBCallbacks.push(callback);
    }
    window.WVJBCallbacks = [callback];
    var WVJBIframe = document.createElement('iframe');
    WVJBIframe.style.display = 'none';
    WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() {
        document.documentElement.removeChild(WVJBIframe)
    }, 0)
}
// 初始化jsbridge
setupWebViewJavascriptBridge(function(bridge) {
    global.bridge = bridge;// 获取到jsbridge;
})
OC调用js

首先需要js注册交互

this.$bridge.registerhandler('ocCallJs', (data, responseCallback) => {
        alert(data)
        responseCallback(data)
    });

iOS端需要WebViewJavascriptBridge使用callHandler调用js注册的方法

[self.bridge callHandler:@"ocCallJs" data:@"123" responseCallback:^(id responseData) {
        NSLog(@"responseData=%@",responseData);
    }];

js调用OC

需要iOS注册一个交互以供js回调

[self.bridge registerHandler:@"jsCallOc" handler:^(id data, WVJBResponseCallback responseCallback) {
        //将base64字符串转为NSData
        NSData *decodeData = [[NSData alloc]initWithBase64EncodedString:data options:NSDataBase64DecodingIgnoreUnknownCharacters];
        //将NSData转为UIImage
        UIImage *decodedImage = [UIImage imageWithData: decodeData];
        TwoController *twoCtr = [[TwoController alloc] init];
        twoCtr.view.backgroundColor = [UIColor purpleColor];
        [self.navigationController pushViewController:twoCtr animated:YES];
        twoCtr.image = decodedImage;
        
        if (responseCallback) {
            // 反馈给JS
            responseCallback(@{@"jsCallOc的参数1": @"jsCallOc的参数2"});
        }
    }];

js调用交互

this.$bridge.callhandler("jsCallOc",image.replace("data:image/jpeg;base64,",""),(responseData)=>{
          console.log("responseData",responseData);
        })

效果图

1.gif

作者:小猿_Luck_Boy
链接:https://www.jianshu.com/p/3fea43b882eb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

IOS WebView中JS和OC相互调用

现在有很多项目中都涉及到HTML5,在一个普通的APP里面嵌入HTML5网页,嵌入的这些网页在服务器把页面修改之后同样也能跟着修改,这样就能很方便的修改程序的展示内容。如果使用tabelView或者其他的控件展示内容的话,当展示的内容的样式需要发生改变的时候必须修改程序本身才能够达到修改的效果。所以,在APP里面HTML5的嵌入可以更加方便的维护程序的内容,但是由于现在受网速啊,手机性能等因素的影响导致HTML5的用户体验没有系统原生代码修改的APP好,所以针对这个JS和OC的项目调用,这里只是简单的讲讲,方便大家以后的学习和使用。


1.JS调OC

  在JS调用OC的时候,其实我使用的是取巧的办法,当我们使用webView加载网页的时候,每一次请求都会被webView的

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

代理方法捕捉到,然后我们就可以在该代理方法里面进行相应的操作,这里我们先看看我写的一个简单测试代码,首先看测试网页的源代码:

<html>
    <head lang="en">
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <br/><br/>
    <button onclick="fn_call();">打电话</button>
    <br/><br/>
    <button onclick="fn_getmethon();">获取参数</button>
    <script type="text/javascript">
    function fn_call(){
        window.location.href='sn://call';
    }
    function fn_getmethon(){
        window.location.href='sn://getValue';
    }
    function fn_alertMsg(msg){
        alert(msg);
    }
    </script>
    </body>
</html>

在这个页面里面,有两个按钮,按钮点击之后分别调用两个方法,在方法里面使用window.location.href='sn://getValue'发送一个请求,但是这个请求的协议不是http所以页面不会进行跳转;接着我们在webView的代理方法里面捕捉事件。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *url = request.URL.absoluteString;
    NSLog(@"打印请求的URL-->%@", url);
    NSRange range = [url rangeOfString:@"sn://"];
    NSUInteger location = range.location;
    if (location != NSNotFound) {
        NSString *methon = [url substringFromIndex:location + range.length];
        SEL fn = NSSelectorFromString(methon);
        [self performSelector:fn withObject:nil];
    }

    return YES;
}

- (void)call {
    NSLog(@"调用打电话");
}
这里写图片描述

当我们按了”打电话”这个按钮之后,控制台就会出现

这里我们就可以对得到的URL进行简单的截取啊,判断等操作,然后再执行需要执行的OC代码,我这里是调用了call()这个方法,这样就完成了JS对OC的调用。

2.OC调JS

  前面讲了JS对OC的调用,在这里OC调用JS的原理其实差不多,在最开始的html页面里面有一个”获取值”的按钮点击之后就会调用js方法,然后在OC里面捕捉到对应的方法

- (void)getValue {
    NSLog(@"调用获取值");
    NSMutableString *js = [NSMutableString string];
    

[js appendString:[NSString stringWithFormat:@“fn_alertMsg(‘%@’);”, @“这是测试传值”]

]; [self.webView stringByEvaluatingJavaScriptFromString:js]; }

执行结果

这这个方面里面我们主要记住要在webView执行js代码就要调用- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;这个方法,我们只需要传入需要执行的JS代码然后就可以在webView里面执行。先看看执行的效果:
通过上面提到的的代码就完成了OC对JS的调用,在用OC对JS调用的时候我们要先测试JS代码的正确性,这样能提高我们的开发效率。

  好了,webView中JS和OC调用,以及OC和JS的调用就写到这里了,有不足的地方希望大家多提提,最后附上完整的代码,webView控件使用故事版拖的

@interface ViewController () <UIWebViewDelegate>
@property (strong, nonatomic) IBOutlet UIWebView *webView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:@"test.html"]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    self.webView.delegate = self;
    [self.webView loadRequest:request];
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *url = request.URL.absoluteString;
    NSLog(@"打印请求的URL-->%@", url);
    NSRange range = [url rangeOfString:@"sn://"];
    NSUInteger location = range.location;
    if (location != NSNotFound) {
        NSString *methon = [url substringFromIndex:location + range.length];
        SEL fn = NSSelectorFromString(methon);
        [self performSelector:fn withObject:nil];
    }

    return YES;
}

- (void)getValue {
    NSLog(@"调用获取值");
    NSMutableString *js = [NSMutableString string];
    

[js appendString:[NSString stringWithFormat:@“fn_alertMsg(‘%@’);”, @“这是测试传值”]

]; [self.webView stringByEvaluatingJavaScriptFromString:js]; } – (void)call { NSLog(@”调用打电话”); } @end

iOS开发:Sign In With Apple(使用Apple登录)

1、什么是Sign In With Apple

苹果在WWDC2019推出了Sign In With Apple(以下简称SIWA),即用户通过端上的Apple ID就可以登录第三方应用。目的是为用户提供一种快速,安全且隐私友好的方式来设置帐户并开始使用您的服务。这次苹果提供的Sign In With Apple快捷登录,也是通过这个跨平台的功能使得在这个生态环境中的应用操作更加方便简洁。详情请参考:WWDC2019 Sign In With Apple 视频

官方文档介绍:https://developer.apple.com/cn/sign-in-with-apple/get-started/

https://developer.apple.com/cn/news/?id=09122019b

通过视频观看,苹果反复的阐述安全性,包括本身Apple ID具备的双重认证、用户可以隐藏自己的Apple ID等,这些都使得用户隐私得到了最大的保护,虽然我们依然可以通过API来获取到诸如用户姓名这样的信息,但是最为关键的user则是毫无规律可言的(至少在我们开发者看来),而这个user则为同一个开发者账号下的所有app中保持有且仅有一个,这个特点看上去能够和已有的帐号体系打通。

Sign In With Apple特点:

Streamlined account setup(简化帐户设置)

Verified email addresses(验证电子邮件地址)

Built-in security(内置安全性)

Anti-fraud(反欺诈):包括:

Cross-platform(跨平台):支持苹果生态的所有系统。eg: iOS,macOS, watchOS, tvOS, JavaScript等。

接入Sign In With Apple的好处:应用程序或网站中显示“使用Apple登录”按钮意味着人们可以使用他们已有的Apple ID进行点击或登录,而无需填写表格,验证电子邮件地址和选择密码。使用Apple登录提供了一种新的,更私密的方式,可以简单快速地登录应用程序和网站,同时为人们提供他们可以信赖的一致登录体验,以及不必记住多个帐户和密码的便利。如果您选择询问姓名和电子邮件地址,人们可以选择保留其电子邮件地址的私密性,而共享一个唯一的随机电子邮件地址。有关开发人员的指导,请参阅AuthenticationServices

2、如何接入Sign In With Apple

2.1:系统配置:苹果开发文档明确规定:苹果电脑系统macos(10.15)(吓的我赶紧升级我的笔记本系统),手机iOS系统ios(13.0)(毫不犹豫的升级)。由于只有Xcode11才支持Sign In With Apple接入代码的开发,所以Xcode我升级到了11.1正式版。

上面三个条件缺一不可。

开发文档规定的系统版本号

2.2:工程配置

2.2.1按照下图所示

工程配置顺序

2.2.2:点击 “+”号,在弹出的框里搜索 Sign In With Apple,然后双击菜单中的 Sign In With Appple,添加到工程中。

搜索Sign In With Apple

2.2.3:添加成功Sign In With Apple,如下图所示

添加成功所示

2.2.4:如果项目中没有 AuthenticationServices.framework ,请务必添加 AuthenticationServices.framework 。

添加系统支持的framework

2.3:集成Sign In With Apple 流程

集成苹果ID登录流程

3、集成Sign In With Apple

3.1:集成SIWA,一般用到的是下面几个类:

Sign In with Apple Entitlement:一种权利,可让您的应用程序使用“通过Apple登录”。Key: com.apple.developer.applesignin

class  ASAuthorizationAppleIDProvider:一种用于基于用户的Apple ID生成用于验证用户身份的请求的机制。

class  ASAuthorizationController:一个控制器,用于管理提供者创建的授权请求。

class  ASAuthorizationAppleIDCredential:成功的Apple ID身份验证产生的凭证。

class ASAuthorizationAppleIDButton:苹果封装的关于SIWA的按钮类。

calss ASPasswordCredential:密码认证管理类

3.2:集成SIWA开发

主要通过使用#import <AuthenticationServices/AuthenticationServices.h>框架来实现整个的苹果ID登录流程。而核心的类是ASAuthorizationAppleIDProvider,它是来创建和发起苹果ID登录请求的提供者。

3.2.1:SIWA按钮

SIWA按钮实现的代码

3.2.2按钮的点击实现:

点击按钮实现的功能

3.2.3:授权代理方法:

3.2.3.1:授权成功的方法:

– (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization*)authorization  API_AVAILABLE(ios(13.0))

授权成功

3.2.3.1:授权失败的方法:

– (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError*)error  API_AVAILABLE(ios(13.0))

授权失败

3.2.4:授权页面显示

初次授权和已经授权再次点击时所展现出的界面是不一样的,如下图所示:左边图片是初次登陆页面展示,右边图片是再次登陆页面展示

授权登录页面展示

弹出页面后,按页面所示进一步操作即可。

在初次授权中,姓名这一栏是可以自己修改的,点击<姓名>这一栏,就可以根据需要修改

电子邮件:

可以选择显示或者隐藏,也可以选择其他的邮箱,点击共享我的电子邮件,可以弹出你自己所有的APPID邮箱账号,你可以选择其他的账号。

如果你选择<共享我的电子邮件>,那么在授权成功后,你会拿到真实的邮箱:

真实的邮箱

如果你选择<隐藏邮件地址>,那么在授权成功后,你会拿到一个经过处理后的混乱的邮箱:

混乱的邮箱

相关示例代码,请点击这里

4、Sign In With Apple按钮样式

样式介绍:官方介绍

4.1:Sign In with Apple 提供了两种按钮样式变种:【Sign In with Apple】 和 【Continue with Apple】。根据需要,选择最适合自己服务的登录场景所需要的样式。

俩种按钮样式

4.2:根据平台的不同,该系统最多可以为使用Apple登录按钮提供三种外观:白色(white),带有轮廓的白色(white with an outline)和黑色( black)。选择最适合您在其上显示按钮的背景的外观。

4.2.1:白色:

白色样式在iOS,macOS,tvOS和Web中可用。在提供足够对比度的深色或彩色背景上使用此样式。

白色样式

4.2.2:白色带轮廓

白色轮廓样式可在iOS,macOS和Web中使用。在无法提供足够对比度的白色或浅色背景上使用此样式;不要在黑暗或饱和的背景上使用它。

白色带轮廓

4.2.3:黑色

黑色样式适用于所有平台和网络。在可提供足够对比度的白色或浅色背景上使用此样式;不要在黑色或深色背景上使用它。

黑色

4.3:按钮尺寸和位置

4.3.1:突出显示“使用Apple登录”按钮。使“使用Apple登录”按钮与其他登录按钮的大小相同,并避免使人们滚动查看该按钮。

4.3.2:调整拐角半径以匹配应用程序中其他按钮的外观。默认情况下,“使用Apple登录”按钮具有圆角。在iOS,macOS和网络中,您可以更改转角半径以产生带有方形角的按钮或弧形形的按钮。有关开发人员的指导,请参阅cornerRadius(iOS和macOS)和“使用Apple Buttons显示和配置登录”(Web)。

按钮样式所示

4.3.3:保持最小的按钮大小和按钮周围的边距。请注意,按钮标题的长度可能会因地区而异。使用以下值作为指导:

按钮尺寸参考

5、关于使用Sign In With Apple审核的问题

关于苹果对Sign In With Apple适配审核的规则,官方文档说明 第4.8条

Apps that exclusively use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option. A user’s primary account is the account they establish with your app for the purposes of identifying themselves, signing in, and accessing your features and associated services.

仅使用第三方或社交登录服务(例如Facebook登录,Google登录,Twitter登录,LinkedIn登录,Amazon登录或微信登录)的应用来设置或验证用户的主帐户该应用程序还必须提供“与Apple登录”作为等效选项。用户的主要帐户是他们在您的应用中建立的帐户,用于识别自己的身份,登录并访问您的功能和相关服务。

如果是下面这类类型的应用则不需要添加:

您的应用专门使用公司自己的帐户设置和登录系统。

您的应用是教育,企业或商业应用,要求用户使用现有的教育或企业帐户登录。

您的应用程序使用政府或行业支持的公民身份识别系统或电子ID来对用户进行身份验证。

您的应用是特定第三方服务的客户端,要求用户直接登录其邮件,社交媒体或其他第三方帐户才能访问其内容

另外需要注意,关于何时接入Sign In With Apple,苹果在有关“通过 Apple 登录”的指南更新有说明:

We’ve updated the App Store Review Guidelines to provide criteria for when apps are required to use Sign in with Apple. Starting today, new apps submitted to the App Store must follow these guidelines. Existing apps and app updates must follow them by April 2020. We’ve also provided new guidelines for using Sign in with Apple on the web and other platforms.

我们更新了《 App Store审查指南》,为何时需要应用程序使用“使用Apple登录”提供了标准。从今天开始,提交到App Store的新应用必须遵循这些准则现有应用程序和应用程序更新必须在2020年4月之前进行。我们还提供了有关在网络和其他平台上使用Apple登录功能的新指南。

查看《App Store 审核指南》 (英文)

6、关于Sign In With Apple登录后和服务器交互的问题,

Sign In With Apple 从登陆到服务器验证

Sign in with Apple(苹果授权登陆)

快速配置 Sign In with Apple

苹果官方文档之生成并验证token

7、附录:

苹果最新更新:https://developer.apple.com/news/

APP添加apple登录https://developer.apple.com/documentation/authenticationservices/adding_the_sign_in_with_apple_flow_to_your_app

作者:狂奔的蜗牛_
链接:https://www.jianshu.com/p/efb02bc8935a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。