一间杂货铺~

10月 12

iOS微信分享及从Safari跳转到App

一般的操作流程

  1. 分享链接到微信(分享到朋友圈或分享给好友)
  2. 在微信中点击该链接后,在微信中展示网页,页面中有“App内打开”按钮
  3. 点击“App内打开”按钮后,在微信内进入到另外一个网页,页面中显示:“点击右上角的‘...’按钮,选择‘在Safari中打开’”
  4. Safari中打开后,会弹出提示:“在‘xxx’中打开链接吗?”,点击“打开”,自动跳转到应用内,并用某个页面展示内容

实现上述操作所包含的功能

配置Information Property List

在项目的Information Property List文件中,进行URL types相关配置:

  • 微信
  • 自定义Scheme
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>weixin</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>wx......</string>
        </array>
    </dict>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>xxxName</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>xxxScheme</string>
        </array>
    </dict>
</array>

微信分享

[WXApiRequestHandler sendLinkURL:xxxURLString 
                     TagName:nil
                     Title:title
                     Description:desc
                     ThumbImage:thumb
                     InScene:scene];

在网页中加入相关的跳转代码

在需要进行跳转的网页中加入:

<!-- 2秒后自动跳转到xxxScheme://xxx -->
<meta http-equiv="refresh" content="2;url=xxxScheme://xxx" />

但,这种方式会导致用户无法与页面进行交互。

如果想在用户触发网页上的某个元素时,进行跳转,就需要使用Location对象href属性assign()方法

href属性assign()方法的相关说明,见以下链接:

10月 11

使用Protocol Buffers For Objective-C

使用这个类库:https://github.com/alexeyxo/protobuf-objc,支持ARC

构建 Objective-C Protocol Buffers 编译器

步骤如下:

  1. 通过brew -v命令,检查是否已经安装了Homebrew。如果未安装,请执行ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"命令进行安装
  2. 安装Protocol Buffers编译器及必要的工具:brew install automakebrew install libtoolbrew install protobuf
  3. (可选操作)为Protocol Buffers编译器创建符号链接:ln -s /usr/local/Cellar/protobuf/2.6.1/bin/protoc /usr/local/bin
  4. 克隆该库:git clone https://github.com/alexeyxo/protobuf-objc.git
  5. 切换到该库所在目录:执行./scripts/build.sh

编译Protocol Buffers协议文件

所用命令:protoc --plugin=/usr/local/bin/protoc-gen-objc 协议文件 --objc_out="类文件输出目录"

请确保类文件输出目录已存在。

比如,当前目录下有一个名为IMSDK.proto的协议文件,通过以下命令,将生成的类文件保存到当前目录下的IMSDKPBClasses目录中:

protoc --plugin=/usr/local/bin/protoc-gen-objc ./IMSDK.proto --objc_out="./IMSDKPBClasses"

协议文件通常以proto为扩展名,但也可以使用其它扩展名,不过,使用其它扩展名,默认生成的类文件的名称就会包含这个扩展名:

协议文件 生成的类文件
IMSDK.proto Imsdk.pbobjc.h Imsdk.pbobjc.m
IMSDK.txt ImsdkTxt.pbobjc.h ImsdkTxt.pbobjc.m

生成类文件后,需要将其添加到项目中。

将类库项目添加到主项目中

以子项目的形式将该类库中的src/runtime/ProtocolBuffers.xcodeproj加入到主项目中。

因为直接加入是不会将文件复制过去的,因此,最好是在主项目所在目录下创建一个新目录,并将runtime目录下的所有内容拷贝到这个新目录中,然后,将该新目录下的ProtocolBuffers.xcodeproj拖到主项目中。

例如:

导入头文件:#import <ProtocolBuffers/ProtocolBuffers.h>

10月 11

将命令手册导出

例如,将ls命令的手册,导出到~/man_ls.txt

man ls > ~/man_ls.txt

然后,用vi打开~/man_ls.txt,内容如下:

会发现许多重复的字符,及^H(使用命令行时,在某些情况下,按下回退键会生成^H)。

为了把这些重复的字符及^H过滤掉,需要通过管道man ls的结果传递给col命令,进行过滤。col命令需要使用-b选项,其作用是,不输出任何退格,只打印最后一个写到每个列位置的字符。

man ls | col -b > ~/man_ls.txt

再次用vi打开~/man_ls.txt,内容如下:

10月 08

C语言数组与指针

举例,

int list[10] = {0};

那么,list[2]的类型为整型。

但是,list的类型是什么呢?是表示整个数组么?很遗憾,这种说法是错误的!

为什么这个说法是错误的呢?

举个例子,如果表示的是整个数组,那么,下面的例子,就表示复制整个数组:

int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a;

可是,b = a;,这个表达式明显是非法的(这里,之所以非法,原因在b身上,而非在a身上),所以,a表示的不是整个数组!

几乎所有(注意,是几乎所有,不是一个单独的所有或一个单独的全部)使用数组名的表达式中,数组名的值是一个只读指针(也就意味着,不能修改它的值,数组名不能作为可修改的左值。数组名的值,不是数组中某个元素的值)。

为什么上面说的是几乎所有呢?

当数组名作为sizeof操作符或单目操作符&的操作数时,数组名并不是指针常量。

sizeof(list),返回的是整个数组的长度,而不是指向数组的指针的长度。

&list,产生的是一个指向数组的指针,而不是一个指向指针常量值的指针。

注意上面的第二点,在多数早期版本的C语言实现中,并没有“数组的地址”这个概念,因此,&list,要么是非法的,要么就等同于list

除了优先级外,下标引用和间接访问是完全相同的!

array[subscript]等同于*(array + (subscript))

*(list + 3)等同于list[3]

(list + 3)等同于(3 + list),也等同于3[list]

下标引用可以作用于任意的指针,而不仅仅是数组名。

下标绝不会比指针更有效率,但,指针有时会比下标更有效率(前提是,它们被正确地使用)。

指针和数组并不是相等的!

举个例子,

int a[5];
int *b;

声明数组a时,编译器为a保留5int所需的连续的内存空间,然后再创建数组名a,让a指向这段内存空间的起始位置。

声明指针b时,编译器只为指针b保留内存空间,此时,指针并未指向任何内存空间。

当使用数组或指针作为函数参数时,哪个更准确呢?答案是指针,因为,实参实际上是指针,而不是数组。只传递给函数一个数组名,是无法在函数内部得知数组的长度的(得知的是指针的长度),这也就反证了,实参实际上是指针,而不是数组。

C语言中只存在一维数组,所谓的多维数组,只是多个一维数组嵌套而来的,也可以说成是仿真模拟

04月 01

JavaScriptCore框架入门

在进入主题之前,先简单了解一下主要所涉及的类

JSContext
  • JavaScript执行环境
JSValue
  • 强引用JavaScript值(这些JavaScript值可以是JavaScript函数、JavaScript变量等)
  • 绑定在一个JSContext上,绑定属于强引用

主题

  1. Objective-C调用JavaScript
  2. JavaScript调用Objective-C
  3. 内存管理
  4. 线程
  5. JavaScriptCore与WebView

1. Objective-C调用JavaScript

Objective-C调用JavaScript函数的基本步骤:

  1. 获取相关的JavaScript代码
  2. 通过JSContextevaluateScript:方法,装载JavaScript代码
  3. 通过JSContextobjectForKeyedSubscript:方法,获取已装载的JavaScript函数,获取到的JavaScript函数也就是一个JSValue对象
  4. 通过JSValuecallWithArguments:方法,调用JavaScript函数

下面通过示例,进行演示。

function.js文件中的内容如下,定义了一个函数,其名称为factorail

var factorail = function(n) {
    if (n < 0) {
        return;
    }
    if (n === 0) {
        return 1;
    }
    return n * factorail(n - 1);
}

Objective-C调用JavaScript函数:

// 获取`function.js`文件中的JavaScript代码
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"];
NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
// 创建一个JSContext
JSContext *ctx = [JSContext new];
// 装载JavaScript代码
[ctx evaluateScript:funcScript];
// 获取`factorail`JavaScript函数
JSValue *jsFunctionValue = ctx[@"factorail"];// 等同于[ctx objectForKeyedSubscript:@"factorail"];
// 调用JavaScript函数,获取返回值
JSValue *jsReturnValue = [jsFunctionValue callWithArguments:@[@10]];

2. JavaScript调用Objective-C

有两种方式,可以实现JavaScript调用Objective-C。

  • 2.1. 使用Block,实现JavaScript函数
  • 2.2. 使用JSExport协议,实现JavaScript对象

2.1. 使用Block

具体说,就是通过JSContextsetObject:forKeyedSubscript:方法,为指定的下标设置一个Block,这样就相当于在JSContext中装载了一个名称为下标名的JavaScript函数,这个函数的参数就是Block的参数,返回值类型就是Block的返回值类型。

在使用Block时,有两点需要注意:

  1. 避免捕获JSValue:应将其作为参数传递到Block中
  2. 避免捕获JSContext:应在Block中使用[JSContext currentContext]

示例,在JSContext中装载一个名为makeUIColorWithNSDictionary的JavaScript函数。

#define RGB_COMPONENT_RED   @"red"
#define RGB_COMPONENT_GREEN @"green"
#define RGB_COMPONENT_BLUE  @"blue"

JSContext *ctx = [JSContext new];
ctx[@"makeUIColorWithNSDictionary"] = ^UIColor *(NSDictionary<NSString *, NSNumber *> *rgb) {
    float r = [rgb[RGB_COMPONENT_RED] floatValue];
    float g = [rgb[RGB_COMPONENT_GREEN] floatValue];
    float b = [rgb[RGB_COMPONENT_BLUE] floatValue];
    return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
};

装载后,就可以调用该JavaScript函数了,因为该JavaScript函数是通过Objective-C实现的,在其它JavaScript函数中调用该JavaScript函数,就可以实现JavaScript调用Objective-C。

接着,在function.js文件中加入colorWithRGBDictionary(rgb)JavaScript函数,这个函数,调用了上面已经为JSContext设置的makeUIColorWithNSDictionary

function colorWithRGBDictionary(rgb) {
    if (rgb) {
        return makeUIColorWithNSDictionary(rgb);
    }
    return undefined;
}

其余的主要代码:

// 获取`function.js`文件中的JavaScript代码
NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"];
NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
// 装载JavaScript代码
[ctx evaluateScript:funcScript];
// 获取`function.js`文件中的`colorWithRGBDictionary`函数
JSValue *jsFunctionValue = ctx[@"colorWithRGBDictionary"];
// 执行`colorWithRGBDictionary`函数
NSDictionary *rgb = @{
                      RGB_COMPONENT_RED:   @(0.3),
                      RGB_COMPONENT_GREEN: @(0.5),
                      RGB_COMPONENT_BLUE:  @(0.8),
                      };
JSValue *colorValue = [jsFunctionValue callWithArguments:@[rgb]];
UIColor *color = [colorValue toObject];

2.2. 使用JSExport协议

该方式的优点:很容易让JavaScript与Objective-C对象进行交互。

JSExport协议的作用就是将Objective-C类、实例方法、类方法及属性暴露给JavaScript代码。

示例:

@class MyPoint;

@protocol MyPointExport <NSObject, JSExport>
@property (nonatomic, assign) double x;
@property (nonatomic, assign) double y;

+ (MyPoint *)makePointWithX:(double)x y:(double)y;
@end

@interface MyPoint : NSObject <MyPointExport>
@property (nonatomic, assign) double x;
@property (nonatomic, assign) double y;

+ (MyPoint *)makePointWithX:(double)x y:(double)y;
- (double)distanceBetweenOriginalPoint;
@end

@implementation MyPoint

+ (MyPoint *)makePointWithX:(double)x y:(double)y {
    MyPoint *p = [MyPoint new];
    p.x = x;
    p.y = y;
    return p;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@ - %p> : (x = %f, y = %f)", self.class, self, self.x, self.y];
}

- (double)distanceBetweenOriginalPoint {
    return sqrt(pow(self.x, 2) + pow(self.y, 2));
}

@end

在上面的代码中,自定义了一个MyPointExport协议,该协议继承了JSExport协议,在这个协议中,声明了两个属性和一个方法,因此,我们可以在JavaScript代码中使用它们。

function.js文件中加入middlePointOfTwoPoints(p1, p2)JavaScript函数,在这个函数中,调用了协议中声明的属性及方法:

function middlePointOfTwoPoints(p1, p2) {
    var x = (p1.x + p2.x) / 2.0;
    var y = (p1.y + p2.y) / 2.0;
    return Point.makePointWithXY(x, y);
}

在上面的JavaScript代码中,使用了Point类,它是与MyPoint类相关联的:

NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"functions" ofType:@"js"];
NSString *funcScript = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];

JSContext *ctx = [JSContext new];
[ctx evaluateScript:funcScript];

JSValue *functionValue = ctx[@"middlePointOfTwoPoints"];

ctx[@"Point"] = [MyPoint class];// 在调用相关JavaScript函数之前,配置相关的类

MyPoint *p1 = [MyPoint makePointWithX:100 y:100];
MyPoint *p2 = [MyPoint makePointWithX:300 y:300];

JSValue *pointValue = [functionValue callWithArguments:@[p1, p2]];
MyPoint *middlePoint = [pointValue toObject];

注意MyPoint类中的distanceBetweenOriginalPoint方法,因为该方法并没有在MyPointExport协议中进行声明,因此,在JavaScript代码中,是不能使用该方法的,否则就会产生异常。通过JSContextsetExceptionHandler:方法,可以捕获到异常:

JSContext *ctx = [JSContext new];
[ctx setExceptionHandler:^(JSContext *context, JSValue *exception) {
    NSLog(@"exception: %@", exception);
}];

3. 内存管理

  • Objective-C使用ARC(你可能还在使用MRC)
  • JavaScriptCore使用垃圾回收,所有的引用都是强引用
  • JavaScriptCore API中的内存管理,多数是自动进行管理的

不过需要特别注意两种情形:

  1. 在Objective-C对象中存储JavaScript值
  2. 向Objective-C对象中添加JavaScript域

如何避免循环引用:
先使用JSManagedValue对JavaScript值进行弱引用,然后再使用JSVirtualMachineaddManagedReference:withOwner:方法,将JSManagedValue转入“垃圾回收”引用

4. 线程

JavaScriptCore API是线程安全的,锁粒度为JSVirtualMachine,因此对于并发/并行,需要使用多个JSVirtualMachine

单个进程可以包含多个JSVirtualMachine,单个JSVirtualMachine可以包含多个JSContext,单个JSContext可以包含多个JSValue

处于同一个JSVirtualMachine中的JSValue,可以在该JSVirtualMachine中的JSContext之间进行传递,但不能传递到其它JSVirtualMachineJSContext中。

5. JavaScript与WebView

要想通过JavaScriptCore框架让web view中的JavaScript与Objective-C进行交互,关键点就是获取web view中的JSContext

获取到JSContext后,就可以装载自定义对象,以及替换原有的回调函数。

对于WebView类,通过WebFrameLoadDelegate协议的webView:didCreateJavaScriptContext:forFrame:方法,可以获取到JSContext对象。

但对于UIWebView类,苹果官方并没有公开相关的方法。不过,可以使用下面的两种方式:

JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  • 参照Mac OS X中WebFrameLoadDelegate协议的webView:didCreateJavaScriptContext:forFrame:方法,获取web view中的JSContextUIWebView-TS_JavaScriptContext
08月 04

如何创建透明的UINavigationBar

要点

使用setBackgroundImage:*相关方法:

  1. - (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics
  2. - (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarPosition:(UIBarPosition)barPosition barMetrics:(UIBarMetrics)barMetrics

并结合使用shadowImage属性:

@property(nullable, nonatomic,strong) UIImage *shadowImage

通过上述方法及属性,将背景图片阴影图片设置为透明图片,就能达到目的。

如果手头上没有透明图片,可以引入UIImage类别,通过代码,生成透明图片。例如:

UIImage+INB.h

#import <UIKit/UIKit.h>

@interface UIImage (INB)
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size cornerRadius:(CGFloat)radius;
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size;
+ (UIImage *)imageWithColor:(UIColor *)color;
@end

UIImage+INB.m

#import "UIImage+INB.h"

@implementation UIImage (INB)
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size cornerRadius:(CGFloat)radius {
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSaveGState(ctx);
    if (radius > 0.0f && radius <= size.width && radius <= size.height) {
        UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, size.width, size.height) cornerRadius:radius];
        [color setFill];
        [path fill];
    } else {
        CGContextSetFillColorWithColor(ctx, color.CGColor);
        CGContextFillRect(ctx, CGRectMake(0, 0, size.width, size.height));
    }
    CGContextRestoreGState(ctx);
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}

+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
    return [UIImage imageWithColor:color size:size cornerRadius:0.0f];
}

+ (UIImage *)imageWithColor:(UIColor *)color {
    return [UIImage imageWithColor:color size:CGSizeMake(1, 1)];
}
@end

注意,通过上面的方法创建图片时,UIKIT_EXTERN void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)函数的opaque参数一定要设置为NO,只有这样,图片的alpha通道才会起作用,进而才能创建出透明图片。

先看看初步的代码及效果:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    UINavigationBar *navBar = self.navigationController.navigationBar;
    UIImage *bgImg = [UIImage imageWithColor:[UIColor redColor]];
    [navBar setBackgroundImage:bgImg forBarMetrics:UIBarMetricsDefault];
}

transparent_nav_bar_320_568_1.png

现在将背景图片设置为透明图片:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    UINavigationBar *navBar = self.navigationController.navigationBar;
    UIColor *transparentColor = [UIColor colorWithWhite:0 alpha:0];
    UIImage *transparentImg = [UIImage imageWithColor:transparentColor];
    [navBar setBackgroundImage:transparentImg forBarMetrics:UIBarMetricsDefault];
}

transparent_nav_bar_320_568_2.png

可以看到,UINavigationBar的底部会有一条“黑线”,这条“黑线”其实是一个UIImageView

请看Debug View Hierarchy的结果:

transparent_nav_bar_bottom_img_view.png

现在,只要将这条“黑线”去掉或隐藏掉就能达到最终的目的。做成看起来像是去掉或隐藏掉也是可以的。

方法比较多。例如使用navBar.layer.masksToBounds = YES,但将这种方式用在此处并不是我所建议的。确切地说,此处的“黑线”就是shadowImage所产生的效果。

修改一下代码,就可以看出来:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    UINavigationBar *navBar = self.navigationController.navigationBar;
    UIColor *transparentColor = [UIColor colorWithWhite:0 alpha:0];
    UIImage *transparentImg = [UIImage imageWithColor:transparentColor];
    [navBar setBackgroundImage:transparentImg forBarMetrics:UIBarMetricsDefault];
    UIImage *shadowImg = [UIImage imageWithColor:[UIColor redColor] size:CGSizeMake(1, 20)];
    navBar.shadowImage = shadowImg;
}

其效果如下:

transparent_nav_bar_320_568_3.png

Debug View Hierarchy的结果如下:

transparent_nav_bar_bottom_shadow_img_view.png

明白了这一点,将shadowImage设置为之前所生成的transparentImg透明图片即可。

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    UINavigationBar *navBar = self.navigationController.navigationBar;
    UIColor *transparentColor = [UIColor colorWithWhite:0 alpha:0];
    UIImage *transparentImg = [UIImage imageWithColor:transparentColor];
    [navBar setBackgroundImage:transparentImg forBarMetrics:UIBarMetricsDefault];
    navBar.shadowImage = transparentImg;
}