Arming 2016-01-05T00:02:42+08:00 cloudniw1@126.com 2016计划 2016-01-01T00:00:00+08:00 Arming http://mrmign.github.io/2016/01/01/2016-ji-hua 2015已经过去,心里想要干的事情没能如数完成,藏在心里就会老是给自己找借口开脱,这里要把2016要做的事情写下来,也算有个凭据,不能再容忍自己给自己找借口了。到了2016年末写总结时,再来看这篇Todo计划,会很自豪的。

读书

技术书

]]>
Google Protobuf原理学习 2015-12-31T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/31/google-protobuf-yuan-li-xue-xi Pb组包解包实现

Protobuf是Google开源的一套跨平台、跨语言的序列化协议。基于Protocbuf的数据组包方式有效减了网络数据传输的大小,减少流量消耗。

pb协议都是通过*.proto的文件进行描述的,该描述文件是非常通用,不限制针对哪种语言,通过Google提供的compiler可以选择编译的目标编程语言,如java,C++,Python等。这里以C++为例。通过protoc *.proto --cpp_out=dir命令就可以根据proto描述文件生成对应的C++文件*.pb.h*.pb.cc,然后引用相应的头文件就可以进行组包、解包了。

上面的做法是最基本最常用的,而且我们在使用pb协议的时候也都是这么做的。因为PbCodec不再需要生成C++的头文件了。通过描述文件生成的pb头文件给我们提供的主要方法就是一堆gettersetterserializeparse方法,这些方法确实方便了我们组包、解包。

Pb使用((T)([L]V))类似的格式,即Tag-Length-Value。每一个字段值都使用TLV的格式,最后将所有字段的TLV序列拼接为一个二进制字节流进行传输,收到字节流按TLV一个一个字段的解析出来。这里的Tag也可以叫做key,pb使用的格式抽象一下也可以看成是一个个的Key-Value对。

这里的Key也叫Tag是由什么组成的?我们知道在proto描述文件里message下的每个字段值都有一个field数字,表示它在message里是第几个值,我们称它为field_num;声明一个字段的时候还要指定该字段用来放什么类型的值,是可变长度还是固定长度等,这里的字段类型称为wire_type。好了,有了field_numwire_type我们就可以得到我们的key了,key = field_num << 3 | wire_type

Protobuf里对wireType声明下面几种类型:

  enum WireType {
    WIRETYPE_VARINT           = 0,
    WIRETYPE_FIXED64          = 1,
    WIRETYPE_LENGTH_DELIMITED = 2,
    WIRETYPE_START_GROUP      = 3,
    WIRETYPE_END_GROUP        = 4,
    WIRETYPE_FIXED32          = 5,
  };

wireType说明

Varint是一种比较特殊的编码方式,后面有详细介绍。FixedXX就是固定长度的。Length_Delimited是针对那些变长数据的如bytes,String,subMessage等。

Value的组成就是由数据长度和数据组成,但是长度可能没有,像对于数字类型的不需要长度,只有那些长度不确定的类型(bytes,string等)需要数据的真实长度才能正确解析。Protobuf的数组格式就是这样的。下面要简单分析下Protobuf的工作原理。

这里我们从具体的pb协议出发,逆向学习pb的原理,下面的图是来电协议oidb_65foidb_931里的几个message与Protobuf的类继承关系。

message继承图

每一条协议都在自己的命名空间中,pb的描述文件中定义的每一个message都会生成一个对应的类,从上面的类关系图中可以看到生成的类中65f协议中类的父类是MessageLite,而931协议中的类的父类是Message,Message又继承自MessageLiteMessage类扩充了什么功能呢?我们后面再说。这里最重要是搞明白协议描述文件里的每个message最后生成的类中都有序列化,反序列码的方法了。

看看下面协议中的序列化方法是如何实现的:

void ReqBody::SerializeWithCachedSizes(::google::protobuf::io::CodedOutputStream* output) const {
//optional cmd0x65f.SetMsgValidTimeReq msg_set_req = 1;
  if (has_msg_set_req()) {
		::google::protobuf::internal::WireFormatLite::WriteMessage(1, this->msg_set_req(),output);
	}
}
// optional cmd0x65f.SetMsgValidTimeReq msg_set_req = 1;
inline bool ReqBody::has_msg_set_req() const {
    return (_has_bits_[0] & 0x00000001u) != 0;
}

从上面代码中可以看出,当要将message进行序列化时,会向CodedOutputStream流中填写,大家自己看一个更复杂点的pb协议的message时,当进行序列化时,会逐一对message下的每个field进行判断,也就是类似上面中的has_msg_set_req(),如果有设置值就会进行写操作,每当对一个字段进行设置值时都会设置对应的标志位。::google::protobuf::uint32 _has_bits_[(1 + 31) / 32];对于每一个message都会根据field的个数来生成一个这样的数组,用来标志每个字段的是否有值,同样在解包的时候也会有对应设置。假设message下有40个字段,则会有::google::protobuf::uint32 _has_bits_[(40 + 31) / 32];这样的数组。若对应字段有值,就调用WriteXXXX方法写入流中。这里会根据字段声明时的值类型调用不同的Write方法,因为在此处的field是另一个message,因此会调用WriteMessage方法,如果是uint32类型,刚会调用WriteUInt32方法,这些Write方法都是相似的。下面看WriteMessage实现:

void WireFormatLite::WriteMessage(int field_number,
                                  const MessageLite& value,
                                  io::CodedOutputStream* output) {
  WriteTag(field_number, WIRETYPE_LENGTH_DELIMITED, output);
  const int size = value.GetCachedSize();
  output->WriteVarint32(size);
  value.SerializeWithCachedSizes(output);
}

inline void WireFormatLite::WriteTag(int field_number, WireType type,
                                     io::CodedOutputStream* output) {
  output->WriteTag(MakeTag(field_number, type));
}

inline uint32 WireFormatLite::MakeTag(int field_number, WireType type) {
  return GOOGLE_PROTOBUF_WIRE_FORMAT_MAKE_TAG(field_number, type);
}

#define GOOGLE_PROTOBUF_WIRE_FORMAT_MAKE_TAG(FIELD_NUMBER, TYPE)                  \
  static_cast<uint32>(                                                   \
    ((FIELD_NUMBER) << ::google::protobuf::internal::WireFormatLite::kTagTypeBits) \
      | (TYPE))

WriteMessage的第一个参数是field_number也就是message中字段的序号,从前面也看到在调用的时候传的第一个参数的值是1,也就是field的序号。方法中首先调用WriteTag方法,从代码中可以看出Tag就是按照之前说的方法field_num << 3 | wire_type生成的。写完tag,因为WireType是变长的,因此要告诉数据长度,接着写数据长度output->WriteVarint32(size);,后面再写入数据。 这样message的序列化的核心工作就完成了,当然我们不会直接调用这里的ReqBody::SerializeWithCachedSizes方法来生成组包的数据buffer。我们调用的序列化方法是MessageLite::SerializeAsString()或是MessageLite::SerializeToString(string* output),然后这两个方法再经过层层调用到达我们的message的SerializeWithCachedSizes方法,最终得到序列化后的结果。

现在pb如何进行序列化我们已经知道了,现在看pb怎么进行反序列化。先上代码了解下:

bool RspBody::MergePartialFromCodedStream(::google::protobuf::io::CodedInputStream* input) {
#define DO_(EXPRESSION) if (!(EXPRESSION)) return false
    ::google::protobuf::uint32 tag;
    while ((tag = input->ReadTag()) != 0) {
        switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
                // optional cmd0x65f.SetMsgValidTimeRsp msg_set_rsp = 1;
            case 1: {
                if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
                    ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) {
                    DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(input, mutable_msg_set_rsp()));
                } else {
                    goto handle_uninterpreted;
                }
                if (input->ExpectAtEnd()) return true;
                break;
            }
                
            default: {
            handle_uninterpreted:
                if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
                    ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
                    return true;
                }
                DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag));
                break;
            }
        }
    }
    return true;
#undef DO_
}

上面的反序列化代码是针对特定message的,因此每个message都会有自己的反序列化实现。上面反序列化方法的参数是一个输入流,不用想也知道是一串二进制流,通过循环不停的读取tag,然后根据tag得到field_num,然后就是读值了。首先有没有这样的疑问,pb怎么知道读出来的WireType是对的呢,跟哪种类型进行比较呢?因为在根据描述文件生成这些方法的时候都知道每个字段的数据类型,也就知道在序列化时用的是哪个WireType了,同样也知道该调用哪个方法来读取值,并写入哪个字段。在搞明白了序列化操作后,反序列化就没有那么神秘了。

反序列化首先要读取Tag,但是这里我们看到的是一串二进制数据,在读取Tag时如何判断结束条件呢?有了这个疑问,我们就需要了解Protobuf的Varint编码,这是一种比较特殊的编码方式。

对于int32类型的数字,一般需要4个byte 来表示。但是采用 Varint,对于很小的int32 类型的数字,则可以用 1个byte来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。 Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010 下图演示了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。

Varint编码图

对Varint编码了解后,前面ReadTag时就是这样来判断结束条件的。但是大家有没有疑问,就是前面说的Tag(也叫Key)是按field_num << 3 | Wire_type生成的,这里field_num左移3位会不会导致数据丢失呢?这里就假设field_num是uint32类型的,左移3位依然能表示2^28个,相信在一个message里不会达到这么多字段吧,因此完全够用。而且对Tag进行Varint编码的是field_num << 3 | Wire_type这个结果。

题外篇

对于在proto描述文件中添加option optimize_for = LITE_RUNTIM到底有什么用呢?

当添加后在编译proto文件时编译器会使用libprotobuf-lite库而不是libprotobuf库,使用libprotobuf-lite我们的message生成的类会继承自MessageLite,如果使用libprotobuf,生成的类则继承自Message。然后Message又继承自MessageLite,并且添加了descriptor和reflection特性,这会导致生成的头文件中有支持这两个特性的相关代码,然后我们日常使用pb时根本用不到这两个特性,因此完全没有必要保留着他们。这也是为什么添加这个优化选项的原因了。对于这两个特性有什么功能大家可以自己去探索下。

这个原来是内部分享时写的,去除了部分内容,希望对于想要学习Pb的同学有所帮助。Pb的确是很优秀的序列化方法,值得深入研究学习。基于Pb也可以创造其他的一些工具,数据存储等方案。

本文是删减版本,如果有什么不清楚的地方,请留言进一步沟通。

]]>
2015总结 2015-12-31T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/31/2015-zong-jie 2016年的1月1日马上就要到来了,过去的几年每当到这个时候就会看到各种年底总结新年寄望。看着他们的一篇篇总结与新一年的奋斗目标,心里也说不出什么感觉。写出新的一年奋斗目标的那些同学现在已经当上CEO,迎娶了白富美,走上人生巅峰了吧。鉴于此,我也要为了同样的目标对今年做个总结,为明年树立个目标。

2015.4拿到毕业证正式入职鹅厂,这不知不觉就过完了9个月,到了年末。入职后在原来团队待了2个来月就到了新的团队,不过在新团队氛围还是挺不错的。在今年主要是熟悉业务逻辑,了解老的代码,然后做新需求,小组内做了3次分享,但是都是kpi之外的,根据当时的需要自愿分享的。

一个是关于iOS的私有API的分享,关于私有API在去年的时候就开始研究了,不过当时由于对一些底层知识如Mach-O了解不多,没能深入的把工具做好,只是当时就针对特定APP进行了扫描,而且结果也不是很理想,现在也底层知识有了相对比较全面的了解了,又没有时间去搞这一块了,这次分享也基本是把之前做的一些研究给小组同学普及了一下,进行过越狱开发的基本也都有所涉及。说到私有API,就得提下越狱开发,今年7月左右买了逆向开发的第二版书,计算全面地学习下逆向工程的,可是今年过完了书也没有完全看完,逆向过的应用也只有几款,而且都是简单的功能(去广告)逆向,再复杂点的就没有搞了,这些没有完成的目标都是由于自己的拖延导致的,2016年克服的第一大问题就是拖延。逆向的知识还是要继续学习的,尝试逆向更复杂的功能。刚开始学习的时候就想着最后能否自己写个抢红包插件,看来今年要呵呵了。。。虽然网上有人写了Android版本的,但是iOS的木有啊,自己动手,丰衣足食。还是要学习。关于逆向,今年下半年了解了很多逆向相关的工具,有些开源的也要去研究学习下,如MobileSubstrate这个最基本的,Cycript。代码还是要多看,多写。

第二个就是学习了下JSPatch的实现,然后与小组成员一起分享学习了下。JSPatch是一个动态替换应用内方法的框架。代码1400多行,不多,从最初的方案到现在的方案,作者也是花了很多功夫,踩了不少坑的。实现原理充分利用了Objective-C的Runtime特性,对于学习理解Runtime也是很有帮助的。JSPatch实现的出发点很好,利用JavaScriptCore.framework这个iOS系统本身提供的库,不像那些使用Wax框架的还是自己添加解析器,管理运行环境等。JSPatch让我有了一个想法,就是在越狱开发上基于JSPatch可以搭建一个工具平台,因为它本身就是用来动态替换App内的方法的,越狱写的Tweak也是同样的来替换原来的方法,但是需要编译安装,如果通过JavaScript来实现就不需要提前编译,这样的工具与越狱平台上的Flex非常像,但是对于Flex的实现原理还没有研究,也不太清楚它下载的那些插件是什么格式的。但是如果基于JSPatch,插件必定是用JS写的。该想法目前还没有付诸实施。希望2016能够实现此想法。

鉴于上面说的两点,私有API和越狱开发是分不开的,越狱开发特别是针对系统应用来开发Tweak,肯定要搞清楚系统的API,这里就不泛大量私有API。而对于第三方应用进行越狱开发,也要对于应用的类有所了解才行,但是随着iOS系统的升级,苹果也一直在修复系统漏洞,加强系统安全,有时dump应用的头文件已经不那么好使了。但是,应用要运行起来必须要加载到内存中必须要解密后才可以正常运行,因此通过动态方法可以比较容易得到应用内的类,就像RuntimeBrowser提供的功能一样,我们可以对它进行改造,做成一个动态库,每当应用运行起来后就可以dump内部的类,根据RuntimeBroser的做法,可进一步做出类的关系图,将继承关系,协议的Confirm关系通过图的形式表现出来,就可以清晰的表示出来了。更完美的是可以进行模糊查找某些类的关系图。

第三个就是关于Protobuf的分享,虽然之前一直在用,但是一直没有研究它的工作原理,为什么会这么优秀。通过本次分享对它的格式及原理都有了了解。而且在查资料的过程中也发现了很多基于pb的其他的优秀工具,可见pb的工作原理是非常优秀的。下一步还要更多研究下相关内容,希望能把代码完整的看一遍。

上面三点是主要做的一些事情,其他的时间就要还是在做需求相关的工作,接触消息相关逻辑,期间也做了第一款动画效果,也花费了些时间去学习了解,说到这里还有两个话题需要分享,一个是消息逻辑,另一个是动画相关知识分享。

走入社会也要不断学习呀,今年买了几本书,但是都还没有看完,其中有几本是非技术相关的,《沟通的艺术》、《学会提问》、《人性的弱点》都只是翻了一部分,技术书倒是读了一些,主要是iOS相关的,因为苹果去年也发布了新的编程语言Swift,虽然去年抽空学习了下,由于工作中主要还是Objective-C,长时间不用也就淡忘了,今年都发布2.0了,又重新学习了下,对比起来代码真的少好多,希望在新的一年里也能用Swift来写款小软件吧。技术书方面希望接下来能把那几本经典的都再读一遍,《iOS X Pushing The Limit6,7》,iOS的设计模式,官方文档多读,非技术书方面希望把今年没读完的先读完,然后再读些心理学方面的东西。

本来写这篇年末总结的时候是想着一气呵成的,竟然又拖拖拉拉的3天假期才写完,而且前后衔接不连贯,先这样吧,第一次写这样的总结,越写越流利的(自我安慰下~)。

]]>
Swift学习笔记5 2015-12-14T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/14/swift-xue-xi-bi-ji-5 Memory Management

Weak Reference

  • only an Optional reference can be marked as weak.
  • the reference must be a var reference.

对于循环引用的情况,只需要其中一者是weak的就可以,规则

The one that is not the “owner” will have a weak reference to its “owner”.

Unowned Reference

该引用在下面的例子中特别有用,如果一个对象没有对其他对象的引用就不能存在,但是这个引用又不需要一直存在。 就像人与小狗,小狗必须有主人,但是人不一定有小狗。 可以把小狗的主人声明为unowned.

Unowned reference可以是Optional也可以是let的。但是用unowned是有风险的,有可能后面访问对象时为空。

只有对类类型的引用才能声明为weakunowned

匿名函数中的weak与unowned引用

class FunctionHolder {
    var function : (Void -> Void)?
    deinit {
        print("deinit")
    }
}
func testFunctionHolder() {
    let f = FunctionHolder()
    f.function = {
        [weak f] in
        print(f)
    }
}

传入匿名函数的引用是Optional的,通常是进行一个weak-strong转换

class FunctionHolder {
    var function : (Void -> Void)?
    deinit {
        print("deinit")
    }
}
func testFunctionHolder() {
    let f = FunctionHolder()
    f.function = {
        [weak f] in //weak
        guard let f = f else { return }
        print(f) //strong
    }
}

unowned用的最多的场景还是针对self引用的情况。

class MyDropBounceAndRollBehavior : UIDynamicBehavior {
    let v : UIView
    init(view v:UIView) {
        self.v = v
        super.init()
    }
    override func willMoveToAnimator(dynamicAnimator: UIDynamicAnimator?) {
        if dynamicAnimator == nil { return }
        let sup = self.v.superview!
        let grav = UIGravityBehavior()
        grav.action = {
            [unowned self] in
            let items = dynamicAnimator?.itemsInRect(sup.bounds) as! [UIView]
            if items.indexOf(self.v) == nil {
                dynamicAnimator?.removeBehavior(self)
                self.v.removeFromSuperview()
            }
        }
        self.addChildBehavior(grav)
        grav.addItem(self.v)
    }
}

对于协议类型的引用只有当它是类协议(标记为@objc或class)的时候才可以声明weakunowned

Obejctive-C中声明的协议已经隐式标记为@objc,因此是类协议。

]]>
Swift学习笔记4 2015-12-14T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/14/swift-xue-xi-bi-ji-4 Extension

Extension声明只能在文件的最顶层。也就是说不能嵌套在其他类型中声明。 extension typeName [: protocols ] {}

限制:

  • extension不能重写已有的成员(但是你可以重载overload已有的方法)
  • 不能声明存储属性,但是可以声明计算属性
  • 类型的扩展不能声明designated initializer或deintializer,但是可以声明convenience initializer.

扩展可以用来组织代码,可以将要实现的协议用扩展来实现。

当扩展Swift的struct时,可以声明initializer,并且保留隐式的初始化方法

struct Digit {
    var num : Int
}

extension Digit {
    init() {
        self.init(num: 2)
    }
}

Extending Protocols

当扩展协议时,你可以为协议添加方法和属性,跟协议声明不同,这些方法和属性不是必须要被adopter实现的。他们是真实的方法和属性,会被实现该协议的类型继承。

protocol Flier {
}
extension Flier {
    func fly() {
        print("flap flap flap")
    }
}
struct Bird:Flier {
}
let b = Bird()
b.fly() //flap flap flap

struct Insect : Flier {
    func fly() {
        print("whirr")
    }
}
let i = Insect()
i.fly() // whirr

Warning: 这种继承不是多态的 adopter的实现不是重写,它是另一个实现。 如果要让它像多态继承,必须在原始的protocol中声明为必须实现的方法。

Extending Generics

extension Array where Element:Comparable {

}

Umbrella Types

AnyObject

它实际上是一个protocol,它有特殊特征,所有的类类型都自动conform to it.

AnyObject是swift版本的id

AnyClass

Any

The Any type is a type alias for an empty protocol that is automatically adopted by all types.

Error

在Swift中,an error必须实现ErrorType协议的对象,它只有一个string的_domain和一个Int的_code属性。

错误机制有两个阶段需要考虑:

  • throwing an error.会打断当前的执行路径,会将一个错误对象丢给错误处理机制
  • catching an error. 捕获错误异常做出响应,代码继续在捕获的地方执行。

只能在下面两处地方throw:

  • In the do block of a do...catch construct
do {
... //throw can happen here
} catch errortype {
..
} catch {
..
}
  • In a function marked throws
enum NotLongEnough : ErrorType {
    case ISaidLongMeantLong
}
func giveMeALongStinrg(s:String) throws {
    if s.characters.count < 5 {
        throw NotLongEnough.ISaidLongMeantLong
    }
    print("thanks for the string")
}

在函数声明中添加throws会创建一个新的函数类型。 上面函数giveMeALongStinrg的类型不是(String)->()而是String)throws ->()

对于调用有throws的函数也有要求:调用者在函数前必须加try

A function called with try can throw. 也就是说这里的try跟throw是一样的,因此必须在do..catch块或是标记为throws的函数中。

func stringTest() {
    do {
        try giveMeALongStinrg("is this long enough")
    } catch {
        print("I guess it wasn't long enough")
    }
}

如果你非常确定一个函数可以throw但实际上没有throw,你可以调用时用try!来代替try,可以在任意地方使用try!,这样就不需要捕获可能的异常了。但是如果你错了就可能导致程序crash

trytry!之间的是try?,你可以在任意地方使用try?而不需要捕获异常。但是,如果有异常抛出不会crash,它会返回nil。因此当表达式有返回值的时候try?非常适用。如果没有异常抛出,返回结果会是Optional。

Defer

The purpose of the defer statement is to ensure that a certain block of code will be executed at the time of the path of execution flows out of the current scope, no matter how.

func doSomethingTimeConsuming() {
    UIApplication.sharedApplication().beginIgnoreInteractionEvents()
    defer {
        UIApplication.sharedApplication().endIgnoreInteractionEvents()
    }
    // ... do stuff...
    if someThingHappend {
        return 
    }
    // ... do more stuff
}

defer语句的作用就是不管执行过程中发生了什么,都会执行defer语句块代码。 比较常见的例子,读写文件时如果中间发生错误, 要关闭文件句柄,就可以通过defer实现。

如果当前范围内有多个defer块,将会按照他们出现顺序的逆序执行。实际上是有一个defer栈,每一个后续的defer块都放在栈顶,要执行defer块时,依次从醉顶取出并执行。

Aborting

故意地让程序崩溃。

一种方式就是用全局函数fatalError实现。

required init?(coer aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

包含fatalError的初始化方法没必要初始化任何属性,因为 fatalError是用@noreturn属性声明的。

有返回值的函数当遇到fatalError调用的时候不用返回值。

Guard

A guard statement is an if statement where you must exit early in response to failure of the condition.

guard condition else {
    statements
    exit
}

The else block must jump out of the current scope.

guard case let .Number(n) = err else {return}

let s = // ... some optional
guard let s = s else {return} //compile error

上面代码编译不过是因为guard letif letwhile let不一样,它声明的变量范围不是嵌套。

Operators

  • infix (默认的,可省略)
  • prefix
  • postfix

操作符声明和对应的函数声明必须在文件的最顶层(top level of a file)

infix operator ^^ {}
func ^^(lhs:Int, rhs:Int) -> Int {
    var result = lhs
    for _ in 1 ..<rhs { result *= lhs}
    return result
}

当定义操作符时,如果你要考虑在表达式中该操作符与其他操作符的优先级顺序你要添加precedenceassociativity说明。

Privacy

  • internal 默认地,在module内可见
  • private 只在被包含的文件可见,在文件层级
  • public
]]>
Swift学习笔记3 2015-12-13T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/13/swift-xue-xi-bi-ji-3 Casting

as!进行转换时可能会出错,可以先用is进行判断,然后再转。另一种方法就是用as?来转,结果是Optional的。

Type Reference

实例可以通过dynamicType方法来获取它的类型。

class Dog {
    class var whatDogSay:String {
        return "woof"
    }
    fun bark() {
        print(self.dynamicType.whatDogSay)
    }
}

dynaimicType而不是硬编码用爆大锅是因为它遵循多态。

func dogMakerAndNamer(whattye:Dog.Type) -> Dog {
    let d = whattype.init(name:"Fido")
    return d
}

因为这里不确定是否Dog的每个子类型都实现了init(name:)初始化方法,因此会编译失败。 为了保证每个类型都有init(name:)初始化方法,我们需要添加required关键字。

Type Self

class Dog {
    var name : String
    required init(name: String) {
        self.name = name
    }
    class fun makeAndName() -> Self {
        let d = self.init(name:"Fido")
        return d
    }
}

class NoisyDog: Dog {
}
  • .dynamicType sent to an instance
  • .Type sent to a type
  • .self sent to a type: the type.如果需要Dog.Type, 可以传Dog.self
  • self 在实例代码中表示实例;在静态或类代码中表示这个类型
  • Self 在方法声明中,指定返回类型

Protocols

A protocol is an object type, but there are no protocol objects.

一个对象类型实现了一个协议也就是实现了协议中的属性和方法。

Any type — enum, struct, class, another protocol—can adopt protocol.

swift中最有用的一个协议就是CustomStringConvertible, 该协议要求我们实现description属性。

Declaring a protocol

协议声明只能在文件的最顶层。协议可以包含:

  • Properties. 协议中的属性声明用var, name, type {get} 或者 {get set}。如果是{get},实现协议的属性也可以是writable,如果是{get set},实现协议的属性就不能是保读的。用static声明static/class属性,类实现者可以实现为class属性,也可以是static的。
  • Methods. 协议中方法的声明没有方法体,没有{}。用static声明static/class方法,类实现时可以实现为class方法。
    • 如果enum/struct实现的方法需要声明为mutating,协议必须指定mutating。如果协议没有指定,实现者不能添加mutating
    • 如果协议有指定mutating,实现者可以省略
  • Type alias.
  • Protocol adoption.

Optional Protocol Members

在声明协议为@objc时,Swfit允许有可选的协议成员。

@objc protocol Flier {
    optional var song: String {get}
    optioanl func sing()
}

只有类能实现这样的协议,而且该特性只有在类是NSObject的子类时才管用,或者是可选的成员标识为@objc

class Bird : Flier {
    @objc func sing() {
        print("tweet")
    }
}

如果可选方法有返回值,返回类型是Optional的

Class Protocol

A protoco declared with keyword class after the colon after its name is a class protocol ,meanig that it can be adopted only by class object type.

protocol SecondViewControllerDelegate: class {
    func acceptData(data: AnyObject!)
}

如果协议已经标记为@objc就没必要再声明class,@objc也暗示这是个类协议。

class SecondViewController: UIViewController {
    weak var delegate : SecondViewControllerDelegate?
    ...
}

只有类实例才会有特殊的内存管理。

Implicitly Required Initializer

An initializer declared in a protocol is implicitly required, and the class is forced to make that requirement explicit.

protocol Flier }
    init()
}
class Bird : Flier {
    required init() {}
}

如果Bird标记为final就没有必要把init标记为required

Generics

在编译时确定真正的类型。

Generic declaration

  • Generic protocol with Self.在协议中,用Self把协议变成一个generic.Self是实现协议的类型。如下面的协议,如果是Bird实现了该协议,在实现flockTogetherWith方法时需要声明参数类型为Bird
protocol Flier {
    func flockTogetherWith(f:Self)
}
  • Generic protocol with empty type alias.协议可以声明一个type alias但不指定代表什么类型,也就是没有包含赋值符号,这样就把协议变成了generic。实现协议的要声明一个具体的类型
  • Generic functions.
func take<T>(t:T) -> T
{
    return t
}
  • Generic object types
struct Something<T> {
    var first: T
    var second: T
}

Type Constraints

You can limit the types that eligible to be used for resolving a particular placeholder. The simplest form of type constraint is to put a colon and type name after the placeholder’s name when it first appears.

protocol Flier {
    typealias Other : Flier
    func flockTogetherWith(f:Other)
}

上面的代码是不合法的,protocol不能用自己作为类型限制。

protocol Superfiler {}
protocol Flier : Superflier {
    typealias Other : Superflier
    func flockTogetherWith(f:Other)
}

上面通过声明另一个protocol,让Flier adopt,并且限制Other为该Protocol。

Explicit Specialization

Generic protocol with associated type

protocol Flier {
    typealias Other
}
struct Bird: Flier {
    typealias Other = String
}

Generic object type

class Dog<T> {
    var name : T?
}
let d = Dog<String>()

class NoisyDog<T> : Dog<T> {}

class NoisyDog : Dog<String> {}

Associated Type Chain

protocol Wieldable {}
struct Sword : Wieldable {}
struct Bow : Wieldable {}

protocol Superfighter {
    typealias Weapon : Wieldable
}
protocol Fighter : Superfighter {
    typealias Enemy : Superfighter
    func steal(weapon:Self.Enemy.Weapon, from:Self.Enemy)
}
struct Soldier : Fighter {
    typealias Weapon = Sword
    typealias Enemy = Archer
    func steal(weapon:Bow, from:Archer) {
    }
}
struct Archer : Fighter {
    typealias Weapon = Bow
    typealias Enemy = Soldier
    func steal(weapon: Sword, from:Soldier) {
    }
}
]]>
swift学习笔记-2 2015-12-07T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/07/swift-xue-xi-bi-ji-2 Object Types

函数参数可以有默认值

property必须在声明时或是在初始化方法中被初始化

Referring to self

在实例属性没有初始化完成前,初始化方法不能显式或隐匿的引用self(An initializer may not refer to self, explicitly or implicitly, until all instance properties have been initialized).

struct Cat {
	var name: String
	var license: Int
	init(name:String, license:Int) {
		self.name = name
		meow() //compile error
		self.license = license
	}
	func meow() {
		print("meow")
	}
}

Delegating initializer

An intializer that calls another initializer is called a delegating initializer.

A delegating initailzer cannot set an immutable property(a let variable).因为只有在调用了其他初始化方法后才能引用属性,这个时候实例已经完全初始化了,已经无法再初始化let的属性了。

struct Digit {
	var number: Int
	var meaningOfLife: Bool
	//let meaningOfLife :Bool
	init(number:Int) {
		self.number = number
		self.meaningOfLife = false
	}
	init() {
		self.init(number:43)
		self.mearningOfLife = true //如果meaningOfLife声明为let这里就编译错误了
	}
}

Failable initailizer

有可能返回nil的初始化方法。An initializer can return an Optional wrapping the new instance.

calss Dog {
	let name: String
	let license: Int
	init!(name:String, license:Int) {
		self.name = name
		self.license = license
		if name.isEmpty {
			return nil
		}
		if license <= 0 {
			return nil
		}
	}
}

上面的返回值是Dog!, the Optional is implicitly unwrapped. 可以直接使用返回的结果,但是如果返回nil, 直接使用就会导致crash

let fido = Dog(name:"", license:0)
let name = fido.name //crash

UIImage的初始化方法init?(named:)也是failable initializer,它没有implicitly unwrapped,所以返回是UIImage?,在使用前必须unwrapped.

Properties

  • 有固定的类型
  • 可以是var或let
  • 可以是stored或computed
  • 可以有setter observer
  • 实例属性可以声明为lazy

存储型(stored)的实例属性必须有初始值(可以在声明时赋,也可以在初始化方法中赋值)。在属性初始化时不会调用Setter observer。

初始化属性的代码不能获取实例属性也不能调用实例方法,因为会显式或隐匿的引用到self,但是在初始化时还没有self

class Moi {
	let first = "mat"
	let last = "Nur"
	let whole = self.first + " " + self.last //compile error
	let who : String { //改用计算型的
		return self.first + " " + self.last
	}
	lazy var who2: String = self.first + " " + self.last
}

上面在初始化时访问了其他属性导致编译错误,可以改为用计算型的,因为计算型的不会真正计算直到self存在。

另外,还可以通过声明whole为lazy来解决。同样只有在self存在的时候才执行。

同样,计算型的和lazy的可以调用实例方法。

属性的初始化方法可以有多行代码,你可以定义执行匿名函数。如果代码有访问其他的属性或是实例方法,必须声明为lazy

class Moi {
	let first = "mat"
	let last = "Nur"
	lazy var whole2: String = {
        var s = self.first
        s.appendContentsOf(" ")
        s.appendContentsOf(self.last)
        return s
    }()
}

静态属性初始化时可以引用其他静态属性,因为静态属性的初始化是lazy的。

Methods

静态方法和类方法是通过type来访问的,self也就是type.

静态方法和类方法不能引用实例,因为根本就没有实例。因此静态方法和类方法不能直接引用任何实例属性和调用实例方法。反过来,实例方法可以访问静态属性和类属性,同样也可以调用静态方法和类方法。

关于实例方法的一个私密

实例方法实际上是静态/类方法

下面的代码是合法的

class MyClass {
	var s = ""
	func store(s:String) {
		self.s = s
	}
}
let m = MyClass()
let f = MyClass.store(m)

store是一个实例方法,但是可以按类方法的方式来调用,参数是该类的一个实例。

原因是An instance method is actually a curried static/class method composed of two functions - one function that takes an instance, and another function that takes the parameters of the instance method.

因此,上面代码执行后f是第二种函数,can be called as a way of passing a parameter to the store method of the instance m.

f("howdy")
print(m.s) //howdy

Enums

可以用rawValue:的实例化方法来初始化有初始值的enum.let type = Filter(rawValue:"Album"),因为这里给的raw value可能没有对应的case,因此这是个failable initializer,返回值是Optional的。

Enum Property

enum可以有实例属性和静态属性,但是enum的实例属性不能是存储型的(stored),因为如果相同case的两个实例如果存储了不同的实例属性值,它们就不再相同了。computed的属性是可以的。

Struct

自动会有一个无参的init(),但是如果显式地添加了你自己的初始化方法,就不能再访问init()了,但是可以添加init()方法。

如果结构体有存储型属性但是没有显式的初始化方法,就会有一个根据实例属性衍生来的隐式初始化方法。

struct Digit {
	var number : Int
	var str : String
}

let d = Digit(number:2, str:"strut")

如果实例方法要设置属性值,该方法必须标识为mutating,调用该方法的实例必须是var类型的。

Struct As Namespace

struct Default {
	static let Rows = "Rows"
	static let Columns = "Columns"
}

many Objective-C enums are bridged to Swift as this kind of Struct

Classes

在Objective-C中,类是唯一的对象类型。Some built-in Swift struct are magically bridged to Objective-C class types, but your custom struct types dont’t have that magic. 这也是在与OC和Cocoa交互时,声明类而不是结构体的主要原因。

递归引用

值类型与引用类型的区别:值类型不能结构上递归,也就是值类型的实例属性不能是相同类型的实例,如下代码就会编译失败

struct Dog {
    var puppy : Dog?
}

In Swift 2.0 an enum case’s associated value can be an instance of that enum, provided the case( or the entire enum)is marked indirect

enum Node {
    case None(Int)
    indirect case Left(Int, Node)
    indirect case Right(Int, Node)
    indirect case Both(Int, Node, Node)
}

子类可以继承父类的属性,也可以添加自己的属性,也可覆盖继承来的属性。

  • 通过final声明类可以阻止子类继承自该类。
  • 同样也可以用final防止类成员被子类重写

Class Initializer

  • Implicit initializer.如果一个类没有存储属性,亦或存储属性在声明时就初始化了,并且没有显式的初始化方法,就会有一个隐匿的初始化方法init()
  • Designated initializer.如果一个类有存储属性并且在声明时没有进行初始化,该类必须至少有一个designated initializer,当类初始化的时候所有的存储属性必须被初始化了。
  • Convenience initializer.用convenience修饰。必须有self.init(...)调用。在当前类中,必须调用一个designated initializer或是调用另一个convenience初始化方法。
class Dog {
	var name : String
	var license : Int
	inti(name: String, license: Int) {
		self.name = name
		self.license = license
	}
	convenience init(license: Int) {
		self.init(name:"Fido", license:license)
	}
	convenience init() {
		self.init(license:1)
	}
}

Subclass initializer

  • No declared initializer. 初始化方法由父类继承来的组成
  • Convenience initializer only.
  • Designated initializer. 不再有继承的初始化方法了!显式的designated initializer 阻止了初始化方法的继承。现在子类有的唯一的初始化方法就是显式定义的。子类的designated initializer必须做下面的事:
    1. 必须保证子类所有的属性被初始化
    2. 必须调用super.init(...),而且该方法必须是父类的designated initializer
    3. 然后才能使用self来调用实例方法,或访问继承来的属性。

Override initializer

  • 如果子类初始化方法的签名与父类的convenience初始化方法相同,必须也是convenience的,并且不要标识override
  • 如果子类初始化方法的签名与父类的designated初始化方法相同,可以是designated或是convenience初始化方法,并且必须标识为override

Failable initializer

A failable initializer that returns an implicitly umwrapped Optional(init!) is treated just like a normal initializer(init) for purpose of overriding and delegation.对于返回init?的failable initializer,有其他的限制:

  • init可以重写init?,反过来不行
  • init?可以调用init
  • init可以通过init来调用init?,将返回结果解包
class A:NSObject {
    init?(ok:Bool) {
        super.init() // init? call init
    }
}
class B:A {
    override init(ok:Bool) { // init override init?
        super.init(ok:ok)!   // init call init? using "!"
    }
}

Required intializer

如果初始化方法标识为required,子类不能少了该初始化方法。

class Dog {
    var name: String
    required init(name:String) {
        self.name = name
    }
}
class NosiyDog:Dog {
    var obedient = false
    init(obedient:Bool) {
        self.obedient = obedient
        super.init(name:"Fido")
    }
    required init(name:String) {
        super.init(name:name)
    }
}

从上面代码中看到我们重写的required初始化方法没有标识为override,但是标识为了required,因此可以保证该要求可以一直往子类传。

Class Deinitializer

方法名是deinit。如果一个类有父类,子类的deinitializer方法先调用,再调用父类的。

Class Properties and Methods

子类可以重写继承的属性,但是必须与要继承的属性有相同的名字和类型,并且必须标识为override

  • 如果父类属性是可写的(存储属性或是有setter方法的计算属性),子类的重写可以为属性添加setter observer
  • 子类可以重写为计算属性,但是:
    • 如果父类是存储属性,子类重写为计算属性必须同时有getter和setter方法
    • 如果父类属性是计算的,子类重写的计算属性必须重新实现所有父类的accessors。如果父类的属性是只读的(只有getter方法),子类重写可以添加setter方法。

重写的属性的方法可以用super来访问被继承的属性。

staticclass成员都会被子类继承,并且也是static或是class成员。

从程序员角度看static方法与class方法的主要不同是static方法不能重写。

静态属性与类属性之间的区别也差不多,非常明显的就是静态属性可以是stored,类属性只能是computed.

class Dog {
    class var whatDogSay : String {
        return "woof"
    }
    func bark () {
        print(Dog.whatDogSay)
    }
}

calss NosiyDog :Dog {
    override static var whatDogSay : String {
        return "WOOF"
    }
}

上面代码中子类继承了whatDogSay并且重写为类属性或是静态属性。但是即使重写为static类型的,也不能是存储属性。

]]>
swift学习笔记 2015-12-01T00:00:00+08:00 Arming http://mrmign.github.io/2015/12/01/swift-xue-xi-bi-ji Swift有三种类型的对象: class, struct, enum

Function Parameters and Return Value

Void(函数没有返回值时返回的类型)在Swift中是真实的类型,没有返回任何值的函数实际返回的就是Void类型,也可以用()来表示。 return Void,return (),return 是一个效果。

func f() -> Void {}
func f() -> () {}
func f() {}

上面三个方法都是没有返回值的。

函数签名(Function Signature)

利用参数与返回值确定函数签名,例如(Int, Int) -> Int。对于没有参数与返回值的函数可以用Void -> Void,() -> (), Void -> (), () -> Void表示,为了一致,还是优先使用前两种。

External Parameter Names

作用:

  • 声明每个参数的目的
  • 区分函数。两个函数名字和签名可以相同,但是外部参数名不能相同
  • 帮助Swift与OC,Cocoa交互

除了第一个参数,剩下的所有的参数默认自动是外部参数。

func say(s:String, times: Int) {}

//调用
say("hi", times:3)

如果不想要外部参数,在前面加_,像下面这样:

func say(s:String, _ times: Int) {}

//调用
say("hi", 3)

一个Swift函数的名字是括号前面的名字加上外部参数的名字,如上面的say(_:times:)say(_:_:)

Overloading

两个函数如果有相同的signature但是外部参数名字不同不能说是重载;因为他们外部参数名称不同,因此他们是两个函数名不相同的函数。

Default Parameter Values

func say(s: String, times: Int = 1) {}

可以用say("hi")say("hi", times:3)来调用该函数。

Ignored Parameters

func say(s:String, times:Int, loudly _:Bool) {}
//调用
say("hi",times:3,true)

func say(s:String, times:Int, _:Int){}
//调用
say("hi", 3, true)

当参数局部名称是_时在函数体内无法读取该参数值。

Modifiable Parameter

func say(s:String, times:Int, var loudly: Bool){}

默认情况下函数参数隐式声明为常量(let),不能进行赋值。如果需要在函数体中对参数进行赋值,需要显式声明为var。虽然声明为var后可以进行赋值,但是不能修改函数体外部原来的值。如果要修改传递参数的原始的值,可以按下面操作:

  1. 把想要修改的参数声明为inout
  2. 在调用函数时,想让函数修改其值的变量必须声明为var
  3. 不要用变量来当参数,需要传递它的地址,在变量名前加&
func removeFromeString(inout s:String, character c:Character){}

var s = "hello"
removeFromString(&s, "l")

UnsafeMutablePointer

当需要与OC打交道而不是Swift时,需要使用UnsafeMutablePointer 而不是inout

func CGRectDivide(rect: CGRect, _ slice: UnsafeMutablePointer<CGRect>, _ remainder: UnsafeMutablePointer<CGRect>, _ amount: CGFloat, _ edge: CGRectEdge)

var arrow = CGRectZero
var body = CGRectZero
CGRectDivide(rect, &arrow, &body, Arrow.ARHEIGHT, .MinYEdge)

当参数是class类型时,不需要声明为inout,因为class类型是引用类型的,其他的都是值类型。

Function As Value

函数要作为值需要有类型,函数的类型就是它的signature.

func doThis(f:()->()) {
	f()
}
func whatTodo() {
	print("I did it")
}
doThis(whatTodo)

可以使用typealis给某个函数类型一个名字,如typealias VoidVoidFunc = () -> ()

Anonymous Functions

func f(finished:Bool) {
	print("finished:\(finished)")
}

{
	(finished:Bool)->() in
	print("finished:\(finished)")
}
匿名函数的使用:
UIView.animationWithDuration(0.4, animations: {
	()->() in
	self.frame.origin.y += 20
	}, completion: {
		(finished: Bool) -> () in
		print("finished:\(finished)")
})

省略返回值

UIView.animationWithDuration(0.4, animations: {
	() in
	self.frame.origin.y += 20
	}, completion: {
		(finished: Bool) in
		print("finished:\(finished)")
})

当没有参数时省略in

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}, completion: {
		(finished: Bool) in
		print("finished:\(finished)")
})

省略参数类型

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}, completion: {
		(finished) in
		print("finished:\(finished)")
})

省略括号

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}, completion: {
		finished in
		print("finished:\(finished)")
})

即使有参数也省略in行。如果返回值可以省略,并且如果编译器知道参数类型,你可以省略in行,在匿名函数体内直接用$0,$1,..引用对应的参数。

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}, completion: {
		print("finished:\($0)")
})

省略参数的名字。如果匿名函数体不需要引用参数,可以用_在in行替换参数列表,如果不需要引用任何参数,可以只用一个_来替换整个参数列表。

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}, completion: {
		_ in
		print("finished:\(finished)")
})

不能同时省略in行也不通过magic name引用参数!!

省略函数参数名。如果在调用函数时匿名函数作为最后一个参数,可以在倒数第二个参数闭合函数,将没有名称的匿名函数紧跟在后面。

UIView.animationWithDuration(0.4, animations: {
	self.frame.origin.y += 20
	}){
		_ in
		print("finished:\(finished)")
}

省略调用函数的括号。如果你用了尾函数语法,并且你调用的函数只有一个函数作为参数,你可以在调用时省略空括号。

func doThis(f:()->()) {
	f()
}
doThis {
	print("hi")
}

省略关键字return

func sayHi() -> String {
	return "Hi"
}
func performAndPrint(f:()->String) {
	let s = f()
	print(s)
}
performAndPrint {
	sayHi()
}

Closure

Swift函数就是闭包。

Curried Functions(柯里化函数)

func makeRoundedRectangleMaker(sz:CGSize, _ r:CGFloat) -> () -> UIImage {
	return {
		imageOfSize(sz) {
			let p = UIBezierPath(
				rounderRect: CGRect(origin:CGPointZero, size: sz),
				cornerRadius:r)
			p.stroke()
		}
	}
}

let maker = makeRoundedRectangleMaker(CGSizeMake(45,20),8)
func makeRoundedRectangleMaker(sz:CGSize) -> (CGFloat) -> UIImage {
	return {
		r in
		imageOfSize(sz) {
			let p = UIBezierPath(
				rounderRect: CGRect(origin:CGPointZero,size: sz),
				cornerRadius:r)
			p.stroke()
		}
	}
}

let maker = makeRoundedRectangleMaker(CGSizeMake(45,20))
let image = maker(8)
func makeRoundedRectangleMaker(sz:CGSize)(_ r:CGFloat) -> UIImage {
	return imageOfSize(sz) {
			let p = UIBezierPath(
				rounderRect: CGRect(origin:CGPointZero, size: sz),
				cornerRadius:r)
			p.stroke()
	}
}

let image = makeRoundedRectangleMaker(CGSizeMake(45,20))(8)

Variable

变量的类型不能改变。

Computed Initializer

let timed : Bool = {
	if val == 1 {
		return true
	} else {
		return false
	}
}()

定义并调用匿名函数通常是唯一合法的方式来计算实例属性的初始值。因为在初始化实例属性时不能调用实例的方法(此时还没有实例,你正在创建实例)。

Computed Variables

var now: String {
	get {
		return NSDate().description
	}
	set {
		print(newValue)
	}
}

注意:

  • 变量必须声明为var,类型必须显式声明
  • get 方法必须返回与变量类型一致的值
  • setter就像是有一个参数的函数,参数的局部名称默认是newValue,也可以修改该参数的局部名称set (val) {}
  • setter函数不是必需的,如果省略setter,变量就变成只读的,如果尝试赋值会报编译错误。在swift中,一个没有setter的计算变量是创建一个只读变量的主要方式
  • 一定要有getter!!,如果没有setter, get及后面的{}可以省略。这也是只读变量的合法声明。
var now: String {
	return NSDate().description
}

Setter Observer

与声明computed Variable类似。

var s = "whatever" {
	willSet {
		print(newValue)
	}
	didSet {
		print(oldValue)
	}
}

注意:

  • 变量必须是var,可以有初始值。
  • 默认情况下willSet接收新值,局部名为newValue,同样也可以改成其他名字
  • 默认情况下didSet接收旧的值,局部名为oldValue,同样也可以改为其他名字。新值已经存储了,在didSet里可以访问到,在didSet函数里修改为其他值也是合法的。

Setter Observer函数在初始化或是在didSet里修改值时不会调用,否则会死循环!

Computed Variable不能有setter observer!

Lazy Initialization

swift中有三种类型可以initialized lazily:

  • global variables. 自动lazy.全局变量的初始化是在第一次访问的时候进行的。通过dispatch_once进行保护,使初始化只进行一次且是线程安全的。
  • static properties. 静态属性行为与全局变量相似,原因也相同。(There are no stored class properties in Swift, so class properties can’t be initialized and thus can’t have lazy initialization.??这句是什么意思)
  • Instance properties. 实例属性默认不是lazy的,可以在声明时用lazy标记,属性必须声明为var不能是let。

Lazy initialization通常用来实现单例。

Tuple

tuple当作函数参数传递时必须是常量。

Optional

ImplicitlyUnwrappedOptional,也是optional的,但是它的值可以直接用。

func realStringExpecter(s:String){}
var str: ImplicitlyUnwrappedOptional<String> = "how"
realStringExpecter(str)

var str! = "how" 效果一样

An implicitly unwrapped optional is still an Optional !

explicitly unwrapping an Optional contains nothing will crash!

var stringMaybe : String?
let s = stringMaybe! //crash

Optional Chains

To send a message safely to an Optional that might be empty, you can unwrap the Optional optionally.

var str: String?
let upper = str?.uppercaseString

If any of them is unwrapped optionally, the entire expression produces an Optional wrapping the type.

An assignment into an Optional chain with optional unwrapping returns an Optional wrapping Void.

let ok : Void ? = self.window?.rootViewController = UIViewController()
if ok != nil {
	//it worked
}
]]>
JSPatch学习 2015-09-21T00:00:00+08:00 Arming http://mrmign.github.io/2015/09/21/jspatch-xue-xi iOS上播放消息提示音及振动总结 2015-09-21T00:00:00+08:00 Arming http://mrmign.github.io/2015/09/21/ios-shang-bo-fang-xiao-xi-ti-shi-yin-ji-zhen-dong-zong-jie 在QQ,微信中当收到新消息的时候会播放提示音或是振动,亦或是声音+振动,如何在自己的应用中实现同样的功能呢?

播放提示音

当应用在前台运行收到消息时,这个时候要播放消息的提示音了,如何播放直接上代码:

/* 播放完成回调 */
void AudioSoundPlayComplete(SystemSoundID soundid, void* clientData)
{
    AudioServicesRemoveSystemSoundCompletion(soundid);
    AudioServicesDisposeSystemSoundID(soundid);
    AlertSoundService *alerService = (__bridge AlertSoundService*)clientData;
    [alerService stopPlaySound:soundid];
}

- (SystemSoundID)playSound:(NSString *)soundName
{
    SystemSoundID ssid;
    NSString* pathName = nil; 
    pathName = [[NSBundle mainBundle]pathForResource:soundName ofType:nil];
    if (pathName) {
        //将NSString类型路径名转换为NSURL类型
        NSURL* pathUrl = [[NSURL alloc] initFileURLWithPath:pathName];
        if (pathUrl) {
            OSStatus err = AudioServicesCreateSystemSoundID((__bridge CFURLRef)pathUrl, &ssid);
            if (err == kAudioServicesNoError) {
                AudioServicesAddSystemSoundCompletion(ssid, NULL, NULL, AudioSoundPlayComplete,  (__bridge void*)self);
                AudioServicesPlaySystemSound(ssid);
                return ssid;
            }
            else
            {
                NSLog(@"create system sound id fail,%s",__FUNCTION__);
                return 0;
            }
        }
        else
        {
            NSLog(@"path with NSString convert to path with NSURL fail!!!,%s",__FUNCTION__);
            return 0;
        }
    }
    else
    {
        NSLog(@"the path for sound play is nil,%s",__FUNCTION__);
        return 0;
    }
}

播放提示音是传入要播放的声音文件的名称,从bundle寻找对应文件找到了就进行播放,这里添加了系统声音播放完成后的回调,以便在声音播放结束后进行其他处理,比如如果需要根据不同的消息播放不同的提示音,如果当前正在播放提示音1,此时收到其他消息要播放提示音2,这里就不能简单的直接调用该方法进行播放了,我们需要一个播放队列,把要播放的提示音添加到队列中来处理,当播放完成一个后就需要从队列移除,此时播放完成的回调就派上用场了。

振动

如果要振动提示又该如何实现呢?系统为我们提供了振动的soundID kSystemSoundID_Vibrate,可以直接使用该soundID实现振动,代码如下:

void VibratePlayComplete(SystemSoundID soundId, void* clientData)
{
    AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate);
    AudioServicesDisposeSystemSoundID(kSystemSoundID_Vibrate);
}

- (void)playVibrate
{
    AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, NULL, NULL, VibratePlayComplete, (__bridge void*)self);
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}

这里振动实现与播放提示音方法基本一致,只是传的soundID参数不同,在播放提示音时ssid是根据我们的音频文件生成的,而振动是系统定义的。振动完成后也有回调方法进行特殊处理,与播放声音的实现一致。

通过上面的方法我们就实现了消息提示音及振动,这里只是简单的实现,但是在实际场景下还需要添加其他的逻辑处理,比如连续收消息,不能一直播放提示音或是振动吧,用户会不爽的,可以在播放完成提示音后设置一个时间间隔,比如2秒钟内收到消息就不播提示音不振动,超过2秒再有提示,这些都需要根据具体业务需求来处理了。

既然已经实现了播放提示音与振动,那如果我想根据系统是否静音来选择哪种提示:

  • 如果系统是静音模式,消息提示为振动
  • 如果系统是正常模式,消息提示为提示音

看起来不难嘛,我们读取系统的音量,根据音量大小来判断系统是否是静音不就OK了,那如何读取系统音量呢,一番查资料后,找到如下几种方案:

方案1:

Float32 volume;
UInt32 dataSize = sizeof(Float32);
AudioSessionGetProperty (
                     kAudioSessionProperty_CurrentHardwareOutputVolume,
                     &dataSize,
                     &volume
                     );

该方案中获取的音量值一直不变。放弃!

方案2:链接

 - (BOOL)isMuted  
{  
    CFStringRef route;  
    UInt32 routeSize = sizeof(CFStringRef);  
    OSStatus status = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &routeSize, &route);  
    if (status == kAudioSessionNoError)  
    {  
        if (route == NULL || !CFStringGetLength(route))  
            return TRUE;  
    }  
    return FALSE;  
} 

 - (BOOL)addMutedListener  
{  
    OSStatus s = AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,  
    audioRouteChangeListenerCallback,  
    self);  
    return s == kAudioSessionNoError;  
}  
void audioRouteChangeListenerCallback (void *inUserData,  
                                       AudioSessionPropertyID inPropertyID,  
                                       UInt32 inPropertyValueSize,  
                                       const void *inPropertyValue  
                                       )  
{  
    if (inPropertyID != kAudioSessionProperty_AudioRouteChange) return;  
    BOOL muted = [mediaVolume isMuted];  
    // add code here  
}

该方案中使用的方法在iOS7中已不建议使用了,而且该方案在尝试的过程中一直不成功。

方案3:链接

该方案中使用的方法是播放一段无声的系统声,判断播放完成所需要的时间,如果获得的时长接近0则表示系统处于静音状态。该方案通过笨方法实现静音的检测,虽然性能不高,因为要一直循环着播放无声的音频文件来检测系统静音状态,但是可以通过正常的API调用来得到系统状态。

根据上面的三种方案感觉通过判断系统音量的方案可以放弃了,再想想其他办法,随着iOS系统的不断完善,硬件也有直接切换静音的按钮(可设置),系统会不会自动帮我们处理静音振动与非静音播放声音呢?

- (void)playMsgSound
{
    [self playDefaultSound];
    [self playVibrate];
}

摒着试一试的心态在收到消息时先播放声音,再振动,并尝试通过按键设置系统的静音状态,这里先说明一下iOS系统的设置里针对振动的设置项:

  1. 响铃模式振动
  2. 静音模式振动
  • 关闭1,在收到消息时切换静音状态,可以得到结果静音下只振动,非静音下只播放声音
  • 关闭2,静音下不振动也没有播放声音,非静音下,振动+播放声音
  • 关闭1和2,不管在静音状态还是非静音状态都没振动和声音
  • 打开1和2,静音下振动,非静音下振动+声音

因此我们可以得出系统已经根据用户的设置自动选择了振动与声音模式,我们可以不用判断系统音量来选择振动或是播放声音了,直接播放声音,然后振动。到此问题算是解决了。

插播一黑魔法

在上面的方法中振动我们使用系统定义的kSystemSoundID_Vibrate来实现,如果在设置中关闭振动选项,就不会再振动了,如果此时不管怎样都要振动,该怎么办呢?黑魔法黑科技来拯救你:

#define kSystemSoundID_Vibrate 1352
- (void)playVibrate
{
    AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, NULL, NULL, VibratePlayComplete, (__bridge void*)self);
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}

1352 完全无视系统的静音振动设置,至于为什么会成功还没有深入研究。待补充~~

如果使用该方法能不能正常通过苹果审核不确定,如若被拒,后果自负~~

参考

]]>