吴昌文

总有骄阳


  • Home

  • Tags

  • Categories

  • Archives

Throttle和Debounce的区别

Posted on 2019-07-01 | In iOS

《如何阅读一本书》读书笔记

Posted on 2019-06-12

阅读的层次

阅读的活力与艺术

  • 拿同样的书给不同的人阅读,一个人却读得比另一个人好这件事,首先在于这人的阅读更主动,其次,在于他在阅读 中的每一种活动都参与了更多的技巧。阅读是一个复杂的活动,就跟写作一样, 包含了大量不同的活动。要达成良好的阅读,这些活动都是不可或缺的。一个人越能运作这些活动,阅 读的效果就越好。
  • 阅读的目标:为获得资讯而读,以及为求得理解而读
  • 阅读就是学习:指导型的学习,以及自我发现型的学习之间的差异

阅读的层次

  • 第一层次:基础阅读,接收基础的阅读训练,获得初步的阅读技巧
  • 第二层次:检视阅读,在一定的时间之内,抓出一本书的重点
  • 第三层次:分析阅读,在无限的时间里,最好也最完整的阅读
  • 第四层次:主题阅读,读很多书,而不是一本书,并列举出这些书之间相关之处,提出一个所有的书都谈到的主题

基础阅读

  • 无限制的受教育机会 是一个社会能提供给人民最有价值的服务—或说得正确一点,只有当一个人的自我期许,能力与需要受 限制时,教育机会才会受到限制。我们还没有办法提供这种机会之前,不表示我们就有理由要放弃尝试

检视阅读

  • 略读的习惯:
    1. 先看书名页,然后如果有序就先看序。要很快地看过去。特别注意副标题,或其他的相关说明或宗旨,或是作者写作本书的特殊角度
    2. 研究目录页,对这本书的基本架构做概括性的理解
    3. 如果书中附有索引,也要检阅一下—大多数论说类的书籍都会有索引。快速评估一下这本书涵盖了哪些议题的范围,以及所提到的书籍种类与作者等等
    4. 如果那是本包着书衣的新书,不妨读一下出版者的介绍
    5. 从你对一本书的目录很概略,甚至有点模糊的印象当中,开始挑几个看来跟主题息息相关的篇章来看
    6. 最后一步,把书打开来,东翻翻西翻翻,念个一两段.有时候连续读几页,但不要太多。就用这样的方法把全书翻过一遍,随时寻找主要论点的讯号,留意主题的基本脉动
  • 在阅读一本书的时候,慢不该慢到不值得,快不该快到有损于满足与理解。不论怎么说,阅读的速度,不论是快还是慢,只不过是阅读问题一个微小的部分而已
  • 头一次面对一本难读的书的时候,从头到尾先读完一遍,碰到不懂的地方不要停下来查询或思索

如何做一个自我要求的读者

  • 一个阅读者要提出的四个基本问题
    1. 这本书到底在谈些什么?
    2. 作者细部说了什么,怎么说的?
    3. 这本书说得有道理吗?是全部有道理,还是部分有道理?
    4. 这本书跟你有什么关系?
  • 如果你有读书时提出问题的习惯,那就要比没有这种习惯更能成为一个好的阅读者。但是,就像我们所强调的,仅仅提出问题还不够。你还要试着去回答问题
  • 艺术就跟其他有规则可循的事一样,是可以学习、运作的。就跟养成其他事情的习惯一样,只要照着规则练习,就可以培养出习惯来
  • 一个人只要学习过一种复杂的技巧,就会知道要学习一项新技巧,一开始的复杂过程是不足为惧的。也知道他用不着担心这些个别的行动,因为只有当他精通这些个别的行动时,才能完成一个整体的行动

分析阅读

###一本书的分类

  • 分析阅读的第一个规则可以这么说:规则一,你一定要知道自己在读的是哪一类书,而且要越早知 道越好。最好早在你开始阅读之前就先知道
  • 一开始时,你要先检视这本书—用检视阅读先浏览一遍。你读读书名、副标 题、目录,然后最少要看看作者的序言、摘要介绍及索引。如果这本书有书衣,要看看出版者的宣传文案。这些都是作者在向你传递讯号,让你知道风朝哪个方向吹。如果你不肯停、看、听,那也不是他的错
  • 在说明检视阅读的艺术时,我们提醒过你在读完前言或索引之后,不要停下来,要看看书中的重点 摘要部分。此外也要看看这本书的开头跟结尾,以及主要的内容

完美的UITextField字符限制方案

Posted on 2019-04-08 | In iOS

在做一个基本的输入框时,需求限制20字,到达20字之后无法继续输入,虽然是简单子字符限制,但也在开发过程中踩了好几个坑,最后总结了一个完美的字符限制方案,可以应对各种边界输入的情况

1
2
3
4
5
6
7
NotificationCenter.default.rx.notification(NSNotification.Name(rawValue:
"UITextFieldTextDidChangeNotification"), object: textField).subscribe(onNext: { (noti) in
guard let textField = noti.object as? UITextField, let text = textField.text as NSString? else { return }
if textField.markedTextRange == nil, textField.length > nickNameMaxLength {
textField.text = text.substring(to: text.rangeOfComposedCharacterSequences(for: NSRange(location: 0, length: nickNameMaxLength)).upperBound)
}
}).disposed(by: disposeBag)
  • 使用RxSwift监听UITextFieldTextDidChangeNotification,每次文本变化时接收回调
  • markedTextRange:防止最后一个字需要输入汉字时,用拼音只能输入一个字母的情况
  • rangeOfComposedCharacterSequences:防止最后一个字符为Emoji被截断的问题

Dart官方文档阅读笔记

Posted on 2019-02-11 | In Flutter

由于准备入坑Flutter,所以Dart语言也是必学的。花了两天时间过了一遍官方入门文档,对于如果拥有其他面向对象语言基础,并且英文比较好的同学,上手Dart是非常快的(Swift,Dart,Kotlin三胞胎🤣)。本文总结了Dart的一些语法特点便于以后翻阅,比较基础的就跳过啦。

基本概念

  • 所有的变量均为对象,所有的对象都是一个类的实例,包括数字,方法和空值(null)。所有的对象继承自Object类。
  • Dart是强类型语言,并且可以推断变量类型即使没有声明。
  • Dart无继承关键字(public, protected, and private),使用_开头的成员变量默认为私有属性。

变量

  • 未初始化的变量默认为null,即使是数字这种基本类型也为null,因为在Dart中所有变量均为对象类型。
  • 一个final修饰的变量无法再次被赋值。
  • 一个const修饰的值在编译期间就已经确定,无法被改变。

内建类型

Dart对于以下数据类型有特殊处理:

  • numbers
  • strings
  • booleans
  • lists (also known as arrays)
  • maps
  • runes (for expressing Unicode characters in a string)
  • symbols

可使用字面量语法初始化这些类型的对象

数字

Dart中的数字有两种类型:int和double

字符串转数字:

var one = int.parse('1');

数字转字符串:

String oneAsString = 1.toString();

(数字字面量直接用点语法,这糖真甜😁)

字符串

  • Dart字符串为UTF-16字符序列,可使用””或者’’创建
  • 使用${expression}将表达式放入一个字符串中
  • 使用==判断两个字符串是否等同,若两个字符串的字符序列相同,则两者等同
  • 使用+合并字符串
  • 使用r创建一个字符串生值

    var s = r'In a raw string, not even \n gets special treatment.';

布尔值

  • if和assert只可接收布尔值

数组

  • 使用Lists表示,可自动推断元素类型
  • 在数组前加const表示一个不可变数组,为编译器常量

    var constantList = const [1, 2, 3];

字典

  • 使用Maps表示,可自动推断元素类型
  • 加const代表不可变字典

    1
    2
    3
    4
    5
    final constantMap = const {
    2: 'helium',
    10: 'neon',
    18: 'argon',
    };

字符

  • Dart字符由UFT-32字符集构成,由于字符(UFT-32)与字符串(UTF-16)为不用字符集,相互转化时需要特殊语法
  • 表示一个Unicode字符:\uXXXX,超过4位:\u{1f600}

方法

  • Dart中的方法是一个Funciton对象
  • Dart返回简写语法:

    bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

  • 参数默认值

    void enableFlags({bool bold = false, bool hidden = false}) {...}

  • 函数可作为参数传入另一个函数
  • 匿名函数

    1
    2
    3
    4
    var list = ['apples', 'bananas', 'oranges'];
    list.forEach((item) {
    print('${list.indexOf(item)}: $item');
    });

操作符

  • 使用as做强制类型转换, 可能会抛出异常

    (emp as Person).firstName = 'Bob';

  • 使用is判断对象类型

    1
    2
    3
    4
    if (emp is Person) {
    // Type check
    emp.firstName = 'Bob';
    }
  • expr1 ?? expr2:若expr1非空,返回该值,否则返回expr2

级联调用

  • 使用..对同一个对象连续做一系列操作,包括函数调用,无需声明临时变量

    1
    2
    3
    4
    querySelector('#confirm') // Get an object.
    ..text = 'Confirm' // Use its members.
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));

异常

  • 未捕获的异常会造成程序终止
  • Dart提供Exception和Error两种类型的异常,也可以抛出一个非空对象作为异常

    throw 'Out of llamas!';

  • 使用on捕获特定类型的异常,使用catch捕获未被指定的异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    try {
    breedMoreLlamas();
    } on OutOfLlamasException {
    // A specific exception
    buyMoreLlamas();
    } on Exception catch (e) {
    // Anything else that is an exception
    print('Unknown exception: $e');
    } catch (e) {
    // No specified type, handles all
    print('Something really unknown: $e');
    }
  • 重复传播异常时使用rethrow关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void misbehave() {
    try {
    dynamic foo = true;
    print(foo++); // Runtime error
    } catch (e) {
    print('misbehave() partially handled ${e.runtimeType}.');
    rethrow; // Allow callers to see the exception.
    }
    }
  • 使用Finally可以保证即使有异常产生也保证代码继续运行

    1
    2
    3
    4
    5
    6
    try {
    breedMoreLlamas();
    } finally {
    // Always clean up, even if an exception is thrown.
    cleanLlamaStalls();
    }

类

  • 使用?.可以避免调用者为空值时产生异常

    1
    2
    // If p is non-null, set its y value to 4.
    p?.y = 4;

构造函数

  • 构造函数的语法糖:

    1
    2
    3
    4
    5
    6
    7
    class Point {
    num x, y;

    // Syntactic sugar for setting x and y
    // before the constructor body runs.
    Point(this.x, this.y);
    }
  • 如果未声明构造函数,那么会隐式的生成一个无初始化参数的构造函数

  • 构造函数不会被继承
  • 命名构造函数,同样不会被继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Point {
    num x, y;

    Point(this.x, this.y);

    // Named constructor
    Point.origin() {
    x = 0;
    y = 0;
    }
    }
  • 子类的构造函数默认会在首部调用父类的默认构造函数

  • 若需要触发父类的非默认构造函数,则可在函数名后使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Person {
    String firstName;

    Person.fromJson(Map data) {
    print('in Person');
    }
    }

    class Employee extends Person {
    // Person does not have a default constructor;
    // you must call super.fromJson(data).
    Employee.fromJson(Map data) : super.fromJson(data) {
    print('in Employee');
    }
    }
  • 子类也可直接使用父类的构造函数

    1
    2
    3
    4
    class Employee extends Person {
    Employee() : super.fromJson(getDefaultData());
    // ···
    }
  • 语法糖:可以在构造函数体调用之前出事偶会实例变量的值

    1
    2
    3
    4
    5
    6
    7
    // Initializer list sets instance variables before
    // the constructor body runs.
    Point.fromJson(Map<String, num> json)
    : x = json['x'],
    y = json['y'] {
    print('In Point.fromJson(): ($x, $y)');
    }
  • 语法糖:构造函数体调用前可使用断言验证参数合法性

    1
    2
    3
    Point.withAssert(this.x, this.y) : assert(x >= 0) {
    print('In Point.withAssert(): ($x, $y)');
    }
  • 重定向构造函数(使用已有的构造函数生成另一个构造函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Point {
    num x, y;

    // The main constructor for this class.
    Point(this.x, this.y);

    // Delegates to the main constructor.
    Point.alongXAxis(num x) : this(x, 0);
    }
  • 若对象不会被改变,可使用常量构造函数,需保证所有的实例变量均为编译期常量

    1
    2
    3
    4
    5
    6
    7
    8
    class ImmutablePoint {
    static final ImmutablePoint origin =
    const ImmutablePoint(0, 0);

    final num x, y;

    const ImmutablePoint(this.x, this.y);
    }
  • 工厂模式构造函数,如果调用构造函数时不一定需要生成一个实例时,可使用关键字factory

    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
    class Logger {
    final String name;
    bool mute = false;

    // _cache is library-private, thanks to
    // the _ in front of its name.
    static final Map<String, Logger> _cache =
    <String, Logger>{};

    factory Logger(String name) {
    if (_cache.containsKey(name)) {
    return _cache[name];
    } else {
    final logger = Logger._internal(name);
    _cache[name] = logger;
    return logger;
    }
    }

    Logger._internal(this.name);

    void log(String msg) {
    if (!mute) print(msg);
    }
    }

方法

  • Dart会为对象的属性隐式生成getter和setter方法
  • 抽象方法只可存在于抽象类中
  • 抽象类不可被实例化,一般用于定义接口
  • 声明抽象方法只需用;代替方法体

    1
    2
    3
    4
    5
    abstract class Doer {
    // Define instance variables and methods...

    void doSomething(); // Define an abstract method.
    }
  • 类可以继承多个接口

  • 使用@override重写成员函数

    1
    2
    3
    4
    5
    class SmartTelevision extends Television {
    @override
    void turnOn() {...}
    // ···
    }
  • 支持操作符重写

  • 重写==操作符时同时需要重写hashcode方法(getter)
  • 重写nosuchMethod方法后,当调用一个不存在的方法时会被触发

枚举

  • 使用values获取枚举所有值的数组

    1
    2
    List<Color> colors = Color.values;
    assert(colors[2] == Color.blue);
  • 使用switch语句时,如果没有处理所有的枚举值则会收到警告

  • 枚举类型有如下限制:
    1. 不可继承,混入
    2. 不可实例化

混入

  • 混入可在多个类的体系中复用一个类的代码
  • 使用关键词with,支持混入多个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Musician extends Performer with Musical {
    // ···
    }

    class Maestro extends Person
    with Musical, Aggressive, Demented {
    Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
    }
    }
  • 使用on指代一个混入类的特定类型

    1
    2
    3
    mixin MusicalPerformer on Musician {
    // ···
    }

类变量和类方法

  • 使用static指代类变量和类方法
  • 类变量不可访问this
  • 类变量是编译期常量,可作为函数参数传入

泛型

  • 泛型类型是具体化的,运行时携带了完整的类型信息,可使用is判断其具体类型

    1
    2
    3
    var names = List<String>();
    names.addAll(['Seth', 'Kathy', 'Lars']);
    print(names is List<String>); // true
  • 使用extends约束泛型类型

    1
    2
    3
    4
    5
    6
    class Foo<T extends SomeBaseClass> {
    // Implementation goes here...
    String toString() => "Instance of 'Foo<$T>'";
    }

    class Extender extends SomeBaseClass {...}
  • 泛型函数:

    1. 返回值可为泛型 (T)
    2. 参数可为泛型 (List)
    3. 临时变量为泛型 (T tmp)

      1
      2
      3
      4
      5
      6
      T first<T>(List<T> ts) {
      // Do some initial work or error checking, then...
      T tmp = ts[0];
      // Do some additional checking or processing...
      return tmp;
      }

库

  • 使用package:做唯一标识

    import 'package:test/test.dart';

  • 库名标识冲突时使用as

    1
    2
    3
    4
    5
    6
    7
    8
    import 'package:lib1/lib1.dart';
    import 'package:lib2/lib2.dart' as lib2;

    // Uses Element from lib1.
    Element element1 = Element();

    // Uses Element from lib2.
    lib2.Element element2 = lib2.Element();
  • 引入库的某一部分

    1
    2
    3
    4
    5
    // Import only foo.
    import 'package:lib1/lib1.dart' show foo;

    // Import all names EXCEPT foo.
    import 'package:lib2/lib2.dart' hide foo;
  • 使用deferred as懒加载:

    import 'package:greetings/hello.dart' deferred as hello;

    当需要加载库时,调用loadLibrary()

    1
    2
    3
    4
    Future greet() async {
    await hello.loadLibrary();
    hello.printGreeting();
    }
    • await可保证库加载完成再执行后续代码
    • loadLibrary()可调用多次,但库只会被加载一次

异步

  • 使用async和await支持异步编程
  • 要使用await,其方法须带有async关键字
  • 可以使用try,catch和finally来处理await的异常
  • 异步方法声明

    1
    2
    3
    4
    5
    checkVersion() async {
    // ...
    }

    lookUpVersion() async => /* ... */;

    异步方法在方法体执行之前就返回了

  • 在循环中使用异步

    1
    2
    3
    await for (variable declaration in expression) {
    // Executes each time the stream emits a value.
    }

    expression返回的值必须是stream类型的

生成器

  • 当需要延迟产生一系列的值是使用generator,dart提供两种类型:
    1. 同步-生成器,返回一个Iterable对象
    2. 异步-生成器,返回一个Stream对象
  • 同步-生成器,方法体声明为sync*,使用yield发送值

    1
    2
    3
    4
    Iterable<int> naturalsTo(int n) sync* {
    int k = 0;
    while (k < n) yield k++;
    }
  • 异步-生成器,方法体声明为async*,使用yield发送值

    1
    2
    3
    4
    Stream<int> asynchronousNaturalsTo(int n) async* {
    int k = 0;
    while (k < n) yield k++;
    }
  • 使用yield*递归发送值

    1
    2
    3
    4
    5
    6
    Iterable<int> naturalsDownFrom(int n) sync* {
    if (n > 0) {
    yield n;
    yield* naturalsDownFrom(n - 1);
    }
    }

Callable classes(可调用的类)

  • 类实现call()方法后可作为函数被调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class WannabeFunction {
    call(String a, String b, String c) => '$a $b $c!';
    }

    main() {
    var wf = new WannabeFunction();
    var out = wf("Hi","there,","gang");
    print('$out');
    }

Isolates

Dart不使用多线程,所有的 Dart 代码在 isolates 中运行而不是线程。 每个 isolate 都有自己的堆内存,并且确保每个 isolate 的状态都不能被其他 isolate 访问

Flutter初探

Posted on 2019-01-28 | In Flutter

Fluter是Google开发的一款跨平台的移动端开发框架,目前支持iOS和Android端,2018年末推出了Release版本1.0.0。

Flutter拥有现代前端开发的很多特性,Hot Reload、AOT编译,声明式UI语法,最重量级的是跨平台开发,真正做到了一次编写,到处运行,Flutter采用了与React Native和Weex不一样的解决方案,Flutter的页面渲染没有采用原生组件,而是使用Skia自主绘制,可以想象Flutter编写的应用就像是在播放Flash动画一样,对原生平台没有依赖,这使得Flutter在设计上可以立足于所有前端开发,未来有可能成为移动端,PC端和Web端的通用开发语言。

Material design也是非常好看

但是Flutter目前仍然存在些许缺陷,现在已经是Release 1.0了,Github上的4000个issue还是让人有点望而生畏,Flutter的审核感觉挺严格的,我跑demo的时候遇到了一个Android SDK的问题,提了一个issue,不到一个小时就标记为Duplicate给关了,所以现在未关闭的issue数可能真实的反映出Flutter离成熟的商业开发还有一些距离。

跑完Demo后略有一点小失望,官方Demo在S6 edge +上运行十分流畅,而在6S上切换页面和加载动画时会有掉帧,让我感觉是在iOS系统上安装了一个国产Android应用,可能是Flutter的页面渲染用不是的原生组件,或者Google没有针对Flutter在iOS上做优化,导致在较老的iOS设备上体验不好,对于iOS原生应用,就算设备再老,动画和页面也只是加载的时间长一些而已,体验上是平滑流畅的,很少会出现掉帧的情况。

布局嵌套也略微有点恶心,一层层的括弧反括弧,但是写习惯了也不错,何况还支持宇宙最好用的IDE我大VS Code,相比iOS下的可视化界面和自动布局,这种声明式的UI语法能更好的支持代码复用和团队协作。

已经准备入坑了,虽然现在的Flutter还不成熟,但是我仍然看好它,并相信其走在正确的道路上,Native开发是当下,跨平台开发是未来,我们处在未来与当下之间,希望Flutter能变得越来越好,一统大前端的江湖。

安装Flutter时Gradle运行失败

Posted on 2019-01-23 | In Flutter

执行flutter run命令打包android项目时遇到了这个错误

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Jarvis-2:flutter_gallery jarvis$ flutter run
Launching lib/main.dart on SM G9600 in debug mode...
Initializing gradle... 1.1s
Resolving dependencies...
* Error running Gradle:
ProcessException: Process "/Users/admin/Documents/flutter/examples/flutter_gallery/android/gradlew" exited abnormally:

> Configure project :app
Checking the license for package Android SDK Platform 27 in /Users/admin/Library/Android/sdk/licenses
Warning: License for package Android SDK Platform 27 not accepted.


FAILURE: Build failed with an exception.

* Where:
Build file '/Users/admin/Documents/flutter/examples/flutter_gallery/android/build.gradle' line: 24

* What went wrong:
A problem occurred evaluating root project 'android'.
> A problem occurred configuring project ':app'.
> Failed to install the following Android SDK packages as some licences have not been accepted.
platforms;android-27 Android SDK Platform 27
To build this project, accept the SDK license agreements and install the missing components using the Android Studio SDK Manager.
Alternatively, to transfer the license agreements from one workstation to another, see http://d.android.com/r/studio-ui/export-licenses.html

Using Android SDK: /Users/admin/Library/Android/sdk

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
Command: /Users/admin/Documents/flutter/examples/flutter_gallery/android/gradlew app:properties


Please review your Gradle project setup in the android/ folder.
Jarvis-2:flutter_gallery jarvis$ flutter doctor -v
[✓] Flutter (Channel stable, v1.0.0, on Mac OS X 10.14 18A391, locale zh-Hans-CN)
• Flutter version 1.0.0 at /Users/admin/Documents/flutter
• Framework revision 5391447fae (8 weeks ago), 2018-11-29 19:41:26 -0800
• Engine revision 7375a0f414
• Dart version 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
• Android SDK at /Users/admin/Library/Android/sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-28, build-tools 28.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
• All Android licenses accepted.

[✓] iOS toolchain - develop for iOS devices (Xcode 10.0)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 10.0, Build version 10A255
• ios-deploy 1.9.4
• CocoaPods version 1.5.3

[✓] Android Studio (version 3.2)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 31.3.1
• Dart plugin version 181.5656
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)

[!] VS Code (version 1.30.2)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension not installed; install from
https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (2 available)
• SM G9600 • 3949363555493098 • android-arm64 • Android 8.0.0 (API 26)
• iPhone • 756df34d9fd2baa79c128611bba8bf55a36d4360 • ios • iOS 9.3.2

! Doctor found issues in 1 category.

使用flutter doctor检测环境都是正常的

后来上flutter项目里提了个issue,发现是android SDK版本不对导致的,我调试的安卓设备是8.0版本,而新安装的Android Studio的SDK默认只有9.0版本

解决方案:
在Android Studio的设置里安装设备对应版本的SDK即可

Mac OS下搭建Flutter开发环境

Posted on 2019-01-21 | In Flutter

获取Flutter SDK

  1. 从官网下载Flutter SDK,并解压
  2. 运行以下命令,添加环境变量

    $ export PATH="$PATH:[FLUUTER_PATH]/flutter/bin"

    [FLUUTER_PATH]为zip后文件夹所在的路径,比如解压到了Document文件夹下,则执行命令:

    $ export PATH="$PATH:~/Document/flutter/bin"

检测Flutter开发环境

运行命令:

$ flutter doctor

命令运行后若成功控制台输出:

1
2
3
4
5
6
7
8
9
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.0.0, on Mac OS X 10.14 18A391, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.0)
[✓] Android Studio (version 3.2)
[!] VS Code (version 1.30.2)
[✓] Connected device (1 available)

! Doctor found issues in 1 category.

(这里VS Code这一项有点奇怪,我已经升级到了latest stable version,不知道为什么会报警告
😅)

不符合的项会给出提示,需要安装对应的工具

安装Xcode

  1. 从appStore下载最新的Xcode
  2. 配置Xcode命令行工具

    $ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer

在iOS真机上运行Flutter应用

  1. 安装homebrew
  2. 确保homebrew更新到最新版本

    $ brew update

  3. 部署Flutter

    1
    2
    3
    4
    5
    $ brew install --HEAD usbmuxd
    $ brew link usbmuxd
    $ brew install --HEAD libimobiledevice
    $ brew install ideviceinstaller ios-deploy cocoapods
    $ pod setup
  4. 进入一个Flutter项目的根目录(在Flutter的主目录下的examples中有一些demo项目)

  5. 运行open ios/Runner.xcworkspace打开这个工程文件
  6. 登录你的Apple账号,选择自动签名

  7. 运行flutter run, 之后应用就会开始编译打包并在你的手机上运行

如何为View(包含子View)同时添加圆角和阴影效果

Posted on 2019-01-18 | In iOS

想要为UIView添加一个圆角需要设置layer层的masksToBounds为YES(不设置的话子视图超出圆角的部分会显示出来),如果同时我们为按钮添加阴影效果的话,此时阴影效果由于masksToBounds的值为YES也无法显示出来。

我们可以考虑将UIView的子视图也设置为圆角,将父视图的masksToBounds设置为NO,子视图的masksToBounds设置为YES。

比如我们想为如下的View(包含一个titleView和一个contentView)添加圆角和阴影

  1. 为父视图添加圆角和阴影效果,masksToBounds设置为NO
1
2
3
4
5
6
self.layer.cornerRadius = 8.0;
self.layer.shadowRadius = 8.0;
self.layer.shadowColor = [UIColor grayColor].CGColor;
self.layer.shadowOffset = CGSizeMake(0, 7);
self.layer.shadowOpacity = 0.15;
self.layer.masksToBounds = NO;
  1. 为上半部分View添加左上和右上的圆角,masksToBounds设置为YES
1
2
3
4
5
6
7
8
9
10
11
12
CGRect topCornerRect =
CGRectMake(self.titleView.bounds.origin.x, self.titleView.bounds.origin.x, self.bounds.size.width, 24);
UIBezierPath *titleViewMaskPath = [UIBezierPath bezierPathWithRoundedRect:topCornerRect
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(8, 8)];
//创建 layer
CAShapeLayer *titleViewMaskLayer = [[CAShapeLayer alloc] init];
titleViewMaskLayer.frame = topCornerRect;
//赋值
titleViewMaskLayer.path = titleViewMaskPath.CGPath;
self.titleView.layer.mask = titleViewMaskLayer;
self.titleView.layer.masksToBounds = YES;
  1. 为下半部分View添加左上和右上的圆角,masksToBounds设置为YES
CGRect bottomCornerRect = CGRectMake(self.blockSuperView.bounds.origin.x, self.blockSuperView.bounds.origin.x,
                                     self.bounds.size.width, self.bounds.size.height - 24);
UIBezierPath *blockSuperViewMaskPath =
    [UIBezierPath bezierPathWithRoundedRect:bottomCornerRect
                          byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                                cornerRadii:CGSizeMake(8, 8)];
//创建 layer
CAShapeLayer *blockSuperViewMaskLayer = [[CAShapeLayer alloc] init];
blockSuperViewMaskLayer.frame = bottomCornerRect;
//赋值
blockSuperViewMaskLayer.path = blockSuperViewMaskPath.CGPath;
self.blockSuperView.layer.mask = blockSuperViewMaskLayer;
self.blockSuperView.layer.masksToBounds = YES;

最终显示效果:

更换Launch-Screen的图片后不生效的解决办法

Posted on 2018-12-25 | In iOS

公司产品升级需更换新的启动页
在Images.xcassets文件夹中将图片替换后发现在某些系统上还是使用的旧图标
这是苹果的一个bug,当Launch Screen.storyboard引用了Images.xcassets之后就会出现这个问题
最快的解决办法就是卸载应用,重启设备,重装应用,启动页可替换
但是app上线后不可能让用户做这个操作啊,会被产品和技术支持diss
还有一个解决办法就是不使用Images.xcassets,改用直接引用图片文件
1.项目中添加png图片文件
image.png
2.storyboard中引用该图片
image.png
3.取消勾选Clears Graphics Context选项
image.png

iOS10-3系统Release模式下下未初始化指针导致崩溃

Posted on 2018-12-16 | In iOS

公司应用发布上线后有一个只在iOS10.3系统下的高频崩溃,通过崩溃日志定位到的崩溃行为:
image.png
该崩溃为访问了野指针szHeaders造成的崩溃
经过代码检查发现
image.png
经过调试发现指针被声明时即置为空指针
我将Xcode的build设置为Release后复现出该崩溃,指针声明未初始化时指向一块位置内存区域,导致野指针崩溃
在StackOverFlow上有该回答:
image.png

即Release模式下指针声明时不会置为空指针
指针声明时立即初始化赋值即解决该崩溃(开发习惯)
另外遇到线上的崩溃复现不了时应考虑是否为Release模式和Debug模式之间的区别导致的

123

Jarvis Wu

唯有时刻保持清醒,才能看清真正的价值在哪里

21 posts
4 categories
22 tags
GitHub E-Mail Google
© 2019 Jarvis Wu
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4