스위프트는 Objective-C의“@synchronized”에 해당하는 것은 무엇입니까?


231

Swift 책을 검색했지만 @synchronized의 Swift 버전을 찾을 수 없습니다. Swift에서 상호 배제를 어떻게합니까?


1
파견 장벽을 사용합니다. 배리어는 매우 저렴한 동기화를 제공합니다. dispatch_barrier_async (). 등
— 프레 더릭 C. 리

@ FrederickC.Lee, 래퍼를 만들 때와 같이 쓰기 를 동기화 하려면 어떻게해야 removeFirst()합니까?
— ScottyBlades 2016 년

답변:


183

GCD를 사용할 수 있습니다. 보다 약간 장황 @synchronized하지만 대체 기능으로 작동합니다.

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
    // code
}

12
이것은 훌륭하지만 @synchronized로 재 입력 할 수있는 능력이 부족합니다.
— Michael Waterfall

9
이 접근 방식을 사용하면 조심해야합니다. 다른 스레드에서 블록이 실행될 수 있습니다. API 문서에 따르면 : "최적화로서이 함수는 가능한 경우 현재 스레드에서 블록을 호출합니다."
— bio

20

4
아니, 가끔 교착 상태가 발생합니다.
— Tom Kraina

70
아냐 아냐 좋은 시도이지만 완벽하게 작동합니다. 왜? Matt Gallagher의 필수 자료 (대안,주의 사항에 대한 포괄적 인 비교) 및 훌륭한 유틸리티 프레임 워크 : cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html @ wuf810이 처음 언급 한 (HT) 이 기사가 얼마나 좋은지 과소 평가했다. 모두 읽어야합니다. (더 이상은 초기에 볼 수 있도록하기위한 최소하여이 작업을 upvote에, 그러나하시기 바랍니다.)
— t0rst

181

나는 이것을 직접 찾고 있었고 아직 이것에 대한 신속한 내부에 네이티브 구성이 없다는 결론에 도달했습니다.

Matt Bridges 및 다른 코드에서 본 코드 중 일부를 기반 으로이 작은 도우미 기능을 구성했습니다.

func synced(_ lock: Any, closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

사용법은 매우 간단합니다

synced(self) {
    println("This is a synchronized closure")
}

내가 찾은 문제가 하나 있습니다. 잠금 인수로 배열을 전달하면이 시점에서 매우 혼란스러운 컴파일러 오류가 발생하는 것으로 보입니다. 그렇지 않으면 원하는대로 작동하는 것 같습니다.

Bitcast requires both operands to be pointer or neither
  %26 = bitcast i64 %25 to %objc_object*, !dbg !378
LLVM ERROR: Broken function found, compilation aborted!

좋은! 1.0에서 여전히 문제가되는 경우 버그를 제기하십시오.
— MattD

14
이것은 매우 유용하고 구문 보존 @synchronized좋게 블록을하지만, 참고 그것은 같은 실제 내장 블록 문 동일하지 않은 것을 @synchronized때문에, 목표 - C의 블록 return과 break같은 주변 기능 / 루프 밖으로 뛰어 더 이상 작동 문 이것이 평범한 진술이라면 가능할 것입니다.
— newacct

3
그 배열은 값없는 참조로 전달에 오류 가능성 때문이다
— james_alvarez

9
던질 때에도 호출 defer을 보장하기 위해 새 키워드를 사용하는 것이 좋습니다 . objc_sync_exitclosure
— devios1

3
@ t0rst 링크 된 기사에 근거하여이 답변을 "flawed"라고 부르는 것은 유효하지 않습니다. 이 기사는이 방법이 "이상적인 것보다 약간 느리며" "애플 플랫폼으로 제한된다"고 밝혔다. 그렇다고해서 오랫동안 "결점"되지는 않습니다.
— RenniePet

150

나는 여기에 많은 답변을 좋아하고 사용하므로 가장 적합한 것을 선택하십시오. 즉, objective-c와 같은 것이 필요할 때 선호하는 방법 은 신속한 2에 도입 된 진술을 @synchronized사용합니다 defer.

{ 
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    //
    // code of critical section goes here
    //

} // <-- lock released when this block is exited

이 방법에 대한 좋은 일이, 중요한 부분은 원하는 어떤 방식으로 포함하는 블록을 종료 할 수 있다는 것입니다 (예를 들어, return, break, continue, throw), 그리고 "를 연기 문 내의 문은 프로그램 제어가 전송되는 방식에 상관없이 실행됩니다." 1


나는 이것이 아마도 여기에 제공된 가장 우아한 해결책이라고 생각합니다. 의견을 보내 주셔서 감사합니다.
— Scott D

3
무엇입니까 lock? lock초기화 방법
— Van Du Tran

6
lockobjective-c 객체입니다.
— ɲ euroburɳ

1
우수한! Swift 1이 소개되었을 때 몇 가지 잠금 도우미 메서드를 작성했으며 잠시 동안 다시 방문하지 않았습니다. 연기를 완전히 잊었다. 이것은 갈 길입니다!
— 랜디

나는 이것을 좋아하지만 Xcode 8에서 컴파일러 에러 "Braced 문장 블록은 사용되지 않은 클로저이다"라는 오류를 얻는다.
— 던컨 그로 네 발트

83

objc_sync_enter(obj: AnyObject?)와 사이에 문을 삽입 할 수 있습니다 objc_sync_exit(obj: AnyObject?). @synchronized 키워드는 표지 아래 해당 방법을 사용하고 있습니다. 즉

objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)

3
이것이 Apple의 개인 API 사용으로 간주됩니까?
— Drux

2
아니, objc_sync_enter및 objc_sync_exitObjc - sync.h에 정의 된 방법이며, 오픈 소스 : opensource.apple.com/source/objc4/objc4-371.2/runtime/...
— bontoJR

여러 스레드가 동일한 리소스에 액세스하려고하면 두 번째 스레드가 대기, 재시도 또는 충돌합니까?
— TruMan1

@bontoJR는 말에 추가, objc_sync_enter(…)& objc_sync_exit(…)등 아이폰 OS / 맥 OS / 기관이 제공 한 공개 헤더이다. API ( ….sdk경로 안에있는 것처럼 보입니다 usr/include/objc/objc-sync.h) . 공개 API인지 여부를 확인하는 가장 쉬운 방법은 (Xcode에서) 함수 이름을 입력하는 것입니다 (예 : objc_sync_enter()C 함수에 인수를 지정할 필요가 없음) . 해당 API의 헤더 파일 이 표시되면 공개입니다 (헤더가 공개되지 않은 경우 헤더를 볼 수 없으므로) .
— 슬립 D. 톰슨

75

@synchronizedObjective-C 의 지시문 아날로그는 rethrowsSwift에서 임의의 반환 유형과 훌륭한 동작을 가질 수 있습니다 .

// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

의 사용 defer문은 직접 임시 변수를 도입하지 않고 값을 반환 할 수 있습니다.


Swift 2에서 @noescape더 많은 최적화를 허용하기 위해 속성을 클로저에 추가하십시오 .

// Swift 2
func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }
    return try body()
}

GNewc [1] (임의의 리턴 타입이 마음에 드는 곳)과 Tod Cunningham [2] (나는 좋아하는 곳 ) 의 답변을 바탕으로합니다 defer.


엑스 코드는 @noescape는 기본적 것을 말해되고 스위프트 3에서 더 이상 사용되지 않습니다
— RenniePet

맞습니다.이 답변의 코드는 Swift 2 용이며 Swift 3에 맞게 조정해야합니다. 시간이되면 업데이트하겠습니다.
— wasdiver

1
사용법을 설명 할 수 있습니까? 예를 들어 .. 미리 감사드립니다! 필자의 경우 DispatchQueue의 내용을 조작하기 때문에 동기화 해야하는 세트가 있습니다.
— sancho 2016 년

@sancho이 게시물을 간결하게 유지하고 싶습니다. 일반적인 동시 프로그래밍 지침에 대해 묻는 것 같습니다. 그것은 광범위한 질문입니다. 별도의 질문으로 요청하십시오!
— wasdiver

41

스위프트 4

Swift 4에서는 GCD 디스패치 큐를 사용하여 리소스를 잠글 수 있습니다.

class MyObject {
    private var internalState: Int = 0
    private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default

    var state: Int {
        get {
            return internalQueue.sync { internalState }
        }

        set (newState) {
            internalQueue.sync { internalState = newState }
        }
    }
} 

이것은 XCode8.1에서 작동하지 않는 것 같습니다. .serial사용할 수없는 것 같습니다. 그러나 .concurrent가능합니다. : /
— Travis Griggs

2
기본값은 .serial
— Duncan Groenewald

2
이 패턴은 대부분의 일반적인 멀티 스레드 문제를 제대로 방지하지 못합니다. 예를 들어, myObject.state = myObject.state + 1동시에 실행 하는 경우 총 작업 수를 계산하지 않고 비 결정적 값을 산출합니다. 이 문제를 해결하려면 읽기 및 쓰기가 원자 적으로 발생하도록 호출 코드를 직렬 큐에 래핑해야합니다. 물론 Obj-c도 @synchronised같은 문제가 있으므로 그런 의미에서 구현이 정확합니다.
— Berik

1
예, myObject.state += 1읽기와 쓰기 작업의 조합입니다. 다른 스레드는 값을 설정 / 쓰기 위해 여전히 사용될 수 있습니다. 당으로 objc.io/blog/2018/12/18/atomic-variables ,을 실행하는 것이 더 쉽습니다 set변수 자체에서 대신이 아닌 동기 블록 / 폐쇄에.
— CyberMew

23

반환 기능을 추가하려면 다음을 수행하십시오.

func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
  objc_sync_enter(lockObj)
  var retVal: T = closure()
  objc_sync_exit(lockObj)
  return retVal
}

이어서 다음을 사용하여 호출 할 수 있습니다.

func importantMethod(...) -> Bool {
  return synchronize(self) {
    if(feelLikeReturningTrue) { return true }
    // do other things
    if(feelLikeReturningTrueNow) { return true }
    // more things
    return whatIFeelLike ? true : false
  }
}

23

Bryan McLemore의 답변을 사용하여 Swift 2.0 지연 기능으로 안전한 영지에 던지는 객체를 지원하도록 확장했습니다.

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
    objc_sync_enter(lock)
    defer {
        objc_sync_exit(lock)
    }

    try block()
}

내 답변 에서 볼 수 있듯이 rethrows던지지 않는 클로저 (사용하지 않아도 됨 try) 로 사용 을 단순화 하는 것이 좋습니다 .
— wasdiver

10

스위프트 3

이 코드는 재 입력 기능이 있으며 비동기 함수 호출과 함께 작동 할 수 있습니다. 이 코드에서 someAsyncFunc ()가 호출 된 후 직렬 큐의 다른 함수 클로저는 처리되지만 signal ()이 호출 될 때까지 semaphore.wait ()에 의해 차단됩니다. 실수하지 않으면 기본 스레드를 차단하므로 internalQueue.sync를 사용하면 안됩니다.

let internalQueue = DispatchQueue(label: "serialQueue")
let semaphore = DispatchSemaphore(value: 1)

internalQueue.async {

    self.semaphore.wait()

    // Critical section

    someAsyncFunc() {

        // Do some work here

        self.semaphore.signal()
    }
}

objc_sync_enter / objc_sync_exit는 오류 처리 없이는 좋은 생각이 아닙니다.


어떤 오류 처리? 컴파일러는 던지는 것을 허용하지 않습니다. 반면에 objc_sync_enter / exit를 사용하지 않으면 성능이 크게 떨어집니다.
— gnasher729

8

2018 WWDC의 "충돌 및 충돌 로그 이해" 세션 414 에서 동기화와 함께 DispatchQueue를 사용하여 다음과 같은 방법을 보여줍니다.

신속한 4에서는 다음과 같아야합니다.

class ImageCache {
    private let queue = DispatchQueue(label: "sync queue")
    private var storage: [String: UIImage] = [:]
    public subscript(key: String) -> UIImage? {
        get {
          return queue.sync {
            return storage[key]
          }
        }
        set {
          queue.sync {
            storage[key] = newValue
          }
        }
    }
}

어쨌든 장벽이있는 동시 큐를 사용하여 더 빠르게 읽을 수 있습니다. 동기화 및 비동기 읽기는 동시에 수행되며 새 값을 쓰면 이전 작업이 완료 될 때까지 기다립니다.

class ImageCache {
    private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
    private var storage: [String: UIImage] = [:]

    func get(_ key: String) -> UIImage? {
        return queue.sync { [weak self] in
            guard let self = self else { return nil }
            return self.storage[key]
        }
    }

    func set(_ image: UIImage, for key: String) {
        queue.async(flags: .barrier) { [weak self] in
            guard let self = self else { return }
            self.storage[key] = image
        }
    }
}

동기화를 사용하여 읽기를 차단하고 대기열을 느리게 할 필요는 없습니다. 직렬 쓰기에는 sync를 사용할 수 있습니다.
— Basheer_CAD

6

Swift4에서 NSLock 사용 :

let lock = NSLock()
lock.lock()
if isRunning == true {
        print("Service IS running ==> please wait")
        return
} else {
    print("Service not running")
}
isRunning = true
lock.unlock()

경고 NSLock 클래스는 POSIX 스레드를 사용하여 잠금 동작을 구현합니다. 잠금 해제 메시지를 NSLock 객체로 보낼 때 초기 잠금 메시지를 보낸 동일한 스레드에서 메시지가 보내 졌는지 확인해야합니다. 다른 스레드에서 잠금을 잠금 해제하면 동작이 정의되지 않을 수 있습니다.



6

최신 Swift 5에서 리턴 기능 :

/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
    objc_sync_enter(lock)
    defer { objc_sync_exit(lock) }

    return closure()
}

리턴 값 기능을 이용하려면 다음과 같이 사용하십시오.

let returnedValue = synchronized(self) { 
     // Your code here
     return yourCode()
}

아니면 그렇지 않으면 :

synchronized(self) { 
     // Your code here
    yourCode()
}

2
이것은 정답이며 인정되고 고도로 찬성 된 답변이 아닙니다 (에 따라 다름 GCD). 본질적으로 아무도 사용 방법을 사용하거나 이해하지 못하는 것 같습니다 Thread. 나는 그것에 행복하다-반면 GCD에 어려움과 한계로 가득하다.
— javadba

4

시도 : NSRecursiveLock

교착 상태를 유발하지 않고 동일한 스레드에서 여러 번 획득 할 수있는 잠금입니다.

let lock = NSRecursiveLock()

func f() {
    lock.lock()
    //Your Code
    lock.unlock()
}

func f2() {
    lock.lock()
    defer {
        lock.unlock()
    }
    //Your Code
}

2

그림 이전 답변으로 작성된 Swift 5 구현을 게시합니다. 고마워요! 값을 반환하는 것이 도움이되므로 두 가지 방법이 있습니다.

먼저 만드는 간단한 수업은 다음과 같습니다.

import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        closure()
    }
    public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
        objc_sync_enter(lock)
        defer { objc_sync_exit(lock) }
        return closure()
    }
}

그런 다음 반환 값이 필요한 경우처럼 사용하십시오.

return Sync.syncedReturn(self, closure: {
    // some code here
    return "hello world"
})

또는:

Sync.synced(self, closure: {
    // do some work synchronously
})

try public class func synced<T>(_ lock: Any, closure: () -> T)는 void 및 다른 유형 모두에서 작동합니다. 재성장 물건도 있습니다.
— hnh

@hnh 당신은 재성장 물건 무엇을 의미합니까? 또한 <T> 유형의 일반 메소드에 대한 예제 호출을 기꺼이 공유하면 답변을 업데이트하는 데 도움이됩니다.
— TheJeff

다시 자라지 않고 다시 자라다, srz
— hnh

1

세부

xCode 8.3.1, 신속한 3.1

직무

다른 스레드에서 읽기 값을 읽습니다 (비동기).

암호

class AsyncObject<T>:CustomStringConvertible {
    private var _value: T
    public private(set) var dispatchQueueName: String

    let dispatchQueue: DispatchQueue

    init (value: T, dispatchQueueName: String) {
        _value = value
        self.dispatchQueueName = dispatchQueueName
        dispatchQueue = DispatchQueue(label: dispatchQueueName)
    }

    func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                _self._value = closure(_self._value)
            }
        }
    }

    func getValue(with closure: @escaping (_ currentValue: T)->() ) {
        dispatchQueue.sync { [weak self] in
            if let _self = self {
                closure(_self._value)
            }
        }
    }


    var value: T {
        get {
            return dispatchQueue.sync { _value }
        }

        set (newValue) {
            dispatchQueue.sync { _value = newValue }
        }
    }

    var description: String {
        return "\(_value)"
    }
}

용법

print("Single read/write action")
// Use it when when you need to make single action
let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
obj.value = 100
let x = obj.value
print(x)

print("Write action in block")
// Use it when when you need to make many action
obj.setValue{ (current) -> (Int) in
    let newValue = current*2
    print("previous: \(current), new: \(newValue)")
    return newValue
}

전체 샘플

확장 DispatchGroup

extension DispatchGroup {

    class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
        let group = DispatchGroup()
        for index in 0...repeatNumber {
            group.enter()
            DispatchQueue.global(qos: .utility).async {
                action(index)
                group.leave()
            }
        }

        group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
            completion()
        }
    }
}

ViewController 클래스

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //sample1()
        sample2()
    }

    func sample1() {
        print("=================================================\nsample with variable")

        let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")

        DispatchGroup.loop(repeatNumber: 5, action: { index in
            obj.value = index
        }) {
            print("\(obj.value)")
        }
    }

    func sample2() {
        print("\n=================================================\nsample with array")
        let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
        DispatchGroup.loop(repeatNumber: 15, action: { index in
            arr.setValue{ (current) -> ([Int]) in
                var array = current
                array.append(index*index)
                print("index: \(index), value \(array[array.count-1])")
                return array
            }
        }) {
            print("\(arr.value)")
        }
    }
}

1

Swift의 속성 래퍼를 사용하면 이것이 현재 사용중인 것입니다.

@propertyWrapper public struct NCCSerialized<Wrapped> {
    private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")

    private var _wrappedValue: Wrapped
    public var wrappedValue: Wrapped {
        get { queue.sync { _wrappedValue } }
        set { queue.sync { _wrappedValue = newValue } }
    }

    public init(wrappedValue: Wrapped) {
        self._wrappedValue = wrappedValue
    }
}

그럼 당신은 할 수 있습니다 :

@NCCSerialized var foo: Int = 10

또는

@NCCSerialized var myData: [SomeStruct] = []

그런 다음 평소처럼 변수에 액세스하십시오.


1
나는이 솔루션을 좋아하지만 @Decorating 사람들의 비용에 대해 궁금했다. 왜냐하면 그렇게 DispatchQueue하면 사용자에게 숨겨진 것을 만드는 부작용 이 있습니다. 이 SO 참조가 마음을 편안하게하는 것을 발견했습니다. stackoverflow.com/a/35022486/1060314
— Adam Venturella

속성 래퍼 자체는 아주 가볍습니다. 구조체이기 때문에 만들 수있는 가장 가벼운 것 중 하나입니다. DispatchQueue의 링크에 감사드립니다. queue.sync 랩에서 다른 솔루션 (및 대기열 없음)에서 성능 테스트를 수행해야했지만 그렇게하지 않았습니다.
— drewster

1

결론적으로, 여기에는 반환 값 또는 void를 포함하고 던지는 일반적인 방법이 있습니다.

import Foundation

extension NSObject {


    func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
    {
        objc_sync_enter(lockObj)
        defer {
            objc_sync_exit(lockObj)
        }

        return try closure()
    }


}

0

왜 어렵고 자물쇠가 번거 롭습니까? 디스패치 배리어를 사용하십시오.

디스패치 장벽은 동시 큐 내에 동기화 지점을 작성합니다.

실행 중일 때 동시에 다른 코어를 사용할 수 있어도 큐의 다른 블록을 실행할 수 없습니다.

그것이 독점적 인 (쓰기) 잠금처럼 들린다면 그것은 그렇습니다. 비 배리어 블록은 공유 (읽기) 잠금으로 생각할 수 있습니다.

리소스에 대한 모든 액세스가 큐를 통해 수행되는 한 장벽은 매우 저렴한 동기화를 제공합니다.


2
내 말은, 액세스를 동기화하기 위해 GCD 큐를 사용한다고 가정하지만 원래 질문에서는 언급하지 않았습니다. 장벽은 동시 대기열에만 필요합니다. 직렬 대기열을 사용하여 상호 배제 된 블록을 대기열에 넣어 잠금을 에뮬레이트 할 수 있습니다.
— Bill

내 질문, 왜 자물쇠를 모방합니까? 내가 읽은 내용에서 오버 헤드 대 큐 내의 장벽으로 인해 잠금이 권장되지 않습니다.
— Frederick C. Lee

0

유로 버 (Euroburɳ )를 기반으로 서브 클래스 케이스 테스트

class Foo: NSObject {
    func test() {
        print("1")
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)
            print("3")
        }

        print("2")
    }
}


class Foo2: Foo {
    override func test() {
        super.test()

        print("11")
        objc_sync_enter(self)
        defer {
            print("33")
            objc_sync_exit(self)
        }

        print("22")
    }
}

let test = Foo2()
test.test()

산출:

1
2
3
11
22
33

0

dispatch_barrier_async가 더 나은 방법이지만 현재 스레드를 차단하지는 않습니다.

dispatch_barrier_async (accessQueue, {dictionary [object.ID] = 객체})


-5

또 다른 방법은 수퍼 클래스를 만든 다음 상속하는 것입니다. 이런 식으로 GCD를 더 직접 사용할 수 있습니다

class Lockable {
    let lockableQ:dispatch_queue_t

    init() {
        lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
    }

    func lock(closure: () -> ()) {
        dispatch_sync(lockableQ, closure)
    }
}


class Foo: Lockable {

    func boo() {
        lock {
            ....... do something
        }
    }

10
-1 상속은 커플 링 증가에 대한 대가로 하위 유형 다형성을 제공합니다. 전자가 필요하지 않으면 나중에 피하십시오. 게으르지 마십시오. 코드 재사용을위한 구성을 선호하십시오.
— Jano
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.