iOS技术积累

不管生活有多不容易,都要守住自己的那一份优雅。

Effective_C++读书笔记

学习知识只写在本子上也不方便记忆,这里做份记录方便后续查看

  1. 视C++为一个语言联邦

C++ 支持多语言范式:

  • procedural,过程式
  • object-oriented 面向对象式
  • functional 函数式
  • generic 范形式
  • metaprogramming 元编程
  1. 使用const ,enum,inline 代替#define

#define是一个容易检查的预处理指令,将更多的工作交给编译器而不是预处理骑
对于单纯的常量使用是const 和 enum更合适
对于内联函数使用inline更合适,在其他 情况才使用MACROS

  1. 更多的使用const
  • 对于参数仅仅const不同其实也是重载的,
  • const能接受 non-const 和const 如果成员方法是logical contenes,可以使用mutable 也去掉 const对编译器加上的 bitwess contess
  • 当const 和non const方法内容一样时 可以使用 non-const调用const方法 ,内部使用 static_caste<> 转换加上const,然后使用 const_caste<>去掉const
  1. 确定对象先出初始化再使用

    • 为内置数据类型手动初始化
    • 使用成员初始化列表初始化 ,而不是赋值,提升效率
    • 如果有不同的static有初始化顺序依赖关系。使用local-static代替 全局 static对象
  2. 了解C++默默编写并调用了那些函数

  • 默认构造函数
  • 拷贝构造函数
  • 析构函数(系统默认生成的是非virtual 除非有base声明为virtual)
  • 拷贝赋值运算符
  • 移动构造函数 (c11新增)
  • 移动赋值运算符 (c11新增)

如果类里面有引用或者const成员 系统是不会生成 copy = 和 移动=函数的
有动态内存管理的类需要自己实现方法 避免内存泄漏或者重复释放内存导致问题

  1. 如果不想使用编译器自动生成的函数,就拒绝它
  • 在类的private声明一个 拷贝构造函数和赋值运算符(只有声明) ,其他调用就会编译出错,但是友元和其他成员调用没事 ,但是会产生链接错误, 引入第二种方法。
  • 建立一个base类 将 拷贝构造函数和赋值运算符(只有声明)声明为私有,然后当前类私有继承自 base即可
  1. 为多态基类声明virtual析构函数
  • 当作基类的 如果有一个virtual成员函数 一般就有一个Virtual析构函数
  • 动态内存管理的也适合有个virtual析构函数(系统的std::string 的析构函数不是 virtual 所以集成的话如果有多态体现就会有内存泄漏的风险 STL很多这样,)
  • 不适合做基类的不要实现virtual 析构函数,因为有了虚函数表 效率会降低
  • 需要抽象类的时候 一般声明析构函数为纯虚函数是最好的 ,你还必须给析构函数提供定义 不定义在有子类的时候链接会出错
  1. 别让异常逃离析构函数(析构函数不要抛出异常)

    假设 vector ts (10,0) 10个元素有一个有异常 后续了能造成内存泄漏

  • 析构函数绝对不能抛出异常 如果有这样的需求 比如数据库链接对象 在析构时要关闭链接 可能抛出异常 可以使用两种方案
  1. 使用try{} catch (...){ std::abort(); }
  2. 使用try{} catch (...){ someLog()); }
  • 如果用户需要对异常作出反应,可以重新设计一个对外可以暴露的方法让用户去操作 如 public clos()
  1. 绝对不要在构造函数和析构函数中调用virtual函数

java系列不同,请注意
因为在继承构造顺序中,假设你在构造函数中调用virtual函数,其实这时候子类还没有实例化,virtual调用的还是非virtual 的,这时候即使使用了dynamic_cast 拿到的也是父类, 在析构函数调用virtual,子类已经被释放。

还有的时候非常难意识到你调用virtual函数,如多个构造函数调用了某个抽取出来的方法,但是这个方法里面调用了virtual函数,这也是有问题的,而且难以debug
如何解决,建议一种方案 ,比如 在构造函数调用的virtual函数 更改为non-virtual,并指定参数,用子类在构造函数初始化时传递。


class Transcation {
 public :
     explicit Transcation(const std::string & info) {
   //      .....
   log(info);
     }   
     void logT(const std::string & info) { // non-virtual 
    // do something          

     } 


}

class SubT : public Trancastion {
public :
    explicit SubT(XX,const std::string &info) : someInstanceVar(XX), Transcation(info) {}    


}

  1. 在赋值运算符中返回一个 refresence to *this

如何实现 连锁赋值,


class T {
T & operator=(const T & t) {
...
return *this;        
}     

}


- - - - ---




11. 在operator= 处理 “自我赋值”
> 假设在赋值运算符中 *this 和 传递进来的对象执行的是同一个对象时,可能会出现问题,比如 delete old, new Type(param) , param 可能是old  这时候会出问题。
通常做法是

```cpp
 T& operator= (const T & t ) {
    if t == *this { return *this} 
     delete this->xxval 
     this-> xxxval = new XXXVal(xxval)
     return *this
 }    

这个代码在有异常的时候也算有问题的 因为在new的时候发生异常可能导致后续的return 失败 *this执行了一个被释放的内存
新的实现

T & operator=(const T &t) {
XXXval * xxxvalOriginal = this-> xxxVal    // 保留原本的xxxval     
this->xxxVal =  new XXXVal(t.xxxval); // new copy  如果失败了 也不影响
delete xxxValOriginal; // delete old 
return *this;

}

或者使用新的 copy swap技术

class Widget {
    void swap(widget &rhs); {交换*this  和rhs 详情参见条款29   }
    Widget & operator=(const Widget rhs){ // by Reference 
        Widget temp(rhs) // copy cons        
        swap(temp);
        return *this)
    }    
    或者可以重写 接受参数为值传递的赋值运算符
     Widget & operator=(Widget rhs){ // by value
 // 已经copy过了
        swap(rhs

        );
        return *this)
    }    
}

总结: 要注意异常性安全 ,通常会解决自我赋值安全。


  1. 复制对象时不要忘记每一个成员

    copy constructor 和 copy assignmaent成为copying函数,

  2. 新增了成员变量,要修改所有的构造函数,拷贝构造函数,赋值运算符,赋值运算符变种+=

  3. 如果是继承的有基类,需要在拷贝构造函数的成员初始化列表调用父类的拷贝构造函数(),如果不写调用默认的构造函数,(不是没有调用), 在赋值运算符 手动调用一次 基类的赋值运算符 BASECLASS::operator=(rhs)

  4. 以对象管理资源

    在使用工厂方法创建对象时一般会返回一个指针, 这就依赖调用者再使用完毕后delete这个指针, 但是使用的函数如果出现 early return 或者 异常等会导致delete语句无法调用, 所以我们可以使用只能指针来管理 auto_ptr 这个指针在自己释放后会调用包含数据的析构函数
    auto_ptr指针是单一只能指针,在出现copy 赋值或者copy构造函数时旧的指针会清空为null 避免重复释放内存,但是这个指针在使用时会有其他复制多次出现的问题,而且无法支持stl容易, 更好的办法是使用引用计数指针 ,share_ptr,而且能用在stl容器中
    但是但是 这些智能指针都不使用 delete [],使用的是delete版本

    
    class Investement {}
    Investement *factory();
    void caller() {
    Investement *ptr = factor()
    //    xxxxx  假设这里出现return  throw 可能就会有问题 
    delete ptr;
    }
    void caller1() {
    std::auto_ptr<Investement> ptr = factor()

}
void caller2() {
// 1 较好的办法
std::shared_ptr ptr = factor()

}
std::shared_prt factory();


14. 在资源管理时小心copying行为
>  1 禁止复制
>  使用引用技术式的只能指针,注意可以使用 std::tr1::share_ptr,因为在某些时刻我们可能是在引用计数为0时做个额外操作,而不是析构这个对象、

15. 在资源管理类中提供对原始资源的访问
> 有时候我们使用了智能指针,但是在某些API的调用他们需要的是原始指针,那么我们可以通过shard_ptr.get()返回原始指针。
> 这就引出了一个问题 
> 1 提供get方法转换
> 2 实现一个隐式转换 operartor TOClass() const;但是这种可能带来隐藏问题的方式需要提供斟酌考虑是否采用

16. 成对使用new和delete时要采取匹配的形式
> new 和 delete  ,new []和delete[] 配合


17. 用独立的语句来将指针放进智能指针中
> 举个例子 
>void  process(std::tr1::shared_prt<Widget>,int priority)
> 在调用时你可能这样
>
> process(std::tr1::shared_prt<Widget>(new Widget),XXXprority())
> 系统给你出现的顺序可能是
> 1. new Widget
> 2. call XXXprority()
> 3. std::tr1::shared_ptr的构造函数
but  这时候 2 的函数调用出现了异常,  就会出现内存泄漏 ,因此更合适的办法应该是这样
```cpp
void  process(std::tr1::shared_prt<Widget>,int priority){}
// 不实用匿名指针
std::tr1::shared_ptr<Widget> w1(new Widget);
process(w1,XXpriority());
  1. 让接口容易被正确使用
  • 促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容
  • 组织误用的方法包括简历新的类型,限制类型上的操作。束缚对象值,以及消除客户的资源管理在责任
  • shared_ptr支持定制删除器(custom deleter),这可防范DLL问题,可被用来自动解除互斥锁。
  1. 设计class犹如设计type

    • 新的对象如何被创建和销毁 (构造函数和析构函数,自定义new运算符)
    • 对象的初始化和对象的赋值的差别(赋值和拷贝构造函数)
    • 新的对象如果被passwd By Value传递如何书写
    • 新的对象的合法值(需要约束数据是否合理)
    • 新的type的继承体系 比如析构函数
    • 新的Type和其他类型的转换
    • 什么杨的函数和操作符是合理的
    • 访问控制符
    • 是否应该抽象与类型 类型模板
  2. 宁以pass-By-ference-to-const 替换pass-by-Value

    copy value的代价非常昂贵 一般可以使用reference代替 ,为了避免别人修改 可以指定const
    还可以避免对象分割, 假设一个函数接受的是父类对象 那么传递一个子类的话经过 copy,子类信息会丢失 。没有多态性体现

    class Window{}
    class DebugWidow: public Widow{}
    void dosomething(Window w) {} // pass By Value
    // doSomethng(DebugWindow())
    改写为const reference
    void dosomething(const Window & w)

    系统内置类型和stl一般建议使用 pass by value

  3. 必须返回对象时,别妄想返回去reference

    绝不要返回一个指向localStack对象的指针引用,也不要返回heap-Allocated的指针或者引用,这种时候 直接返回对象就好了 copy by value 也无所谓

  4. 将成员变量声明为private

    封装的思想不再赘述

  5. 宁以non-member。non-friend替换member函数

    封装指的是封装数据。越多的函数可以访问数据,封装性就越低。
    外部的函数做的事情够用就好,没必要声明为成员函数,因为成员函数可以访问所有数据
    在组织代码结构时可以将class和 对class操作的函数放在同一个命名空间,但是不放在同一个文件中,这样在需要的时候才需导入头文件。

  6. 若所有参数皆需类型转换,请为此采用non-member函数

    如果你需要为某个函数的所有参数(包括 被this指针所值的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member函数

    
    class Rational {
    // 如果是成员运算符
    Rational(int numerator = 0, int denominator = 1); //这里没有explicit
    const Rational operator*(const Ration &rhs) const;
    调用Rational oneEight(1,8)
    Rational OneHalf(1,2)
    Ration     result  =  oneEight * 2//成功 , 因为当前相当于 oneEight.operator*(Ration(2))  2位于已有对象的参数列表里
    Ration     result  = 2 * oneEight// 这里失败  因为2没有成员函数调用,也没有全局函数可供调用,隐式转换必须位于已有对象 这里没有已有对象 所以不能转换将2转换为Rational对象

}
改写为non-member函数
const ration operator*(const Rational &lhs, const Rational &rhs){}
这时候调用皆能通过



25. 考虑写出一个不抛出异常的swap函数
如果std::swap效率不够或者是不满足使用条件可以考虑以下几种事情。
    1. 提供一个public swap成员函数,让它搞笑的置换你的类型的2个对象值,(必须不能抛出异常)
    2. 在你的class 或者 class template所在的命名空间内提供一个non-member swap并令他调用上述swap成员函数。
    3. 如果你编写的是一个class 而不是class Template 为你的class具体化一个std::swap并令它调用你的swap函数。
    4. 客户如果调用swap函数 请使用using::std::swap  然后直接调用swap函数 ,系统会为你寻找最合适的swap函数
阅读更多

干货集中营-ReactiveCocoa+RXSwift+MVVM


学习函数响应式编程已经接近两个月的时间。说实话坚持下来实在不易。两个月的时间看过近150篇博文,算下来啃下来一本千页的技术书籍也差不多。不过随着知识面的拓广,学习起来也更加顺利。本篇文章主要整理下自己收集的学习路线。其中包括了函数式编程的思想,ReactiveCocoa2.x(主要是Objective-C部分的函数响应式编程框架),ReactiveCocoa4.x(扩充了Swift部分的支持),RXSwift(ReactiveX系列),加上函数式编程对MVVM架构的帮助和影响。

写在之前

这篇文章中不适合新手,没有码过2年业务的程序员我觉得可以了解下,不一定非要学习使用这个,因为你的经验往往不足,在学习之后可能觉得自己能驾驭(要上天),但是随着业务的复杂度上升,类似RAC的重量级框架带来的副作用,调试的困难性等会让你后悔莫及。

面向对象编程是一个被大众易于接受的方式,也就意味着更容易协同工作。函式响应式编程虽然不是一个新的话题,但是引入iOS项目实际开发还是要抱谨慎态度,毕竟学习函数式编程的路线还是比较陡峭,这就意味者在团队开发中,需要斟酌如何引用,如何培养新手,如何控制对框架的不良使用在项目中蔓延。

MVC是一个非常成熟的架构,在业务开始复杂的时候,合理的将网络请求,数据持久化等相关与控制器耦合不严重的公共抽取出来,足够满足日常开发需求。 对MVVM的理解的差异性也会写出让团队成员写出截然不同的风格。

说了这么多废话,其实是想表达,如果想在项目中使用要谨慎,谨慎 ,再谨慎,不过我们可以从其中学习到很多以前我们从来没思考过的问题,甚至是一种全新的思维方式,有时候一些巧妙的实现,会让你感叹到计算机科学的神奇,也可以在你跳槽的时候成为你的加分项。

知识必备

需要你对Objective-C,block有比较清晰的了解,由于后面部分代码是由Swift实现,建议你掌握Swift,(由于Swift的特性,对于函数式编程更容易实现,也更好理解,毕竟你看写博文的作者几乎每个都掌握的,所以去学习吧。),如果你用过Swift的高阶函数,map,FlatMap,reduce,Filter等,会更加容易理解。

思想的建立

这部份主要奠定自己的函数式思想的建立,对函数(闭包,block)作为一等公民有新的理解。

这一部分可能学习的时间最久,需要的知识面也可能更广,也最容易让人放弃,因为你不知道你花了大把时间学习的是什么(jb玩意)。

不过坚持下来,你将会更加理解函数式编程,在学习接下来的ReactiveCocoa和RXSwift的时候只是把思想代码化。

首先来篇自己写的

本文让你对如何实现链式编程有个简单的认识。文中包括了Objective-C和Swift两个版本。

来自Mattt Thompson发布在 NShipster让你对引入RAC这类FRP(Functional Reactive Programming)框架对编程范式的改变有个直观的印象。

来自蘑菇街的limboy解释下RAC和FRP的关系。

简书上看到的文章,从最基本的Demo开始。

在这里直接推荐了两篇使用的文章。貌似会比较突然,但是作者觉得,Talk is cheap ,show me the code.(别装逼了,亮代码吧) 思想总归是要用代码来实现的,这部份内容偏实用,预计需要一周的时间才能掌握的差不多,可能你在学习的过程中,会遗忘,没关系,思想的建立是一步一步的,学习这些代码还是为了更好的建立思想(我也没指望你一遍掌握RAC),至于代码这篇文章,反正你是要看好几遍的,第一次忘了就忘了。

之前的文章都是 functional Programming,和reactive programming,这里偶然看到一篇响应式编程思想入门,强烈推荐。

这篇文章中讲到了RAC自带的debug插件,在学习RAC过程中的实践,以及一些技术博文链接。


在学习过程中我们可能经常会听见一些比较函数式风格的名词,比如monad,functor等,学到这里我找到了另外比较好的资料。

  1. 函数式 Swift
  2. Functor、Applicative 和 Monad
  3. Swift Functors, Applicatives, and Monads in Pictures英文版
  4. Functors, Applicatives, And Monads In Pictures英文版

第一本书籍可能花费的时间很长,不过这本书也是最有深度的,可以从中学到更多函数式的思想。

ReactiveCocoa2.x

非常不错的RAC入门教程

  1. ReactiveCocoa入门教程——第一部分对应的英文版
  2. ReactiveCocoa入门教程——第二部分
    对应的英文版

来自sunnyxx的博文。

  1. Reactive Cocoa Tutorial [1] = 神奇的Macros
  2. Reactive Cocoa Tutorial [2] = 百变RACStream
  3. Reactive Cocoa Tutorial [3] = RACSignal的巧克力工厂
  4. Reactive Cocoa Tutorial [4] = 只取所需的Filters

学了这么多来一篇RAC结构分享的文章


用了这么久的RAC,你可能不止一次听到冷热信号,也可能没有意识到有些什么是不对的,这里给出来自美团的对冷热信号的分析。

  1. RACSignal的Subscription深入分析 - 美团点评技术团队
  2. 细说ReactiveCocoa的冷信号与热信号(一)
  3. 细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号
  4. 细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号

美团博文最后给出了一个冷热信号可以转换的方法,但是没有讲清楚,这里有篇外国博文作为补充。


这里补充一篇对RAC的概述


RAC项目实战

  1. ReactiveCocoa2实战
  2. 这样好用的ReactiveCocoa,根本停不下来
  3. iOS开发下的函数响应式编程

如果你认认真真的学习到了这里,想必以及有了不错的进步,接下来我们可以看下RAC的源码实现了,看下这个庞然大物是如何实现这些神奇的功能的。

  1. RAC核心元素与信号流
  2. 剖析@weakify 和 @strongify
  3. ReactiveCocoa源码阅读之前序
  4. ReactiveCocoa源码阅读之RACSignal(一)
  5. ReactiveCocoa源码阅读之RACSignal(二)
  6. ReactiveCocoa源码阅读之bind函数
  7. ReactiveCocoa源码阅读之攻略flatten
  8. ReactiveCocoa源码阅读之switchToLatest/combineLatestWith
  9. ReactiveCocoa源码阅读之RACScheduler

MVVM With ReactiveCocoa

这部份主要讲了RAC在MVVM中的表现,以及如何利用RAC在MVVM中做数据绑定这项核心操作。

  1. MVVM与ReactiveCocoa的运用(Part1)
  2. MVVM Tutorial with ReactiveCocoa: Part 1/2
  3. MVVM与ReactiveCocoa的运用(Part2)
  4. MVVM Tutorial with ReactiveCocoa: Part 2/2
  5. MVVM With ReactiveCocoa

ReactiveCocoa4.x

在学习这部份的时候发现没有合适的入门教程,所以就自己摸索着翻译了部分。可以参看

  1. ReactiveCocoa-Swift部分入门指南-Signal
  2. ReactiveCocoa-Swift部分入门指南-SignalProducer

一份Reactive4文档翻译

  1. ReactiveCocoa 4 文档翻译目录

ReactiveCocoa4的详细讲解

  1. ReactiveCocoa 4 图解之一——事件(Event)
  2. ReactiveCocoa 4 图解之二——监听器(Observer)
  3. ReactiveCocoa 4 图解之三——存根(Disposable)
  4. [至于四作者并没给出,估计是挖的坑忘记填]()
  5. ReactiveCocoa 4 图解之五——信号(Signal)
  6. ReactiveCocoa 4 图解之六——信号发生器(SignalProducer)

RXSwift

使用自带教程入门

  1. RxSwift 入坑手册 Part0 - 基础概念
  2. RxSwift 入坑手册 Part1 - 示例实战

快速指南系列

  1. RxSwift快速指南(一)
  2. RxSwift快速指南(二)
  3. RxSwift快速指南(三)
  4. RxSwift快速指南(四)

速查表

文末给出了我自己整理的RAC和RXSwift速查表,毕竟谁也不可能记得住那么多方法和小技巧的。

学习交流

这里给出本人的iOS技术交流群,有兴趣探讨技术问题的小伙伴们可以加群交流

1群 173499350
2群 532084214

阅读更多