移动周分享-第51期

Java方法参数过多 - 吴明

  • 示例方法
1
2
3
4
5
6
7
8
9
10
11
public void getNews(Context context,
Callback callback,
String uuid,
String uid,
String from,
String token,
String uid,
String .....){

......
}
  • 示例构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Person {
public String lastName;
public String firstName;
public String middleName;
public String salutation;
public String suffix;
public String streetAddress;
public String city;
public String state;
public boolean isFemale;
public boolean isEmployed;
public boolean isHomeOwner;
public Person(String lastName, String firstName, String middleName, String salutation,
String suffix, String streetAddress, String city, String state,
boolean isFemale, boolean isEmployed, boolean isHomeOwner) {
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
this.salutation = salutation;
this.suffix = suffix;
this.streetAddress = streetAddress;
this.city = city;
this.state = state;
this.isFemale = isFemale;
this.isEmployed = isEmployed;
this.isHomeOwner = isHomeOwner;
}
}
  • 问题:
    • 添加大量参数理解难度
    • 易参数位置不正确且运行正常
    • 不易维护
    • 那一个方法或者构造方法多少个参数才好了—没有答案
1
函数参数的理想个数是零,其次是一,紧随其后的是二,应该尽可能避免三个参数的情况。参数如果多于三个则需要特殊的理由,而且无论如何都不应该再使用。
解决方法
  • 引入参数对象

    • 参数关系紧密合并到一个对象中

      • Person.class

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        	public class Person {
        public FullName fullName;
        public Address address;
        public boolean isFemale;
        public boolean isEmployed;
        public boolean isHomeOwner;
        public Person(FullName fullName, Address address, boolean isFemale, boolean isEmployed, boolean isHomeOwner) {
        this.fullName = fullName;
        this.address = address;
        this.isFemale = isFemale;
        this.isEmployed = isEmployed;
        this.isHomeOwner = isHomeOwner;
        }
        }
- FullName.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	public class FullName {
public String lastName;
public String firstName;
public String middleName;
public String salutation;
public String suffix;
public FullName(String lastName, String firstName, String middleName, String salutation, String suffix) {
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
this.salutation = salutation;
this.suffix = suffix;
}
}
- Address.class
1
2
3
4
5
6
7
8
9
10
	public class Address {
public String streetAddress;
public String city;
public String state;
public Address(String streetAddress, String city, String state) {
this.streetAddress = streetAddress;
this.city = city;
this.state = state;
}
}
- 问题:参数对象可能被滥用。如果一个开发者纯粹为了减少参数数量,把联系不紧的几个参数强捆在一个类中这肯定是行不通的,在可读性上甚至适得其反。
  • Builder模式:

    • 需求:当一个对象需要不同的参数构造方法?不能写5*5满足所有的需求吧
    • 适用范围:构建对象时,如果碰到类有很多参数——其中很多参数类型相同而且很多参数可以为空时,使用Builder模式来完成。当参数数量不多、类型不同而且都是必须出现时,通过增加代码实现Builder往往无法体现它的优势。在这种情况下,理想的方法是调用传统的构造函数。再者,如果不需要保持不变,那么就使用无参构造函数调用相应的set方法吧。
    • 代码

      • Person.class
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      public class Person {
      public FullName fullName;
      public Address address;
      public boolean isFemale;
      public boolean isEmployed;
      public boolean isHomeOwner;

      public Person(FullName fullName, Address address, boolean isFemale, boolean isEmployed, boolean isHomeOwner) {
      this.fullName = fullName;
      this.address = address;
      this.isFemale = isFemale;
      this.isEmployed = isEmployed;
      this.isHomeOwner = isHomeOwner;
      }

      public static class Builder {
      private FullName fullName;
      private Address address;
      private boolean isFemale;
      private boolean isEmployed;
      private boolean isHomeOwner;

      /**
      * 如果有必填参数这里可以构造必填构造方法
      */

      public Builder() {
      }

      public Builder setFullName(FullName fullName) {
      this.fullName = fullName;
      return this;
      }

      public Builder setAddress(Address address) {
      this.address = address;
      return this;
      }

      public Builder setFemale(boolean female) {
      isFemale = female;
      return this;
      }

      public Builder setEmployed(boolean employed) {
      isEmployed = employed;
      return this;
      }

      public Builder setHomeOwner(boolean homeOwner) {
      isHomeOwner = homeOwner;
      return this;
      }

      public Person create() {
      return new Person(fullName, address, isFemale, isEmployed, isHomeOwner);
      }
      }
      }
- FullName.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class FullName {
public String lastName;
public String firstName;
public String middleName;
public String salutation;
public String suffix;

public FullName(String lastName, String firstName, String middleName, String salutation, String suffix) {
this.lastName = lastName;
this.firstName = firstName;
this.middleName = middleName;
this.salutation = salutation;
this.suffix = suffix;
}

public static class Builder {
private String lastName;
private String firstName;
private String middleName;
private String salutation;
private String suffix;

public Builder() {
}

public Builder setLastName(String lastName) {
this.lastName = lastName;
return this;
}

public Builder setFirstName(String firstName) {
this.firstName = firstName;
return this;
}

public Builder setMiddleName(String middleName) {
this.middleName = middleName;
return this;
}

public Builder setSalutation(String salutation) {
this.salutation = salutation;
return this;
}

public Builder setSuffix(String suffix) {
this.suffix = suffix;
return this;
}

public FullName create() {
return new FullName(lastName, firstName, middleName, salutation, suffix);
}
}
}
- Address.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Address {
public String streetAddress;
public String city;
public String state;
public Address(String streetAddress, String city, String state) {
this.streetAddress = streetAddress;
this.city = city;
this.state = state;
}
public static class Builder {
private String streetAddress;
private String city;
private String state;

public Builder() {
}

public Builder setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
return this;
}

public Builder setCity(String city) {
this.city = city;
return this;
}

public Builder setState(String state) {
this.state = state;
return this;
}

public Address create() {
return new Address(streetAddress, city, state);
}
}
}
调用的地方
1
2
3
4
5
6
7
8
 public static void main(String[] args) {
FullName fullName = new FullName.Builder().setFirstName("yes")
.setLastName("no").create();
Address address = new Address.Builder().setCity("china").setState("12")
.create();
Person person = new Person.Builder().setAddress(address)
.setFullName(fullName).create();
}
- 优点:客户端代码的可用性和可读性得到了大大提高,构造函数的参数数量明显减少调用起来非常直观。单个builder构建多个对象时Builder参数可在创建期间进行调整,还可以根据对象不同而进行改变,有效的避免重载构造函数。 - 缺点:增加代码量,代码变得更加冗长(相比较参数数量的增加,相同类型的参数混在一起,可选参数的增加而言,改善代码可读性更有价值)
  • 重载

    • 适用范围:方法中参数可选参数或者参数中指定参数相同
    • 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public String name(String name,int year) {
      return name+year;
      }
      /**
      * 重载
      * @param name
      * @return
      */
      public String name(String name) {
      return name+"null";
      }
- 优点:遇到可选参数或者默认参数时,使用方法重载会十分有效。

Swift与JavaScript交互 - 刘康

概述

iOS原生应用和web页面的交互大致上有这几种方法:

  • iOS7之后的JavaScriptCore
  • 拦截协议
  • 第三方框架WebViewJavaScriptBridge : 是基于拦截协议进行的封装,学习成本相对JavaScriptCore较高
  • iOS8之后的WKWebView : iOS8之后推出的,还没有成为主流使用。

关于JavaScriptCore

涉及到的几种类型:

  • JSContext: JSContext是代表JS的执行环境,通过-evaluateScript:方法就可以执行JS代码
  • JSValue: JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等
  • JSExport: JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用

Swift与JS交互方式

两种调用JS代码的方法:

1、直接调用JS代码

2、在Swift中通过JSContext注入模型,然后调用模型的方法

直接调用JS代码

我们可以不通过模型来调用方法,也可以直接调用方法

1
2
3
4
5
6
7
8
9
10
11
// 直接JS方法
let context = JSContext()
context.evaluateScript("var num = 8")
context.evaluateScript("function double(value) {return value * 2}")
// 调用并打印结果
let result = context.evaluateScript("double(num)")
print("result = \(result)")
// 可通过下标来获取JS方法
let doubleFunc = context.objectForKeyedSubscript("double")
let double10Result = doubleFunc.callWithArguments(["10"])
print("doubleFunc(10), result = \(double10Result.toString())")

这种方式是没有注入模型到JS中的。这种方式使用起来不太合适,通常在JS中有很多全局的函数,为了防止名字重名,使用模型的方式是最好不过了。通过我们协商好的模型名称,在JS中直接通过模型来调用我们在Swift中所定义的模型所公开的API。
注入模型的交互

注入模型的交互

首先,我们需要先定义一个协议,而且这个协议必须要遵守JSExport协议。

All methods that should apply in Javascript,should be in the following protocol.

注意,这里必须使用@objc,因为JavaScriptCore库是ObjectiveC版本的。如果不加@objc,则调用无效果。

1
2
3
4
5
6
objc protocol JavaScriptSwiftDelegate: JSExport {
func callSystemCamera();
func showAlert(title: String, msg: String);
func callWithDict(dict: [String: AnyObject])
func jsCallObjcAndObjcCallJsWithDict(dict: [String: AnyObject]);
}

接下来,我们还需要定义一个模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@objc class SwiftJSModel: NSObject, JavaScriptSwiftDelegate {
weak var controller: UIViewController?
weak var jsContext: JSContext?

func callSystemCamera() {
showAlert("JS调用Native", msg: "这里应该去调用摄像头");

let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");
jsFunc?.callWithArguments([]);
}

func showAlert(title: String, msg: String) {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
let alert = UIAlertController(title: title, message: msg, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "ok", style: .Default, handler: nil))
self.controller?.presentViewController(alert, animated: true, completion: nil)
}
}

func callWithDict(dict: [String : AnyObject]) {
print("JS call objc method: callWithDict, args: %@", dict)
showAlert("JS调用Native方法:callWithDict", msg: "这里处理callWithDict")
}

func jsCallObjcAndObjcCallJsWithDict(dict: [String : AnyObject]) {
print("js call objc method: jsCallObjcAndObjcCallJsWithDict, args: %@", dict)
showAlert("JS调用Native方法:jsCallObjcAndObjcCallJsWithDict", msg: "dict:\(dict)")

let jsParamFunc = self.jsContext?.objectForKeyedSubscript("jsParamFunc");
let dict = NSDictionary(dictionary: ["age": 18, "height": 168, "name": "lili"])
jsParamFunc?.callWithArguments([dict])
}
}

接下来,我们在controller中在webview加载完成的代理中,给JS注入模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func webViewDidFinishLoad(webView: UIWebView) {
let context = webView.valueForKeyPath("documentView.webView.mainFrame.javaScriptContext") as? JSContext
let model = JSObjCModel()
model.controller = self
model.jsContext = context
self.jsContext = context

// 这一步是将OCModel这个模型注入到JS中,在JS就可以通过OCModel调用我们公暴露的方法了。
self.jsContext?.setObject(model, forKeyedSubscript: "OCModel")
let url = NSBundle.mainBundle().URLForResource("test", withExtension: "html")
self.jsContext?.evaluateScript(try? String(contentsOfURL: url!, encoding: NSUTF8StringEncoding));

self.jsContext?.exceptionHandler = {
(context, exception) in
print("exception @", exception)
}
}

JSContext是通过webView的valueForKeyPath获取的,其路径为documentView.webView.mainFrame.javaScriptContext。
这样就可以获取到JS的context,然后为这个context注入模型对象。
先写两个JS方法:

1
2
3
4
5
6
7
function jsFunc() {
alert('Objective-C call js to show alert');
}
// 注意哦,如果JS写错,可能在OC调用JS方法时,都会出错哦。
var jsParamFunc = function(argument) {
document.getElementById('jsParamFuncSpan').innerHTML = argument['name'];
}

这里定义了两个JS方法,一个是jsFunc,不带参数。
另一个是jsParamFunc,带一个参数。

当点击第一个按钮:Call ObjC system camera时,
通过OCModel.callSystemCamera(),就可以在HTML中通过JS调用OC的方法。
在Swift代码callSystemCamera方法体中,添加了以下两行代码,就是获取HTML中所定义的JS就去jsFunc,然后调用它。

1
2
let jsFunc = self.jsContext?.objectForKeyedSubscript("jsFunc");
jsFunc?.callWithArguments([]);

这样就可以在JS调用Siwft方法时,也让Swift反馈给JS。
注意:这里是通过objectForKeyedSubscript方法来获取变量jsFunc。
方法也是变量。看看下面传字典参数:

1
2
3
4
5
6
7
func jsCallObjcAndObjcCallJsWithDict(dict: [String : AnyObject]) {
print("js call objc method: jsCallObjcAndObjcCallJsWithDict, args: %@", dict)

let jsParamFunc = self.jsContext?.objectForKeyedSubscript("jsParamFunc");
let dict = NSDictionary(dictionary: ["age": 18, "height": 168, "name": "lili"])
jsParamFunc?.callWithArguments([dict])
}

获取HTML中定义的jsParamFunc方法,然后调用它并传了一个字典作为参数。