Слабая семантика аргументов метода
Есть ли способ указать, что конкретный аргумент метода имеет слабую семантику?
Чтобы уточнить, это пример кода Objective C, который работает как ожидалось:
- (void)runTest {
__block NSObject *object = [NSObject new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self myMethod:object];
});
// to make sure it happens after `myMethod:` call
dispatch_async(dispatch_get_main_queue(), ^{
object = nil;
});
}
- (void)myMethod:(__weak id)arg0 {
NSLog(@"%@", arg0); // <NSObject: 0x7fb0bdb1eaa0>
sleep(1);
NSLog(@"%@", arg0); // nil
}
Это версия Swift, которая не
public func runTest() {
var object: NSObject? = NSObject()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
self.myMethod(object)
}
dispatch_async(dispatch_get_main_queue()) {
object = nil
}
}
private func myMethod(arg0: AnyObject?) {
println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>)
sleep(1)
println("\(arg0)") //Optional(<NSObject: 0x7fc778f26cf0>)
}
Правильно ли я в предположении ym, что arg0 не может стать nil между вызовами методов в версии Swift? Спасибо!
Обновление пользователя от Apple Dev.Forums указало, что sleep
это плохая функция, и последовательные рассылки могут вызвать состояние гонки. Хотя это может вызывать разумные опасения, это всего лишь пример кода, но основное внимание уделяется передаче слабых аргументов.
3 ответа
У Swift нет "слабых аргументов"… но это, вероятно, только потому, что аргументы Swift (3.0) неизменны (эквивалентно let
с) и weak
вещи в Swift должны быть как var
и Optional
,
Тем не менее, действительно есть довольно простой способ сделать эквивалент слабых аргументов - использовать weak var
local (освобождает arg-var для освобождения). Это работает, потому что Swift не держится на переменные до конца текущей области (как это строго делает C++); скорее он освобождает переменные из области видимости после их последнего использования (что делает lldb
-Пита иногда, но что угодно).
Следующий пример работает последовательно в Swift 3.0.2 на Xcode 8.2.1 на macOS 10.11.6:
class Test
{
func runTest() {
var object:NSObject? = NSObject()
myMethod(arg0: object)
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + 1.0,
qos: .userInteractive,
flags: DispatchWorkItemFlags.enforceQoS
){
object = nil
}
}
func myMethod(arg0:AnyObject?) {
weak var arg0Weak = arg0
// `arg0` get “released” at this point. Note: It's essential that you
// don't use `arg0` in the rest of this method; only use `arg0Weak`.
NSLog("\(arg0Weak)"); // Optional(<NSObject: 0x600000000810>)
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + 2.0,
qos: .userInteractive,
flags: DispatchWorkItemFlags.enforceQoS
){
NSLog("\(arg0Weak)"); // nil
}
}
}
Test().runTest()
Обратите внимание, что если вы попробуете это на игровой площадке, игровая площадка завершит выполнение до того, как DispatchQueue
с огнем. Самый простой способ заставить исполняемый файл работать бесконечно (что я и сделал) - создать новое приложение Cocoa и вставить весь код выше в func applicationDidFinishLaunching(_:Notification) { … }
(да, дословно - Swift допускает определения классов, вложенные в методы).
В ответ на лекцию о безопасности потоков вы перестали использовать dispatch_async
& sleep
в вашем примере, чтобы доказать, что слабые аргументы действительно реальны, вот полный main.m
исходный вариант вашего теста, который является однопоточным и без очереди:
#import <Foundation/Foundation.h>
@interface Test : NSObject
- (void)runTest;
- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Test new] runTest];
}
return 0;
}
@implementation Test
- (void)runTest {
__block NSObject *object = [NSObject new];
[self myMethod:object callback:^{
object = nil;
}];
}
- (void)myMethod:(__weak id)arg0 callback:(void (^)())callback {
NSLog(@"%@", arg0); // <NSObject: 0x100400bc0>
callback();
NSLog(@"%@", arg0); // (null)
}
@end
С языковым синтаксисом пока нет возможности.
Я думаю, что этот обходной путь является самым близким на данный момент.
public struct Weak<T> where T: AnyObject {
public weak var object: T?
public init(_ object: T?) {
self.object = object
}
}
func run(_ a: Weak<A>) {
guard let a = a.object else { return }
}
Есть ли способ указать, что конкретный аргумент метода имеет слабую семантику?
Это не то, что делает ваш пример кода Objective C. Вы получаете случайно почти слабую семантику и у вас есть неопределенное поведение (состояние расы), которого нет у реальных слабых ссылок.
myMethod
может отправить сообщение в la-la-land в любой точке последовательности (первая NSLog
заявление или второе, или даже в середине NSLog
где-то... даже если ARC не исключает сохранение arg0
вы по-прежнему участвуете в выпуске главной очереди или еще хуже - сохраняете объект зомби).
Объявить что-то как __block
просто означает выделить слот в среде кучи для блока (потому что dispatch_async
гарантированно пропустит блок, который он превратит из выделенного в стеке блока в блок кучи, и один из слотов хранения в этой среде блока кучи будет для вас __block
переменная. Под ARC блок автоматически будет иметь Block_copy
называется, возможно, более удачно назван Block_copy_to_heap
).
Это означает, что оба выполняющихся экземпляра блока будут указывать на одну и ту же ячейку памяти.
Если это поможет, представьте этот действительно глупый код, который имеет очевидное состояние гонки. Есть 1000 блоков в очереди одновременно все пытаются изменить unsafe
, Нам почти гарантировано выполнение неприятных операторов внутри блока if, потому что наше присваивание и сравнение не являются атомарными, и мы боремся за одну и ту же область памяти.
static volatile size_t unsafe = 0;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(1000, queue, ^(size_t instance) {
unsafe = instance;
if (unsafe != instance) {
FORMAT_ALL_STORAGE();
SEND_RESIGNATION_EMAIL();
WATCH_THE_WORLD_BURN();
}
});
В вашем примере Swift такой же проблемы нет, потому что блок, который не изменяет значение, вероятно, захватывает (и сохраняет) объект, поэтому не видит изменения из другого блока.
Создатель замыкания должен иметь дело с последствиями управления памятью, поэтому вы не можете создать контракт API, который не предусматривает циклов сохранения в замыкании, кроме пометки чего-либо как @noescape
в этом случае Swift не будет выполнять сохранение / освобождение или другое управление памятью, потому что блок не переживает текущий кадр стека. Это исключает асинхронную отправку по очевидным причинам.
Если вы хотите представить контракт API, который решает эту проблему, вы можете иметь тип принять протокол protocol Consumer: class { func consume(thing: Type) }
затем внутри вашего API храните слабую ссылку на Consumer
экземпляров.
Другой метод заключается в том, чтобы принять карри версию функции экземпляра и слабо захватить self
:
protocol Protocol: class { }
typealias FuncType = () -> Void
var _responders = [FuncType]()
func registerResponder<P: Protocol>(responder: P, usingHandler handler: (P) -> () -> Void) {
_responders.append({ [weak responder] in
guard let responder = responder else { return }
handler(responder)()
})
}
class Inst: Protocol {
func myFunc() {
}
}
let inst = Inst()
registerResponder(inst, usingHandler: Inst.myFunc)