《如何阅读一本书》读书笔记
阅读的层次
阅读的活力与艺术
- 拿同样的书给不同的人阅读,一个人却读得比另一个人好这件事,首先在于这人的阅读更主动,其次,在于他在阅读 中的每一种活动都参与了更多的技巧。阅读是一个复杂的活动,就跟写作一样, 包含了大量不同的活动。要达成良好的阅读,这些活动都是不可或缺的。一个人越能运作这些活动,阅 读的效果就越好。
- 阅读的目标:为获得资讯而读,以及为求得理解而读
- 阅读就是学习:指导型的学习,以及自我发现型的学习之间的差异
阅读的层次
- 第一层次:基础阅读,接收基础的阅读训练,获得初步的阅读技巧
- 第二层次:检视阅读,在一定的时间之内,抓出一本书的重点
- 第三层次:分析阅读,在无限的时间里,最好也最完整的阅读
- 第四层次:主题阅读,读很多书,而不是一本书,并列举出这些书之间相关之处,提出一个所有的书都谈到的主题
基础阅读
- 无限制的受教育机会 是一个社会能提供给人民最有价值的服务—或说得正确一点,只有当一个人的自我期许,能力与需要受 限制时,教育机会才会受到限制。我们还没有办法提供这种机会之前,不表示我们就有理由要放弃尝试
检视阅读
- 略读的习惯:
- 先看书名页,然后如果有序就先看序。要很快地看过去。特别注意副标题,或其他的相关说明或宗旨,或是作者写作本书的特殊角度
- 研究目录页,对这本书的基本架构做概括性的理解
- 如果书中附有索引,也要检阅一下—大多数论说类的书籍都会有索引。快速评估一下这本书涵盖了哪些议题的范围,以及所提到的书籍种类与作者等等
- 如果那是本包着书衣的新书,不妨读一下出版者的介绍
- 从你对一本书的目录很概略,甚至有点模糊的印象当中,开始挑几个看来跟主题息息相关的篇章来看
- 最后一步,把书打开来,东翻翻西翻翻,念个一两段.有时候连续读几页,但不要太多。就用这样的方法把全书翻过一遍,随时寻找主要论点的讯号,留意主题的基本脉动
- 在阅读一本书的时候,慢不该慢到不值得,快不该快到有损于满足与理解。不论怎么说,阅读的速度,不论是快还是慢,只不过是阅读问题一个微小的部分而已
- 头一次面对一本难读的书的时候,从头到尾先读完一遍,碰到不懂的地方不要停下来查询或思索
如何做一个自我要求的读者
- 一个阅读者要提出的四个基本问题
- 这本书到底在谈些什么?
- 作者细部说了什么,怎么说的?
- 这本书说得有道理吗?是全部有道理,还是部分有道理?
- 这本书跟你有什么关系?
- 如果你有读书时提出问题的习惯,那就要比没有这种习惯更能成为一个好的阅读者。但是,就像我们所强调的,仅仅提出问题还不够。你还要试着去回答问题
- 艺术就跟其他有规则可循的事一样,是可以学习、运作的。就跟养成其他事情的习惯一样,只要照着规则练习,就可以培养出习惯来
- 一个人只要学习过一种复杂的技巧,就会知道要学习一项新技巧,一开始的复杂过程是不足为惧的。也知道他用不着担心这些个别的行动,因为只有当他精通这些个别的行动时,才能完成一个整体的行动
分析阅读
###一本书的分类
- 分析阅读的第一个规则可以这么说:规则一,你一定要知道自己在读的是哪一类书,而且要越早知 道越好。最好早在你开始阅读之前就先知道
- 一开始时,你要先检视这本书—用检视阅读先浏览一遍。你读读书名、副标 题、目录,然后最少要看看作者的序言、摘要介绍及索引。如果这本书有书衣,要看看出版者的宣传文案。这些都是作者在向你传递讯号,让你知道风朝哪个方向吹。如果你不肯停、看、听,那也不是他的错
- 在说明检视阅读的艺术时,我们提醒过你在读完前言或索引之后,不要停下来,要看看书中的重点 摘要部分。此外也要看看这本书的开头跟结尾,以及主要的内容
完美的UITextField字符限制方案
在做一个基本的输入框时,需求限制20字,到达20字之后无法继续输入,虽然是简单子字符限制,但也在开发过程中踩了好几个坑,最后总结了一个完美的字符限制方案,可以应对各种边界输入的情况
1 | NotificationCenter.default.rx.notification(NSNotification.Name(rawValue: |
- 使用RxSwift监听
UITextFieldTextDidChangeNotification
,每次文本变化时接收回调 - markedTextRange:防止最后一个字需要输入汉字时,用拼音只能输入一个字母的情况
- rangeOfComposedCharacterSequences:防止最后一个字符为Emoji被截断的问题
Dart官方文档阅读笔记
由于准备入坑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
5final 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
4var list = ['apples', 'bananas', 'oranges'];
list.forEach((item) {
print('${list.indexOf(item)}: $item');
});
操作符
使用
as
做强制类型转换, 可能会抛出异常(emp as Person).firstName = 'Bob';
使用
is
判断对象类型1
2
3
4if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}expr1 ?? expr2
:若expr1非空,返回该值,否则返回expr2
级联调用
使用
..
对同一个对象连续做一系列操作,包括函数调用,无需声明临时变量1
2
3
4querySelector('#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
12try {
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
9void 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
6try {
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
7class 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
11class 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
15class 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
4class 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
3Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}重定向构造函数(使用已有的构造函数生成另一个构造函数)
1
2
3
4
5
6
7
8
9class 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
8class 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
25class 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
5abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}类可以继承多个接口
使用
@override
重写成员函数1
2
3
4
5class SmartTelevision extends Television {
@override
void turnOn() {...}
// ···
}支持操作符重写
- 重写
==
操作符时同时需要重写hashcode
方法(getter) - 重写
nosuchMethod
方法后,当调用一个不存在的方法时会被触发
枚举
使用
values
获取枚举所有值的数组1
2List<Color> colors = Color.values;
assert(colors[2] == Color.blue);使用switch语句时,如果没有处理所有的枚举值则会收到警告
- 枚举类型有如下限制:
- 不可继承,混入
- 不可实例化
混入
- 混入可在多个类的体系中复用一个类的代码
使用关键词
with
,支持混入多个类1
2
3
4
5
6
7
8
9
10
11class Musician extends Performer with Musical {
// ···
}
class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}使用
on
指代一个混入类的特定类型1
2
3mixin MusicalPerformer on Musician {
// ···
}
类变量和类方法
- 使用
static
指代类变量和类方法 - 类变量不可访问
this
- 类变量是编译期常量,可作为函数参数传入
泛型
泛型类型是具体化的,运行时携带了完整的类型信息,可使用
is
判断其具体类型1
2
3var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true使用
extends
约束泛型类型1
2
3
4
5
6class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}泛型函数:
- 返回值可为泛型 (T)
- 参数可为泛型 (List
) 临时变量为泛型 (T tmp)
1
2
3
4
5
6T 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
8import '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
4Future greet() async {
await hello.loadLibrary();
hello.printGreeting();
}await
可保证库加载完成再执行后续代码loadLibrary()
可调用多次,但库只会被加载一次
异步
- 使用
async
和await
支持异步编程 - 要使用
await
,其方法须带有async
关键字 - 可以使用
try
,catch
和finally
来处理await
的异常 异步方法声明
1
2
3
4
5checkVersion() async {
// ...
}
lookUpVersion() async => /* ... */;异步方法在方法体执行之前就返回了
在循环中使用异步
1
2
3await for (variable declaration in expression) {
// Executes each time the stream emits a value.
}expression
返回的值必须是stream
类型的
生成器
- 当需要延迟产生一系列的值是使用
generator
,dart提供两种类型:- 同步-生成器,返回一个
Iterable
对象 - 异步-生成器,返回一个
Stream
对象
- 同步-生成器,返回一个
同步-生成器,方法体声明为
sync*
,使用yield
发送值1
2
3
4Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}异步-生成器,方法体声明为
async*
,使用yield
发送值1
2
3
4Stream<int> asynchronousNaturalsTo(int n) async* {
int k = 0;
while (k < n) yield k++;
}使用
yield*
递归发送值1
2
3
4
5
6Iterable<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
9class 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初探
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运行失败
执行flutter run
命令打包android项目时遇到了这个错误
1 | Jarvis-2:flutter_gallery jarvis$ flutter run |
使用flutter doctor
检测环境都是正常的
后来上flutter项目里提了个issue,发现是android SDK版本不对导致的,我调试的安卓设备是8.0版本,而新安装的Android Studio的SDK默认只有9.0版本
解决方案:
在Android Studio的设置里安装设备对应版本的SDK即可
Mac OS下搭建Flutter开发环境
获取Flutter SDK
- 从官网下载Flutter SDK,并解压
运行以下命令,添加环境变量
$ export PATH="$PATH:[FLUUTER_PATH]/flutter/bin"
[FLUUTER_PATH]为zip后文件夹所在的路径,比如解压到了Document文件夹下,则执行命令:
$ export PATH="$PATH:~/Document/flutter/bin"
检测Flutter开发环境
运行命令:
$ flutter doctor
命令运行后若成功控制台输出:
1 | Doctor summary (to see all details, run flutter doctor -v): |
(这里VS Code这一项有点奇怪,我已经升级到了latest stable version,不知道为什么会报警告
😅)
不符合的项会给出提示,需要安装对应的工具
安装Xcode
- 从appStore下载最新的Xcode
配置Xcode命令行工具
$ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
在iOS真机上运行Flutter应用
- 安装homebrew
确保homebrew更新到最新版本
$ brew update
部署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进入一个Flutter项目的根目录(在Flutter的主目录下的examples中有一些demo项目)
- 运行
open ios/Runner.xcworkspace
打开这个工程文件 登录你的Apple账号,选择自动签名
运行
flutter run
, 之后应用就会开始编译打包并在你的手机上运行
如何为View(包含子View)同时添加圆角和阴影效果
想要为UIView添加一个圆角需要设置layer层的masksToBounds为YES(不设置的话子视图超出圆角的部分会显示出来),如果同时我们为按钮添加阴影效果的话,此时阴影效果由于masksToBounds的值为YES也无法显示出来。
我们可以考虑将UIView的子视图也设置为圆角,将父视图的masksToBounds设置为NO,子视图的masksToBounds设置为YES。
比如我们想为如下的View(包含一个titleView和一个contentView)添加圆角和阴影
- 为父视图添加圆角和阴影效果,masksToBounds设置为NO
1 | self.layer.cornerRadius = 8.0; |
- 为上半部分View添加左上和右上的圆角,masksToBounds设置为YES
1 | CGRect topCornerRect = |
- 为下半部分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的图片后不生效的解决办法
公司产品升级需更换新的启动页
在Images.xcassets文件夹中将图片替换后发现在某些系统上还是使用的旧图标
这是苹果的一个bug,当Launch Screen.storyboard引用了Images.xcassets之后就会出现这个问题
最快的解决办法就是卸载应用,重启设备,重装应用,启动页可替换
但是app上线后不可能让用户做这个操作啊,会被产品和技术支持diss
还有一个解决办法就是不使用Images.xcassets,改用直接引用图片文件
1.项目中添加png图片文件
2.storyboard中引用该图片
3.取消勾选Clears Graphics Context选项
iOS10-3系统Release模式下下未初始化指针导致崩溃
公司应用发布上线后有一个只在iOS10.3系统下的高频崩溃,通过崩溃日志定位到的崩溃行为:
该崩溃为访问了野指针szHeaders造成的崩溃
经过代码检查发现
经过调试发现指针被声明时即置为空指针
我将Xcode的build设置为Release后复现出该崩溃,指针声明未初始化时指向一块位置内存区域,导致野指针崩溃
在StackOverFlow上有该回答:
即Release模式下指针声明时不会置为空指针
指针声明时立即初始化赋值即解决该崩溃(开发习惯)
另外遇到线上的崩溃复现不了时应考虑是否为Release模式和Debug模式之间的区别导致的